Surface upcoming deadlines in the mobile task dock #706
|
|
@ -591,6 +591,7 @@ textarea { resize: vertical; min-height: 120px; }
|
|||
.mobile-task-action[hidden] { display:none; }
|
||||
.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; }
|
||||
.mobile-task-deadline { padding:1px 5px; border-radius:999px; color:#fef3c7; background:#92400e; font-size:10px; line-height:14px; }
|
||||
.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) {
|
||||
|
|
@ -668,6 +669,7 @@ textarea { resize: vertical; min-height: 120px; }
|
|||
.mobile-queue-list button > span:first-child { display:grid; gap:2px; }
|
||||
.mobile-queue-list small { color:var(--muted); }
|
||||
.mobile-queue-list [data-mobile-queue-count] { min-width:28px; padding:3px 8px; border-radius:999px; text-align:center; background:#1d426d; }
|
||||
.mobile-queue-list [data-mobile-queue="agenda"][data-deadlines="true"] { border-color:#f59e0b; background:#30240f; box-shadow:inset 3px 0 #f59e0b; }
|
||||
.mobile-queue-list [data-recommended="true"] { border-color:#60a5fa; box-shadow:0 0 0 2px #60a5fa; }
|
||||
.mobile-today-hud { position:fixed; left:8px; right:8px; bottom:calc(56px + env(safe-area-inset-bottom)); z-index:44; display:grid; grid-template-columns:minmax(0,1fr) minmax(112px,auto); grid-template-areas:"summary complete" "progress toggle"; gap:4px 8px; max-width:100%; padding:8px; border:1px solid #31577f; border-radius:12px 12px 0 0; background:rgba(16,38,65,.98); box-shadow:0 -8px 24px rgba(0,0,0,.28); }
|
||||
.mobile-today-summary { grid-area:summary; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:left; font-weight:700; }
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@
|
|||
Array.from(document.querySelectorAll('[data-mobile-queue-count]')).map(element => [element.dataset.mobileQueueCount, element])
|
||||
),
|
||||
queueBadge: qs('#mobile-queue-count'),
|
||||
deadlineBadge: qs('#mobile-deadline-count'),
|
||||
onSelectQueue:name => name === 'recaps' ? qs('#open-today-recaps').click() :
|
||||
name === 'find' ? qs('#find-work').click() : mobileQueueLauncher.open(name),
|
||||
overlays: mobileTaskOverlays,
|
||||
|
|
|
|||
|
|
@ -948,7 +948,7 @@
|
|||
<button class="mobile-task-action" data-mobile-task="find" type="button">Find</button>
|
||||
<button class="mobile-task-action" data-mobile-task="new" type="button">New</button>
|
||||
<button class="mobile-task-action" data-mobile-task="search" type="button">Search</button>
|
||||
<button class="mobile-task-action" data-mobile-task="queues" type="button" aria-label="Queues, 0 items">Queues <span class="mobile-task-count" id="mobile-queue-count" hidden>0</span></button>
|
||||
<button class="mobile-task-action" data-mobile-task="queues" type="button" aria-label="Queues, 0 items"><span>Queues <span class="mobile-task-count" id="mobile-queue-count" hidden>0</span></span><span class="mobile-task-deadline" id="mobile-deadline-count" hidden>0 due</span></button>
|
||||
</nav>
|
||||
|
||||
<div class="footer">Creative AI-imbued UI • stackchain-dashboard</div>
|
||||
|
|
|
|||
|
|
@ -108,7 +108,18 @@
|
|||
options.queueBadge.textContent = String(total);
|
||||
options.queueBadge.hidden = total === 0;
|
||||
}
|
||||
buttons.queues?.setAttribute('aria-label', 'Queues, ' + total + ' items');
|
||||
if (options.deadlineBadge) {
|
||||
options.deadlineBadge.textContent = normalized.agenda + ' due';
|
||||
options.deadlineBadge.hidden = normalized.agenda === 0;
|
||||
}
|
||||
if (options.queueRows?.agenda) {
|
||||
if (normalized.agenda > 0) options.queueRows.agenda.setAttribute('data-deadlines', 'true');
|
||||
else options.queueRows.agenda.removeAttribute('data-deadlines');
|
||||
}
|
||||
const deadlineLabel = normalized.agenda > 0
|
||||
? ', ' + normalized.agenda + ' upcoming deadlines'
|
||||
: '';
|
||||
buttons.queues?.setAttribute('aria-label', 'Queues, ' + total + ' items' + deadlineLabel);
|
||||
}
|
||||
|
||||
return {start, select, refreshVisibility, updateAttention, updateQueues, updateWork};
|
||||
|
|
|
|||
|
|
@ -293,18 +293,19 @@ sheet.showModal = function () {{ this.open = true; }};
|
|||
sheet.close = function () {{ this.open = false; this.listeners.close?.(); }};
|
||||
const close = new FakeElement();
|
||||
const badge = new FakeElement();
|
||||
const rows = Object.fromEntries(['today','attention','update','later','draft','recaps'].map(name => [name, new FakeElement()]));
|
||||
const deadline = new FakeElement();
|
||||
const rows = Object.fromEntries(['today','agenda','attention','update','later','draft','recaps'].map(name => [name, new FakeElement()]));
|
||||
const counts = Object.fromEntries(Object.keys(rows).map(name => [name, new FakeElement()]));
|
||||
const selected = [];
|
||||
const utilities = [];
|
||||
const dock = createDock({{
|
||||
nav, buttons:{{queues}}, actions:{{queues:()=>{{}}}}, overlays:[sheet],
|
||||
queueSheet:sheet, queueClose:close, queueRows:rows, queueCounts:counts, queueBadge:badge,
|
||||
queueSheet:sheet, queueClose:close, queueRows:rows, queueCounts:counts, queueBadge:badge, deadlineBadge:deadline,
|
||||
onSelectQueue:(name,row)=>name === 'recaps' ? utilities.push(row === rows.recaps ? 'recaps:trigger' : 'recaps:missing-trigger') : selected.push(name),
|
||||
observe() {{}},
|
||||
}});
|
||||
dock.start();
|
||||
dock.updateQueues({{today:2, attention:1, update:5, later:3, draft:4}});
|
||||
dock.updateQueues({{today:2, agenda:5, attention:1, update:5, later:3, draft:4}});
|
||||
queues.click();
|
||||
const opened = sheet.open;
|
||||
rows.update.click();
|
||||
|
|
@ -312,9 +313,18 @@ const closedAfterSelect = !sheet.open;
|
|||
queues.click(); rows.recaps.click();
|
||||
const closedAfterUtility = !sheet.open;
|
||||
queues.click(); close.click();
|
||||
const populated = {{
|
||||
deadline:deadline.textContent, deadlineHidden:deadline.hidden,
|
||||
agendaDue:rows.agenda.attributes['data-deadlines'] || null,
|
||||
badgeLabel:queues.attributes['aria-label'],
|
||||
}};
|
||||
dock.updateQueues({{today:0, agenda:0, attention:0, update:0, later:0, draft:0}});
|
||||
process.stdout.write(JSON.stringify({{
|
||||
opened, closedAfterSelect, closedAfterUtility, selected, utilities,
|
||||
badge:badge.textContent, badgeLabel:queues.attributes['aria-label'],
|
||||
badge:badge.textContent, populated,
|
||||
clearedDeadlineHidden:deadline.hidden,
|
||||
clearedAgendaDue:rows.agenda.attributes['data-deadlines'] || null,
|
||||
clearedBadgeLabel:queues.attributes['aria-label'],
|
||||
counts:Object.fromEntries(Object.entries(counts).map(([name, node]) => [name, node.textContent])),
|
||||
updateLabel:rows.update.attributes['aria-label'],
|
||||
queueFocuses:queues.focuses,
|
||||
|
|
@ -331,10 +341,13 @@ process.stdout.write(JSON.stringify({{
|
|||
"selected": ["update"],
|
||||
"utilities": ["recaps:trigger"],
|
||||
# Updates are a drill-down within Attention and must not inflate the aggregate badge.
|
||||
"badge": "10",
|
||||
"badgeLabel": "Queues, 10 items",
|
||||
"counts": {"today": "2", "attention": "1", "update": "5", "later": "3", "draft": "4", "recaps": "0"},
|
||||
"updateLabel": "Updates, 5 unread conversations",
|
||||
"badge": "0",
|
||||
"populated": {"badgeLabel": "Queues, 10 items, 5 upcoming deadlines", "deadline": "5 due", "deadlineHidden": False, "agendaDue": "true"},
|
||||
"clearedDeadlineHidden": True,
|
||||
"clearedAgendaDue": None,
|
||||
"clearedBadgeLabel": "Queues, 0 items",
|
||||
"counts": {"today": "0", "agenda": "0", "attention": "0", "update": "0", "later": "0", "draft": "0", "recaps": "0"},
|
||||
"updateLabel": "Updates, 0 unread conversations",
|
||||
"queueFocuses": 3,
|
||||
"columnState": None,
|
||||
}
|
||||
|
|
@ -436,6 +449,7 @@ async def test_dashboard_renders_and_wires_mobile_queue_switcher():
|
|||
assert 'data-mobile-queue="recaps"' in html
|
||||
assert '<strong>Recaps</strong><small>History</small>' in html
|
||||
assert 'id="mobile-queue-count"' in html
|
||||
assert 'id="mobile-deadline-count"' in html
|
||||
assert 'mobileTaskDock.updateQueues(counts)' in html
|
||||
assert '<script src="static/mobile-queue-launcher.js"></script>' in html
|
||||
assert "const mobileQueueLauncher = createMobileQueueLauncher({" in html
|
||||
|
|
@ -444,6 +458,8 @@ async def test_dashboard_renders_and_wires_mobile_queue_switcher():
|
|||
assert '.mobile-queue-sheet' in html
|
||||
assert '.my-work-actions { display:none;' in html
|
||||
assert 'padding-bottom:calc(16px + env(safe-area-inset-bottom))' in html
|
||||
assert '.mobile-task-deadline' in html
|
||||
assert '[data-deadlines="true"]' in html
|
||||
assert '.mobile-task-dock[data-attention="true"]' not in html
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user