From bad7d524f02e226def0b8e86cb8c303ec5370810 Mon Sep 17 00:00:00 2001 From: timmy Date: Fri, 7 Aug 2026 22:32:41 +0000 Subject: [PATCH] feat: add mobile task dock (#236) --- README.md | 6 +++ frontend/index.html | 48 +++++++++++++++++- frontend/mobile-task-dock.js | 38 ++++++++++++++ frontend/service-worker.js | 3 +- tests/test_mobile_task_dock.py | 93 ++++++++++++++++++++++++++++++++++ tests/test_service_worker.py | 1 + 6 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 frontend/mobile-task-dock.js create mode 100644 tests/test_mobile_task_dock.py diff --git a/README.md b/README.md index 4eb64cd..45d1b7d 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,12 @@ HTTP 503 with an error when Gitea is unavailable or authentication fails. ## Offline mobile shell +At phone widths, a persistent bottom task dock keeps **Work**, **Find**, **New**, +**Search**, and **Drafts** thumb-reachable. It reuses the existing sheets and My Work +filter, preserves the selected release lane, shows the current draft count, respects +the device safe area, and moves out of the way while a full-screen task is open. +Desktop layout is unchanged. + After one successful online load, the installed dashboard precaches a versioned, subpath-scoped application shell. During a network outage or a dashboard HTTP `500`, `502`, `503`, or `504` response, navigation falls back to that shell when diff --git a/frontend/index.html b/frontend/index.html index 461ebbc..e370552 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -250,7 +250,13 @@ textarea { resize: vertical; min-height: 120px; } .pull-sheet-actions { position:sticky; bottom:0; display:grid; grid-template-columns:1fr 1fr; gap:8px; padding:10px 0; padding-bottom:calc(10px + env(safe-area-inset-bottom)); background:#0b1526; } .pull-sheet-actions a { display:grid; place-items:center; border:1px solid #60a5fa; border-radius:10px; font-weight:700; } .pull-retry { min-height:44px; width:100%; margin-top:10px; } +.mobile-task-dock { display:none; } +.mobile-task-dock[hidden] { display:none; } +.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; } @media (max-width: 600px) { + body { padding-bottom:calc(66px + env(safe-area-inset-bottom)); } header { align-items:flex-start; } .my-work { margin:0; } .my-work-list { grid-template-columns:1fr; } @@ -262,6 +268,7 @@ textarea { resize: vertical; min-height: 120px; } .search-preview-panel { width:100%; border-left:0; padding:14px; padding-bottom:calc(14px + env(safe-area-inset-bottom)); overflow-x:hidden; } .create-issue-panel { width:100%; border-left:0; padding:14px; } .pull-sheet-panel { width:100%; border-left:0; padding:14px; } + .mobile-task-dock { position:fixed; inset:auto 0 0; z-index:45; display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:2px; padding:6px 8px; padding-bottom:env(safe-area-inset-bottom); border-top:1px solid #2a496e; background:rgba(11,21,38,.98); backdrop-filter:blur(12px); } } .obi { width:14px; height:14px; background: url('data:image/svg+xml;utf8,') center/contain no-repeat; display:inline-block; } .footer { padding: 12px; text-align: center; color:#4e6b8a; font-size:12px; } @@ -284,7 +291,7 @@ textarea { resize: vertical; min-height: 120px; }
-
+

My Work

@@ -711,6 +718,14 @@ textarea { resize: vertical; min-height: 120px; }
+ + @@ -730,6 +745,7 @@ textarea { resize: vertical; min-height: 120px; } + ' in html + assert "createMobileTaskDock({" in html + assert "work: () => qs('#my-work').scrollIntoView" in html + assert "find: () => qs('#find-work').click()" in html + assert "new: () => qs('#new-issue').click()" in html + assert "search: () => qs('#open-palette').click()" in html + assert "drafts: () => qs('[data-work-filter=\"draft\"]').click()" in html + assert "draftCount.textContent = sourceDraftCount.textContent" in html diff --git a/tests/test_service_worker.py b/tests/test_service_worker.py index bcbc9e6..3c8b0ce 100644 --- a/tests/test_service_worker.py +++ b/tests/test_service_worker.py @@ -95,6 +95,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell(): "/dashboard/static/review-sheet.js", "/dashboard/static/work-route.js", "/dashboard/static/context-poller.js", + "/dashboard/static/mobile-task-dock.js", }