diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 4ba122b..cb42df1 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: pip install -r requirements-e2e.txt python3 -m playwright install --with-deps chromium - name: Exercise packaged mobile work journeys - run: python3 -m pytest tests/e2e/test_mobile_offline_issue_release.py tests/e2e/test_mobile_search_preview_navigation.py tests/e2e/test_mobile_find_work_release.py tests/e2e/test_mobile_today_handoff_release.py tests/e2e/test_mobile_today_wrap_up_release.py -q + run: python3 -m pytest tests/e2e/test_mobile_offline_issue_release.py tests/e2e/test_mobile_search_preview_navigation.py tests/e2e/test_mobile_find_work_release.py tests/e2e/test_mobile_today_handoff_release.py tests/e2e/test_mobile_today_wrap_up_release.py tests/e2e/test_mobile_wrap_up_handoff_release.py -q release-candidate: runs-on: ubuntu-latest diff --git a/frontend/dashboard.css b/frontend/dashboard.css index 716387d..1a6c239 100644 --- a/frontend/dashboard.css +++ b/frontend/dashboard.css @@ -271,6 +271,19 @@ textarea { resize: vertical; min-height: 120px; } .today-wrap-up-item input { width:24px; min-height:24px; } .today-wrap-up-actions { position:sticky; bottom:0; margin:16px -6px -6px; padding:12px 6px; padding-bottom:calc(12px + env(safe-area-inset-bottom)); background:rgba(11,21,38,.98); border-top:1px solid #2a496e; } .today-wrap-up-actions button { min-height:44px; width:100%; } +.today-handoff-dialog { box-sizing:border-box; width:min(620px,100%); max-width:none; max-height:100dvh; margin:auto auto 0; padding:0; color:#e8f1ff; border:1px solid #2a496e; border-radius:18px 18px 0 0; background:#0b1526; } +.today-handoff-dialog::backdrop { background:rgba(5,12,21,.82); backdrop-filter:blur(4px); } +.today-handoff-panel { max-height:100dvh; overflow:auto; overflow-x:hidden; padding:18px; padding-bottom:calc(18px + env(safe-area-inset-bottom)); } +.today-handoff-panel header { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; } +.today-handoff-panel h2 { margin:.2rem 0; } +.today-handoff-panel header button { min-height:44px; min-width:44px; } +.today-handoff-items { display:grid; gap:8px; margin:14px 0; } +.today-handoff-item { display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:12px; padding:12px; border:1px solid #29486c; border-radius:12px; background:#101f35; } +.today-handoff-item span { display:block; } +.today-handoff-item label { display:flex; align-items:center; min-height:44px; gap:8px; font-weight:700; } +.today-handoff-item input { width:24px; min-height:24px; } +.today-handoff-actions { position:sticky; bottom:0; margin:16px -6px -6px; padding:12px 6px; padding-bottom:calc(12px + env(safe-area-inset-bottom)); background:rgba(11,21,38,.98); border-top:1px solid #2a496e; } +.today-handoff-actions button { min-height:44px; width:100%; } @media (max-width:420px) { .today-interruption-actions { grid-template-columns:1fr; } .today-recap-actions { grid-template-columns:1fr; } diff --git a/frontend/dashboard.js b/frontend/dashboard.js index ef51527..4c740ba 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -339,19 +339,36 @@ }, }); todaySync.startLifecycle({ window, document }); + const todayHandoff = createTodayHandoff({ + storage:localStorage, + getLogin:() => planningOwnerLogin, + identity:item => todayWork.identity(item), + items:() => [...todayMyWork, ...activeMyWork], + todayWork, + todaySync, + }); + const todayHandoffView = todayHandoff.mount({ qs, escapeHtml, + onComplete:() => refreshMyWorkView(), + }); + const promptTodayHandoff = () => todayHandoffView.open(); const laterWork = createLaterWork({ storage: localStorage, getLogin: () => planningOwnerLogin, - onChange: (action, itemId, wakeAt) => { - if (laterSync.enqueue(action, itemId, wakeAt)) laterSync.flush(); + onChange: (action, itemId, wakeAt, handoff) => { + if (laterSync.enqueue(action, itemId, wakeAt, handoff)) laterSync.flush(); }, - onExpire: ids => { + onExpire: (ids, handoffs) => { + if (handoffs.length) { + todayHandoff.capture(handoffs); + todayHandoffView.reset(); + } const queued = ids.map(id => laterSync.enqueue('restore', id)).every(Boolean); if (queued) laterSync.flush(); }, onWake: () => { qs('#my-work-action-status').textContent = 'Deferred work is ready again.'; refreshMyWorkView(); + setTimeout(promptTodayHandoff, 0); }, }); const laterSync = createLaterSync({ @@ -3014,6 +3031,7 @@ } if (reconcileSession && workSession.active()) workSession.reconcile(); updateWorkSessionActions(); + if (todayHandoff.pending().length) setTimeout(promptTodayHandoff, 0); } function activeWorkStreams() { diff --git a/frontend/index.html b/frontend/index.html index 5dec2fc..4cfbc57 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -413,6 +413,16 @@ + +