feat: continue Today from mobile Work dock (#409)
CI / lint (pull_request) Successful in 45s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-09 15:42:42 +00:00
1 parent 3ba1fe76f0
commit 8b36455c21
15 files changed
+242 -25

No files matched your search

+40 -4
View File
@@ -43,20 +43,30 @@
Array.from(document.querySelectorAll('[data-mobile-task]')).map(button => [button.dataset.mobileTask, button])
);
const mobileTaskOverlays = Array.from(document.querySelectorAll('[role="dialog"], #whiteboard-modal, #markdown-modal'));
function openMobileWork() {
function openMobileWorkFallback() {
const counts = countMyWork(activeMyWork);
const filter = todayMyWork.length ? 'today' : (counts.attention ? 'attention' : 'all');
const filter = counts.attention ? 'attention' : 'all';
qs('[data-work-filter="' + filter + '"]').click();
qs('#my-work').scrollIntoView({block:'start'});
qs('#my-work').focus();
}
const mobileWorkEntry = createMobileWorkEntry({
isTodayActive: () => workSession.checkpointed(),
isTodayResumable: () => workSession.resumable(),
getTodayCount: () => todayMyWork.length,
continueToday: continueTodaySession,
resumeToday: resumeTodaySession,
startToday: startTodaySession,
openFallback: openMobileWorkFallback,
});
const mobileTaskDock = createMobileTaskDock({
nav: qs('#mobile-task-dock'),
buttons: mobileTaskButtons,
workLabel: qs('#mobile-work-label'),
attentionBadge: qs('#mobile-attention-count'),
overlays: mobileTaskOverlays,
actions: {
work: openMobileWork,
work: () => mobileWorkEntry.open(),
find: () => qs('#find-work').click(),
new: () => qs('#new-issue').click(),
search: () => qs('#open-palette').click(),
@@ -678,6 +688,7 @@
qs('#start-work-session').hidden = active;
qs('#resume-today-session').hidden = active || !todayMyWork.length || !workSession.resumable();
qs('#end-today-session').hidden = !active;
mobileTaskDock.updateWork(mobileWorkEntry.mode(), countMyWork(activeMyWork).attention);
}
const workSession = createWorkSession({
@@ -709,6 +720,31 @@
},
});
function selectTodayWork() {
qs('[data-work-filter="today"]').click();
}
function continueTodaySession() {
selectTodayWork();
if (!workSession.reopen() && !workSession.reconcile()) {
qs('#my-work-action-status').textContent = 'Current Today item is no longer available.';
updateWorkSessionActions();
}
}
function resumeTodaySession() {
selectTodayWork();
if (!workSession.resume()) {
qs('#my-work-action-status').textContent = 'Saved Today session is no longer available.';
updateWorkSessionActions();
}
}
function startTodaySession() {
selectTodayWork();
workSession.start();
}
const createAndStart = createCreateAndStart({
todayWork,
todaySync,
@@ -1105,7 +1141,7 @@
const element = qs('[data-work-count="' + filter + '"]');
if (element) element.textContent = count;
});
mobileTaskDock.updateAttention(counts.attention);
mobileTaskDock.updateWork(mobileWorkEntry.mode(), countMyWork(activeMyWork).attention);
const activeQueue = qs('[data-work-filter="' + selectedWorkFilter + '"]');
qs('#active-work-queue').textContent = activeQueue.firstChild.textContent.trim() +
' (' + (counts[selectedWorkFilter] || 0) + ')';
+2 -1
View File
@@ -567,7 +567,7 @@
</dialog>
<nav class="mobile-task-dock" id="mobile-task-dock" aria-label="Primary tasks">
<button class="mobile-task-action" data-mobile-task="work" type="button" aria-current="page">Work <span class="mobile-task-count" id="mobile-attention-count" hidden>0</span></button>
<button class="mobile-task-action" data-mobile-task="work" type="button" aria-current="page"><span id="mobile-work-label">Work</span> <span class="mobile-task-count" id="mobile-attention-count" hidden>0</span></button>
<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>
@@ -612,6 +612,7 @@
<script src="static/task-overlay-history.js"></script>
<script src="static/context-poller.js"></script>
<script src="static/mobile-task-dock.js"></script>
<script src="static/mobile-work-entry.js"></script>
<script src="static/mobile-launch.js"></script>
<script src="static/install-app.js"></script>
<script src="static/mobile-search-viewport.js"></script>
+16 -3
View File
@@ -7,6 +7,7 @@
const overlays = options.overlays || [];
let launcher = null;
let wasHidden = false;
let workMode = 'work';
function select(name) {
Object.entries(buttons).forEach(([key, button]) => {
@@ -34,9 +35,17 @@
refreshVisibility();
}
function updateAttention(count) {
function updateWork(mode, count) {
const labels = {
continue: ['Continue', 'Continue Today'],
resume: ['Resume', 'Resume Today'],
start: ['Start', 'Start Today'],
work: ['Work', 'Work'],
};
workMode = labels[mode] ? mode : 'work';
const total = Math.max(0, Number(count) || 0);
const badge = options.attentionBadge;
if (options.workLabel) options.workLabel.textContent = labels[workMode][0];
if (badge) {
badge.textContent = String(total);
badge.hidden = total === 0;
@@ -44,10 +53,14 @@
if (buttons.work) {
buttons.work.setAttribute(
'aria-label',
total ? 'Work, ' + total + ' items need attention' : 'Work'
labels[workMode][1] + (total ? ', ' + total + ' items need attention' : '')
);
}
}
return {start, refreshVisibility, updateAttention};
function updateAttention(count) {
updateWork(workMode, count);
}
return {start, refreshVisibility, updateAttention, updateWork};
});
+22
View File
@@ -0,0 +1,22 @@
(function (root, factory) {
if (typeof module === 'object' && module.exports) module.exports = factory;
else root.createMobileWorkEntry = factory;
})(typeof self !== 'undefined' ? self : this, function createMobileWorkEntry(options) {
function mode() {
if (options.isTodayActive()) return 'continue';
if (options.getTodayCount() > 0 && options.isTodayResumable()) return 'resume';
if (options.getTodayCount() > 0) return 'start';
return 'work';
}
function open() {
const current = mode();
if (current === 'continue') options.continueToday();
else if (current === 'resume') options.resumeToday();
else if (current === 'start') options.startToday();
else options.openFallback();
return current;
}
return {mode, open};
});
+11
View File
@@ -563,8 +563,19 @@ function createWorkSession({
return {
active: () => running,
checkpointed: () => running && durable,
end: () => finish(),
resumable: () => Boolean(checkpoint?.read()),
reopen() {
if (!running) return false;
const items = queue();
const index = items.findIndex(item => workIdentity(item) === currentIdentity);
if (index < 0) return false;
currentIndex = index;
report(items, index);
onOpen(items[index]);
return true;
},
resume() {
const saved = checkpoint?.read();
if (!saved) return false;
+2 -1
View File
@@ -1,6 +1,6 @@
const BASE = new URL('./', self.location.href).pathname;
importScripts(BASE + 'static/background-issue-sync.js');
const CACHE = 'stackchain-dashboard-shell-v65';
const CACHE = 'stackchain-dashboard-shell-v66';
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;
@@ -46,6 +46,7 @@ const SHELL = [
BASE + 'static/task-overlay-history.js',
BASE + 'static/context-poller.js',
BASE + 'static/mobile-task-dock.js',
BASE + 'static/mobile-work-entry.js',
BASE + 'static/mobile-launch.js',
BASE + 'static/install-app.js',
BASE + 'static/mobile-search-viewport.js',