From d6212b2812d85fcf8916514ff89325ddd1caf289 Mon Sep 17 00:00:00 2001 From: timmy Date: Thu, 13 Aug 2026 18:53:50 +0000 Subject: [PATCH] feat: keep mobile update decisions within reach (Closes #763) --- frontend/dashboard.css | 7 ++++++ frontend/dashboard.js | 13 ++++++++++++ frontend/index.html | 33 +++++++++++++++++------------ tests/test_mobile_task_dock.py | 17 +++++++++++++++ tests/test_update_triage_session.py | 2 +- 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/frontend/dashboard.css b/frontend/dashboard.css index 023a443..2d194b7 100644 --- a/frontend/dashboard.css +++ b/frontend/dashboard.css @@ -406,6 +406,10 @@ textarea { resize: vertical; min-height: 120px; } .update-sheet-actions #acknowledge-update-next { min-height:44px; width:100%; } .update-sheet-actions #mute-update-next { min-height:44px; width:100%; } .update-sheet-actions a { border:1px solid #60a5fa; border-radius:10px; font-weight:700; } +.update-more-actions { margin-top:14px; } +.update-more-actions > summary { min-height:44px; display:flex; align-items:center; justify-content:center; cursor:pointer; border:1px solid #60a5fa; border-radius:10px; font-weight:700; } +.update-decision-bar { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:6px; margin-top:14px; padding:8px; background:rgba(11,21,38,.98); border-top:1px solid #2a496e; } +.update-decision-bar button, .update-decision-bar summary { min-height:44px; min-width:0; padding-inline:6px; } .update-retry { min-height:44px; width:100%; margin-top:10px; } .issue-sheet { position:fixed; inset:0; z-index:56; display:none; justify-content:flex-end; background:rgba(5,12,21,.72); backdrop-filter:blur(4px); } .issue-sheet.open { display:flex; } @@ -664,6 +668,9 @@ textarea { resize: vertical; min-height: 120px; } .work-filter { flex:1 1 calc(50% - 8px); } .review-sheet-panel { width:100%; border-left:0; padding:14px; } .update-sheet-panel { width:100%; border-left:0; padding:14px; overflow-x:hidden; } + .update-sheet-panel { padding-bottom:calc(82px + env(safe-area-inset-bottom)); } + .update-decision-bar { position:fixed; inset:auto 0 0; z-index:57; margin:0; padding-bottom:calc(8px + env(safe-area-inset-bottom)); } + .update-more-actions > summary { display:none; } .issue-sheet-panel { width:100%; border-left:0; padding:14px; } .issue-blocker-row { grid-template-columns:1fr; } .issue-blocker-remove { width:100%; } diff --git a/frontend/dashboard.js b/frontend/dashboard.js index b2ee2d4..cb70bde 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -916,6 +916,17 @@ jump: qs('#jump-update-new-activity'), }); qs('#jump-update-new-activity').addEventListener('click', () => updateReadPosition.jump()); + qs('#focus-update-reply').addEventListener('click', () => { + const composer = qs('#update-reply'); + composer.scrollIntoView({ behavior:'smooth', block:'center' }); + qs('#update-reply').focus({ preventScroll:true }); + }); + qs('#toggle-update-more').addEventListener('click', event => { + const more = qs('.update-more-actions'); + more.open = !more.open; + event.currentTarget.setAttribute('aria-expanded', String(more.open)); + if (more.open) more.scrollIntoView({ behavior:'smooth', block:'end' }); + }); const notificationReader = createNotificationReader({ load: fetchNotificationDetail, getScope: () => confirmedOwnerLogin, @@ -933,6 +944,8 @@ updateReadPosition.open(String(item.notification_id)); updateMentions.dismiss(); qs('#update-sheet').classList.add('open'); + qs('.update-more-actions').open = false; + qs('#toggle-update-more').setAttribute('aria-expanded', 'false'); qs('#update-sheet-key').textContent = item.key || ''; qs('#update-sheet-title').textContent = item.title || 'Unread update'; qs('#update-comments').textContent = ''; diff --git a/frontend/index.html b/frontend/index.html index 81b5914..7d52d6c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -749,7 +749,6 @@

Unread update

-
Choose an update.
@@ -789,19 +788,21 @@
-
-
- - +
+ More actions +
+
+ + +
+ + + + + Open in Gitea +
Defer
- - - - - - Open in Gitea -
Defer
-
+ +
+ + + + +
diff --git a/tests/test_mobile_task_dock.py b/tests/test_mobile_task_dock.py index abb6315..727dd83 100644 --- a/tests/test_mobile_task_dock.py +++ b/tests/test_mobile_task_dock.py @@ -636,6 +636,23 @@ async def test_dashboard_renders_and_wires_phone_safe_task_dock(): assert "mobileTaskDock.updateAttention(countMyWork(activeMyWork).attention)" in html +@pytest.mark.anyio +async def test_mobile_update_reader_keeps_primary_decisions_within_thumb_reach(): + html = await dashboard() + + assert 'class="update-decision-bar" aria-label="Update decisions"' in html + assert 'id="keep-update-unread" type="button" aria-label="Keep unread and open next update"' in html + assert 'id="mark-update-read-next" type="button" aria-label="Mark read and open next update"' in html + assert 'id="focus-update-reply" type="button">Reply' in html + assert 'class="update-more-actions"' in html + assert "qs('#focus-update-reply').addEventListener('click'" in html + assert "qs('#update-reply').focus({ preventScroll:true })" in html + assert '.update-decision-bar { position:fixed;' in html + assert 'padding-bottom:calc(8px + env(safe-area-inset-bottom))' in html + assert '.update-decision-bar button, .update-decision-bar summary { min-height:44px;' in html + assert 'padding-bottom:calc(82px + env(safe-area-inset-bottom))' in html + + @pytest.mark.anyio async def test_dashboard_renders_phone_safe_today_session_hud_above_task_dock(): html = await dashboard() diff --git a/tests/test_update_triage_session.py b/tests/test_update_triage_session.py index a568d92..81f0642 100644 --- a/tests/test_update_triage_session.py +++ b/tests/test_update_triage_session.py @@ -108,7 +108,7 @@ async def test_dashboard_wires_resumable_updates_triage_mobile_flow(): assert '' in html assert 'id="update-triage-progress"' in html - assert 'id="keep-update-unread" type="button">Keep unread & next' in html + assert 'id="keep-update-unread" type="button" aria-label="Keep unread and open next update">Keep unread' in html assert "openUpdates: openUpdateTriage" in html assert "updateTriage.acceptCompleted()" in html assert "updateTriage.keepUnreadAndNext()" in html -- 2.43.0