From 9629643a63c62c8da2c15f96e39a63ea1397e1d2 Mon Sep 17 00:00:00 2001 From: timmy Date: Tue, 11 Aug 2026 22:27:53 +0000 Subject: [PATCH] feat: pause Today while handling Attention (Closes #595) --- frontend/dashboard.css | 3 ++ frontend/dashboard.js | 20 +++++++++ frontend/index.html | 4 ++ frontend/service-worker.js | 2 +- frontend/today-timer.js | 43 +++++++++++++++++++- tests/test_comment_next.py | 2 +- tests/test_frontend_bundle.py | 2 +- tests/test_later_sync.py | 2 +- tests/test_markdown_renderer.py | 2 +- tests/test_mobile_composer_integration.py | 2 +- tests/test_mobile_device_setup.py | 2 +- tests/test_mobile_task_dock.py | 15 +++++++ tests/test_my_work.py | 49 +++++++++++++++++++++++ tests/test_plan_today.py | 2 +- tests/test_service_worker.py | 20 ++++----- tests/test_today_readiness.py | 2 +- tests/test_today_sync.py | 2 +- 17 files changed, 153 insertions(+), 21 deletions(-) diff --git a/frontend/dashboard.css b/frontend/dashboard.css index 4f14881..1353074 100644 --- a/frontend/dashboard.css +++ b/frontend/dashboard.css @@ -506,6 +506,8 @@ textarea { resize: vertical; min-height: 120px; } .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[aria-current="page"] { color:#bfdbfe; background:#17365a; outline:1px solid #31577f; } .mobile-task-count { min-width:18px; min-height:18px; padding:1px 5px; border-radius:999px; background:#31577f; font-size:11px; line-height:16px; } +.attention-interruption:not([hidden]) { max-width:100%; display:flex; align-items:center; justify-content:space-between; gap:10px; margin:0 0 12px; padding:10px 12px; border:1px solid #31577f; border-radius:10px; background:#102641; } +.attention-interruption button { min-height:44px; flex:0 0 auto; } @media (max-width: 600px) { body { padding-bottom:calc(66px + env(safe-area-inset-bottom)); } header { min-height:56px; max-height:64px; padding:6px 10px; align-items:center; gap:8px; background:rgba(11,21,38,.98); } @@ -522,6 +524,7 @@ textarea { resize: vertical; min-height: 120px; } .device-setup-action { width:100%; } .device-readiness-card:not([hidden]) { display:grid; gap:12px; margin-left:max(0px,env(safe-area-inset-left)); margin-right:max(0px,env(safe-area-inset-right)); } .my-work { margin:0; } + .attention-interruption:not([hidden]) { position:sticky; top:64px; z-index:6; margin-inline:max(0px,env(safe-area-inset-left)) max(0px,env(safe-area-inset-right)); } .my-work-header { align-items:flex-start; } .my-work-actions { width:100%; flex-wrap:nowrap; } .my-work-actions button { min-height:44px; flex:1 1 0; padding-inline:6px; } diff --git a/frontend/dashboard.js b/frontend/dashboard.js index 98cf616..e89c158 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -44,12 +44,21 @@ Array.from(document.querySelectorAll('[data-mobile-task]')).map(button => [button.dataset.mobileTask, button]) ); const mobileTaskOverlays = Array.from(document.querySelectorAll('[role="dialog"], #whiteboard-modal, #markdown-modal')); + const attentionInterruption = qs('#attention-interruption'); + function renderAttentionInterruption() { + const pending = timer.attentionInterruption(); + attentionInterruption.hidden = !pending; + return pending; + } function openMobileWorkFallback() { qs('[data-work-filter="all"]').click(); qs('#my-work').scrollIntoView({block:'start'}); qs('#my-work').focus(); } function openMobileAttention() { + if (workSession.checkpointed()) timer.beginAttention(); + renderAttentionInterruption(); + timerView.render(); qs('[data-work-filter="attention"]').click(); qs('#my-work').scrollIntoView({block:'start'}); qs('#my-work').focus(); @@ -941,6 +950,17 @@ queryAll: s => document.querySelectorAll(s), formatEstimate: formatPlanMinutes, }); + renderAttentionInterruption(); + qs('#return-to-today').addEventListener('click', () => { + const returned = timer.returnFromAttention(); + renderAttentionInterruption(); + if (!returned) return; + selectTodayWork(); + const item = todayMyWork.find(entry => todayWork.identity(entry) === returned.identity); + if (item) workSession.reopen(item); + else continueTodaySession(); + timerView.render(); + }); const interruptionPrompt = createTodayInterruptionPrompt({ timer, sheet: qs('#today-interruption-sheet'), diff --git a/frontend/index.html b/frontend/index.html index 59b9ade..bef97e7 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -147,6 +147,10 @@ +