From 8d12a0384bf471add7817bf5af78b6e1e235a4f4 Mon Sep 17 00:00:00 2001 From: timmy Date: Mon, 17 Aug 2026 12:44:01 +0000 Subject: [PATCH] feat: hand off active Today sessions across devices (Closes #1022) --- frontend/dashboard.css | 4 + frontend/dashboard.js | 11 ++ frontend/index.html | 9 ++ frontend/service-worker.js | 1 + frontend/today-session-sync.js | 149 +++++++++++++++++++++++++ frontend/today-timer.js | 19 +++- src/frontend_bundle.py | 2 +- src/main.py | 43 ++++++- src/today_store.py | 76 ++++++++++++- tests/test_service_worker.py | 1 + tests/test_today_session_handoff_ui.py | 23 ++++ tests/test_today_session_sync.py | 121 ++++++++++++++++++++ tests/test_today_store.py | 93 ++++++++++++++- tests/test_today_timer_handoff.py | 51 +++++++++ 14 files changed, 596 insertions(+), 7 deletions(-) create mode 100644 frontend/today-session-sync.js create mode 100644 tests/test_today_session_handoff_ui.py create mode 100644 tests/test_today_session_sync.py create mode 100644 tests/test_today_timer_handoff.py diff --git a/frontend/dashboard.css b/frontend/dashboard.css index fcc993d..00656fb 100644 --- a/frontend/dashboard.css +++ b/frontend/dashboard.css @@ -926,6 +926,10 @@ textarea { resize: vertical; min-height: 120px; } .mobile-today-hud { display:none; } .mobile-today-hud[hidden], .mobile-today-hud[data-overlay-hidden="true"] { display:none; } +.today-session-handoff { position:fixed; left:12px; right:12px; bottom:calc(68px + env(safe-area-inset-bottom)); z-index:45; display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:10px; padding:12px; border:1px solid #60a5fa; border-radius:12px; background:rgba(16,38,65,.98); box-shadow:0 8px 28px rgba(0,0,0,.35); } +.today-session-handoff[hidden] { display:none; } +.today-session-handoff button { min-height:44px; } +@media(max-width:420px) { .today-session-handoff { grid-template-columns:1fr; } .today-session-handoff button { width:100%; } } .mobile-task-action { min-width:0; min-height:44px; padding:6px 2px; border:0; border-radius:8px; background:transparent; display:grid; place-items:center; gap:2px; font-size:12px; } .mobile-task-action[hidden] { display:none; } .mobile-task-action[aria-current="page"] { color:#bfdbfe; background:#17365a; outline:1px solid #31577f; } diff --git a/frontend/dashboard.js b/frontend/dashboard.js index 4155af6..6c02429 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -1795,9 +1795,11 @@ 'Agenda progress could not be saved on this device. You can keep working.'; }, }); + let todaySessionSync = null; const timer = createTodayTimer({ storage: localStorage, getLogin: () => confirmedOwnerLogin, + onChange: snapshot => todaySessionSync?.publish(snapshot), }); const timerView = createTodayTimerView({ timer, @@ -1814,6 +1816,15 @@ return completeTodayItem(item); }, }); + todaySessionSync = attachTodaySessionHandoff({ + fetchJson:fetchReviewJson, storage:localStorage, timer, qs, + items:() => [...todayMyWork, ...activeMyWork], + identity:item => todayWork.identity(item), + selectToday:() => selectTodayWork(), + startItem:item => workSession.start(item), + announce:message => { qs('#my-work-action-status').textContent = message; }, + renderTimer:() => timerView.render(), + }); renderAttentionInterruption(); qs('#return-to-today').addEventListener('click', () => { const returned = timer.returnFromAttention(); diff --git a/frontend/index.html b/frontend/index.html index 6d2c4f3..c49efc8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1515,6 +1515,14 @@ + +