Merge pull request 'Keep mobile Update decisions within thumb reach' (#764) from timmy/763-mobile-update-decision-bar into main
All checks were successful
CI / lint (push) Successful in 1m31s
CI / build-release (push) Successful in 5s
CI / release-candidate (push) Successful in 7s

This commit is contained in:
timmy 2026-08-13 18:56:13 +00:00
commit e21cc58cca
5 changed files with 58 additions and 14 deletions

View File

@ -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%; }

View File

@ -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 = '';

View File

@ -749,7 +749,6 @@
<div class="small" id="update-sheet-key"></div>
<h3 id="update-sheet-title">Unread update</h3>
</div>
<button id="keep-update-unread" type="button">Keep unread &amp; next</button>
</div>
<div id="update-triage-progress" class="update-triage-progress small" aria-live="polite" hidden></div>
<div id="update-sheet-status" class="small" aria-live="polite">Choose an update.</div>
@ -789,19 +788,21 @@
</div>
<div id="update-reply-status" class="small" aria-live="assertive"></div>
</section>
<div class="update-sheet-actions">
<div class="update-ownership-actions">
<button id="update-ownership-action" type="button" hidden aria-describedby="update-sheet-status">Take ownership</button>
<button id="update-ownership-start" type="button" hidden aria-describedby="update-sheet-status">Take ownership &amp; start</button>
<details class="update-more-actions">
<summary>More actions</summary>
<div class="update-sheet-actions">
<div class="update-ownership-actions">
<button id="update-ownership-action" type="button" hidden aria-describedby="update-sheet-status">Take ownership</button>
<button id="update-ownership-start" type="button" hidden aria-describedby="update-sheet-status">Take ownership &amp; start</button>
</div>
<button class="share-work-route" type="button">Share</button>
<button id="acknowledge-update-next" type="button" hidden aria-label="Acknowledge and open next update">👍 Acknowledge &amp; next</button>
<button id="mute-update-next" type="button" hidden>Mute future updates &amp; next</button>
<button id="create-update-follow-up" type="button" hidden>Create follow-up</button>
<a id="open-update-gitea" href="#" target="_blank" rel="noopener noreferrer">Open in Gitea</a>
<details class="detail-defer"><summary>Defer</summary><div class="detail-defer-options"><button type="button" data-detail-defer-preset="today" disabled data-planning-disabled>Later today</button><button type="button" data-detail-defer-preset="tomorrow" disabled data-planning-disabled>Tomorrow</button><button type="button" data-detail-defer-custom disabled data-planning-disabled>Choose date &amp; time</button><button type="button" data-detail-defer-cancel>Cancel</button></div></details>
</div>
<button class="share-work-route" type="button">Share</button>
<button id="acknowledge-update-next" type="button" hidden aria-label="Acknowledge and open next update">👍 Acknowledge &amp; next</button>
<button id="mute-update-next" type="button" hidden>Mute future updates &amp; next</button>
<button id="create-update-follow-up" type="button" hidden>Create follow-up</button>
<button id="mark-update-read-next" type="button">Mark read &amp; next</button>
<a id="open-update-gitea" href="#" target="_blank" rel="noopener noreferrer">Open in Gitea</a>
<details class="detail-defer"><summary>Defer</summary><div class="detail-defer-options"><button type="button" data-detail-defer-preset="today" disabled data-planning-disabled>Later today</button><button type="button" data-detail-defer-preset="tomorrow" disabled data-planning-disabled>Tomorrow</button><button type="button" data-detail-defer-custom disabled data-planning-disabled>Choose date &amp; time</button><button type="button" data-detail-defer-cancel>Cancel</button></div></details>
</div>
</details>
<nav class="work-session-nav" aria-label="Work session" hidden>
<span class="small" aria-live="polite" data-work-session-progress></span>
<button type="button" data-work-session-previous>Previous</button>
@ -809,6 +810,12 @@
<button type="button" data-work-session-complete="update" hidden>Done for Today &amp; next</button>
<button type="button" data-work-session-next>Next work item</button>
</nav>
<div class="update-decision-bar" aria-label="Update decisions">
<button id="keep-update-unread" type="button" aria-label="Keep unread and open next update">Keep unread</button>
<button id="mark-update-read-next" type="button" aria-label="Mark read and open next update">Mark read</button>
<button id="focus-update-reply" type="button">Reply</button>
<button id="toggle-update-more" type="button" aria-expanded="false">More</button>
</div>
</section>
</div>

View File

@ -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</button>' 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()

View File

@ -108,7 +108,7 @@ async def test_dashboard_wires_resumable_updates_triage_mobile_flow():
assert '<script src="static/update-triage-session.js"></script>' in html
assert 'id="update-triage-progress"' in html
assert 'id="keep-update-unread" type="button">Keep unread &amp; next</button>' in html
assert 'id="keep-update-unread" type="button" aria-label="Keep unread and open next update">Keep unread</button>' in html
assert "openUpdates: openUpdateTriage" in html
assert "updateTriage.acceptCompleted()" in html
assert "updateTriage.keepUnreadAndNext()" in html