feat: review Following during Prepare Today (Closes #1301)
CI / lint (pull_request) Successful in 3m34s
CI / build-release (pull_request) Successful in 8s
CI / browser-journey (pull_request) Successful in 5m34s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-23 10:09:12 +00:00
1 parent 875ec45928
commit 12c0e18dfe
19 files changed
+189 -42

No files matched your search

+17 -1
View File
@@ -108,6 +108,19 @@
const followingQueue = attachFollowing(item => {
searchPreviewReturnKind = 'following';
return searchPreview.open(item);
}, {
onCount:(count, items) => {
mobileQueueCounts.following = count;
mobileQueueCounts.followingUnavailable = false;
mobilePreparationItems.following = items.filter(item => item.has_unseen_change === true);
mobileStartDay.render();
},
onStatus:status => {
if (status === 'loading') return;
mobileQueueCounts.followingUnavailable = status === 'error';
mobileStartDay.render();
},
onReviewComplete:() => mobileStartDay.completePhase('following'),
});
const mobileDeliveryRecovery = createMobileDeliveryRecovery({
getItems: () => draftInbox.partition(lastDrafts).deliveries,
@@ -145,6 +158,7 @@
openFindWork: () => qs('#find-work').click(),
});
function openMobileStartDay() {
followingQueue.load().catch(() => {});
const state = mobileStartDay.state();
mobileStartDay.render();
qs('#mobile-queue-heading').textContent = state.active ? 'Resume Prepare Today' : 'Prepare Today';
@@ -181,7 +195,9 @@
openQueue: name => {
const sheet = qs('#mobile-queue-sheet');
if (sheet.open) sheet.close();
return name === 'find' ? qs('#find-work').click() : mobileQueueLauncher.open(name);
return name === 'find' ? qs('#find-work').click() :
name === 'following' ? (mobileQueueCounts.followingUnavailable ? followingQueue.open() :
followingQueue.review()) : mobileQueueLauncher.open(name);
},
onHandoff: current => {
qs('#mobile-queue-heading').textContent = 'Prepare Today · ' + current.label;
+15 -2
View File
@@ -80,18 +80,26 @@
}
function finishReview() {
const completed = Boolean(review?.active &&
!snapshot.items.some(item => item.has_unseen_change === true));
if (review) review.active = false;
if (completed && !review.completed) {
review.completed = true;
options.onReviewComplete?.();
}
publish('ready');
return completed;
}
return {
load, open, startReview, previewLoaded:acknowledge, finishReview,
session:() => review?.active ? {items:[...review.items], more:false} : null,
items:() => snapshot.items.map(item => ({...item})),
count:() => snapshot.items.length,
};
}
function attachFollowing(onOpen) {
function attachFollowing(onOpen, hooks = {}) {
const document = globalThis.document;
const query = selector => document.querySelector(selector);
const escapeHtml = value => String(value ?? '').replace(/[&<>"']/g, character =>
@@ -105,6 +113,7 @@
};
let feature;
function render(state) {
hooks.onStatus?.(state.status);
const list = query('#following-list');
const status = query('#following-status');
const reviewButton = query('#review-following');
@@ -138,8 +147,10 @@
value.textContent = count;
value.closest('button').setAttribute('aria-label', 'Following, ' + count +
(count === 1 ? ' unseen change' : ' unseen changes'));
hooks.onCount?.(count, feature.items());
},
onOpen,
onReviewComplete:hooks.onReviewComplete,
onAcknowledge:item => {
const [owner, repo] = item.repository.split('/');
return fetchJson('api/v1/following/' + encodeURIComponent(owner) + '/' +
@@ -159,6 +170,7 @@
query('#retry-following').addEventListener('click', () => feature.load().catch(() => {}));
return {
load:feature.load,
review:feature.startReview,
open() {
const sheet = query('#following-sheet');
if (!sheet.open) sheet.showModal();
@@ -168,7 +180,8 @@
session:feature.session,
previewLoaded:feature.previewLoaded,
returnToFollowing() {
feature.finishReview();
const completed = feature.finishReview();
if (completed) return 'completed-following';
const sheet = query('#following-sheet');
if (!sheet.open) sheet.showModal();
globalThis.requestAnimationFrame?.(() => {
+1 -1
View File
@@ -2003,7 +2003,7 @@
<div>
<h3 id="mobile-start-day-heading">Prepare Today</h3>
<p id="mobile-start-day-summary" class="small" aria-live="polite">Reviewing urgent queues…</p>
<p id="mobile-start-day-phases" class="small muted">Checking Agenda, Attention, Updates, and Filed</p>
<p id="mobile-start-day-phases" class="small muted">Checking Agenda, Attention, Updates, Filed, and Following</p>
</div>
<button class="mobile-start-day-action" id="mobile-start-day-action" type="button">Prepare Today</button>
<button class="mobile-start-day-finish" id="finish-mobile-start-day" type="button" hidden>Finish for now</button>
+12 -3
View File
@@ -10,6 +10,7 @@
['attention', 'Attention'],
['update', 'Updates'],
['filed', 'Filed'],
['following', 'Following'],
];
const anonymousItems = new WeakMap();
let anonymousItemSequence = 0;
@@ -102,19 +103,27 @@
function briefing() {
const counts = options.getCounts ? options.getCounts() : {};
const phases = reviewPhases(counts);
let phases = reviewPhases(counts);
const followingUnavailable = counts.followingUnavailable === true;
if (followingUnavailable) {
phases = phases.filter(phase => phase.name !== 'following');
phases.push({name:'following', label:'Following unavailable', count:count(counts.following)});
}
const total = phases.reduce((sum, phase) => sum + phase.count, 0);
const today = count(counts.today);
const delivery = phases.find(phase => phase.name === 'delivery')?.count || 0;
const other = total - delivery;
const next = phases.length ? phases[0].name : (today ? 'today' : 'find');
const nextLabel = next === 'delivery' ? 'Review Delivery' :
(phases.length ? 'Review ' + phases[0].label : (today ? 'Continue Today' : 'Find Work'));
(next === 'following' && followingUnavailable ? 'Retry Following' :
(phases.length ? 'Review ' + phases[0].label : (today ? 'Continue Today' : 'Find Work')));
const followingRetry = next === 'following' && followingUnavailable;
return {
total,
next,
label: nextLabel,
summary: delivery ? delivery + (delivery === 1 ? ' delivery needs' : ' deliveries need') +
summary: followingRetry ? 'Following needs retry before Today · ' + today + ' planned' :
delivery ? delivery + (delivery === 1 ? ' delivery needs' : ' deliveries need') +
' action before Today' + (other ? ' · ' + other + ' other ' + (other === 1 ? 'item' : 'items') : '') +
' · ' + today + ' planned' :
total ? total + ' items before Today · ' + today + ' planned' :
+1 -1
View File
@@ -1,7 +1,7 @@
const BASE = new URL('./', self.location.href).pathname;
importScripts(BASE + 'static/private-data-registry.js');
importScripts(BASE + 'static/background-issue-sync.js');
const CACHE = 'stackchain-dashboard-shell-v131';
const CACHE = 'stackchain-dashboard-shell-v132';
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;