Compare commits

..

No commits in common. "04ae7480cf712686f7d336ae362237d51e5fcc19" and "a2076cf9a9e5f4fb439ba35da684bf30d3732b8d" have entirely different histories.

16 changed files with 28 additions and 228 deletions

View File

@ -87,11 +87,9 @@ sync service leaves ad-hoc Search usable. Set `STACKCHAIN_SAVED_SEARCH_DB` to ov
`.stackchain-state/saved-searches.sqlite3` path. `.stackchain-state/saved-searches.sqlite3` path.
Completed delegated issues remain in the mobile **Filed** queue until their latest outcome is acknowledged. Completed delegated issues remain in the mobile **Filed** queue until their latest outcome is acknowledged.
The mobile queue sheet begins with **Prepare Today**, a live briefing that totals Agenda, Attention, Updates, and The mobile queue sheet begins with **Prepare Today**, a live briefing that totals Agenda, Attention, Updates, and
Filed work and opens the highest-priority non-empty review queue. Starting it saves a confirmed-account, local-day Filed work, opens the highest-priority non-empty review queue, and refreshes the next action as queues clear. Once
checkpoint: finishing Agenda, Updates, or the final Filed review returns to a focused handoff using fresh queue urgent review is clear it continues the existing Today plan, or opens Find Work when Today is empty; viewing the
counts, while reopening the queue sheet resumes the next live phase. **Finish for now** removes only that local briefing itself never changes Gitea state.
checkpoint. Once urgent review is clear the pass continues the existing Today plan, or opens Find Work when Today
is empty; the briefing and checkpoint never change Gitea state.
Filed separates actionable **Needs review** from a browsable **Reviewed** history, so acknowledgement clears the Filed separates actionable **Needs review** from a browsable **Reviewed** history, so acknowledgement clears the
queue without erasing the delegated-work record. Reviewed cards reopen the existing read-only issue detail and queue without erasing the delegated-work record. Reviewed cards reopen the existing read-only issue detail and
conversation, while the Filed badge continues to count actionable outcomes only. A later Gitea update moves that conversation, while the Filed badge continues to count actionable outcomes only. A later Gitea update moves that

View File

@ -854,7 +854,6 @@ textarea { resize: vertical; min-height: 120px; }
.mobile-start-day h3, .mobile-start-day p { margin:0; } .mobile-start-day h3, .mobile-start-day p { margin:0; }
.mobile-start-day p + p { margin-top:4px; } .mobile-start-day p + p { margin-top:4px; }
.mobile-start-day-action { width:100%; min-height:48px; text-align:center; } .mobile-start-day-action { width:100%; min-height:48px; text-align:center; }
.mobile-start-day-finish { min-height:44px; width:100%; background:transparent; }
.mobile-queue-list { display:grid; gap:8px; margin-top:12px; } .mobile-queue-list { display:grid; gap:8px; margin-top:12px; }
.mobile-queue-list button { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:56px; width:100%; padding:10px 14px; text-align:left; } .mobile-queue-list button { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:56px; width:100%; padding:10px 14px; text-align:left; }
.mobile-queue-list button > span:first-child { display:grid; gap:2px; } .mobile-queue-list button > span:first-child { display:grid; gap:2px; }

View File

@ -97,29 +97,15 @@
}); });
const mobileStartDay = createMobileStartDay({ const mobileStartDay = createMobileStartDay({
getCounts: () => mobileQueueCounts, getCounts: () => mobileQueueCounts,
getLogin: () => confirmedOwnerLogin, openQueue: name => name === 'find' ? qs('#find-work').click() : mobileQueueLauncher.open(name),
openQueue: name => {
const sheet = qs('#mobile-queue-sheet');
if (sheet.open) sheet.close();
return name === 'find' ? qs('#find-work').click() : mobileQueueLauncher.open(name);
},
onHandoff: current => {
qs('#mobile-queue-heading').textContent = 'Prepare Today · ' + current.label;
const sheet = qs('#mobile-queue-sheet');
if (!sheet.open) sheet.showModal();
qs('#mobile-start-day-action').focus();
},
elements: { elements: {
summary: qs('#mobile-start-day-summary'), summary: qs('#mobile-start-day-summary'),
phases: qs('#mobile-start-day-phases'), phases: qs('#mobile-start-day-phases'),
action: qs('#mobile-start-day-action'), action: qs('#mobile-start-day-action'),
finish: qs('#finish-mobile-start-day'),
}, },
}); });
mobileStartDay.start(); mobileStartDay.start();
qs('#finish-mobile-start-day').addEventListener('click', () => mobileStartDay.finish()); function showMobileQueueCompletion(completedName, cleared = true) {
function showMobileQueueCompletion(completedName, cleared = true, phase = '') {
if (cleared && phase && mobileStartDay.completePhase(phase)) return;
mobileStartDay.render(); mobileStartDay.render();
const next = mobileQueueLauncher.recommend(); const next = mobileQueueLauncher.recommend();
qs('#mobile-queue-heading').textContent = completedName + (cleared ? ' cleared' : ''); qs('#mobile-queue-heading').textContent = completedName + (cleared ? ' cleared' : '');
@ -139,7 +125,6 @@
' marked read · ' + outcome.kept + ' kept unread'; ' marked read · ' + outcome.kept + ' kept unread';
review.hidden = !outcome.kept; review.hidden = !outcome.kept;
review.textContent = 'Review ' + outcome.kept + ' kept unread'; review.textContent = 'Review ' + outcome.kept + ' kept unread';
if (!outcome.kept && mobileStartDay.completePhase('update')) return;
showMobileQueueCompletion(outcome.kept ? 'Updates reviewed' : 'Updates', !outcome.kept); showMobileQueueCompletion(outcome.kept ? 'Updates reviewed' : 'Updates', !outcome.kept);
if (outcome.kept) review.focus(); if (outcome.kept) review.focus();
} }
@ -1194,7 +1179,7 @@
qs('#retry-update-load').hidden = !message.startsWith('Could not load update.'); qs('#retry-update-load').hidden = !message.startsWith('Could not load update.');
if (message === 'Inbox cleared.') { if (message === 'Inbox cleared.') {
qs('#my-work-action-status').textContent = message; qs('#my-work-action-status').textContent = message;
if (!mobileStartDay.completePhase('update')) showMobileQueueCompletion('Updates'); showMobileQueueCompletion('Updates');
} }
}, },
onClose: () => closeUpdateSheet(false), onClose: () => closeUpdateSheet(false),
@ -1516,7 +1501,6 @@
updateWorkSessionActions(); updateWorkSessionActions();
if (selectedWorkFilter === 'agenda') { if (selectedWorkFilter === 'agenda') {
closeOpenWorkSheets(); closeOpenWorkSheets();
if (mobileStartDay.completePhase('agenda')) return;
window.location.hash = '#/my-work/agenda'; window.location.hash = '#/my-work/agenda';
qs('#my-work-action-status').textContent = 'Agenda complete.'; qs('#my-work-action-status').textContent = 'Agenda complete.';
return; return;
@ -3580,7 +3564,6 @@
const trigger = qs('#my-work-list [data-' + target.kind + '-index="' + index + '"]'); const trigger = qs('#my-work-list [data-' + target.kind + '-index="' + index + '"]');
openRoutedWork(target.kind === 'update' ? { ...target.item, kind:'update' } : target.item, trigger); openRoutedWork(target.kind === 'update' ? { ...target.item, kind:'update' } : target.item, trigger);
} else { } else {
if (mobileStartDay.completePhase('filed')) return;
window.location.hash = '#/my-work/filed'; window.location.hash = '#/my-work/filed';
qs('#my-work').focus(); qs('#my-work').focus();
} }

View File

@ -1230,7 +1230,6 @@
<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, and Filed</p>
</div> </div>
<button class="mobile-start-day-action" id="mobile-start-day-action" type="button">Prepare Today</button> <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>
</section> </section>
<div class="mobile-queue-list"> <div class="mobile-queue-list">
<button data-mobile-queue="today" type="button"><span><strong>Today</strong><small>Planned work</small></span><span data-mobile-queue-count="today">0</span></button> <button data-mobile-queue="today" type="button"><span><strong>Today</strong><small>Planned work</small></span><span data-mobile-queue-count="today">0</span></button>

View File

@ -2,8 +2,6 @@
if (typeof module === 'object' && module.exports) module.exports = factory; if (typeof module === 'object' && module.exports) module.exports = factory;
else root.createMobileStartDay = factory; else root.createMobileStartDay = factory;
})(typeof self !== 'undefined' ? self : this, function createMobileStartDay(options) { })(typeof self !== 'undefined' ? self : this, function createMobileStartDay(options) {
const checkpointKey = 'stackchain.mobile-start-day.v1';
const storage = options.storage || (typeof localStorage !== 'undefined' ? localStorage : null);
const reviewOrder = [ const reviewOrder = [
['agenda', 'Agenda'], ['agenda', 'Agenda'],
['attention', 'Attention'], ['attention', 'Attention'],
@ -15,57 +13,6 @@
return Math.max(0, Number(value) || 0); return Math.max(0, Number(value) || 0);
} }
function localDay() {
const now = new Date();
const pad = value => String(value).padStart(2, '0');
return now.getFullYear() + '-' + pad(now.getMonth() + 1) + '-' + pad(now.getDate());
}
function identity() {
try {
return {
login: String(options.getLogin ? options.getLogin() : '').trim(),
day: String(options.getDay ? options.getDay() : localDay()),
};
} catch (_) {
return {login:'', day:''};
}
}
function checkpoint() {
if (!storage) return null;
const current = identity();
if (!current.login || !current.day) return null;
try {
const saved = JSON.parse(storage.getItem(checkpointKey) || 'null');
return saved?.login === current.login && saved?.day === current.day ? saved : null;
} catch (_) {
return null;
}
}
function saveCheckpoint() {
if (!storage) return false;
const current = identity();
if (!current.login || !current.day) return false;
try {
storage.setItem(checkpointKey, JSON.stringify(current));
return true;
} catch (_) {
return false;
}
}
function clearCheckpoint() {
if (!storage || !checkpoint()) return false;
try {
storage.removeItem(checkpointKey);
return true;
} catch (_) {
return false;
}
}
function briefing() { function briefing() {
const counts = options.getCounts ? options.getCounts() : {}; const counts = options.getCounts ? options.getCounts() : {};
const phases = reviewOrder const phases = reviewOrder
@ -87,30 +34,10 @@
function startNext() { function startNext() {
const next = briefing().next; const next = briefing().next;
if (reviewOrder.some(([name]) => name === next)) saveCheckpoint();
else clearCheckpoint();
options.openQueue(next); options.openQueue(next);
return next; return next;
} }
function state() {
const current = briefing();
return {active:Boolean(checkpoint()), next:current.next, label:current.label};
}
function completePhase() {
if (!checkpoint()) return false;
const current = render();
if (options.onHandoff) options.onHandoff(current);
return true;
}
function finish() {
const cleared = clearCheckpoint();
render();
return cleared;
}
function render() { function render() {
const current = briefing(); const current = briefing();
if (!options.elements) return current; if (!options.elements) return current;
@ -118,8 +45,7 @@
options.elements.phases.textContent = current.phases.length ? options.elements.phases.textContent = current.phases.length ?
current.phases.map(phase => phase.label + ' ' + phase.count).join(' · ') : current.phases.map(phase => phase.label + ' ' + phase.count).join(' · ') :
'All urgent queues reviewed'; 'All urgent queues reviewed';
options.elements.action.textContent = checkpoint() ? 'Resume preparation · ' + current.label : current.label; options.elements.action.textContent = current.label;
if (options.elements.finish) options.elements.finish.hidden = !checkpoint();
return current; return current;
} }
@ -128,5 +54,5 @@
if (options.elements) options.elements.action.addEventListener('click', startNext); if (options.elements) options.elements.action.addEventListener('click', startNext);
} }
return {briefing, completePhase, finish, render, start, startNext, state}; return {briefing, render, start, startNext};
}); });

View File

@ -1,7 +1,7 @@
const BASE = new URL('./', self.location.href).pathname; const BASE = new URL('./', self.location.href).pathname;
importScripts(BASE + 'static/private-data-registry.js'); importScripts(BASE + 'static/private-data-registry.js');
importScripts(BASE + 'static/background-issue-sync.js'); importScripts(BASE + 'static/background-issue-sync.js');
const CACHE = 'stackchain-dashboard-shell-v104'; const CACHE = 'stackchain-dashboard-shell-v103';
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href; const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]); const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000; const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;

View File

@ -303,4 +303,4 @@ async def test_unread_update_offers_reply_mark_read_and_next_independent_of_toda
assert '.update-reply-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr));' in html assert '.update-reply-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr));' in html
assert '.update-reply-actions button { min-height:44px;' in html assert '.update-reply-actions button { min-height:44px;' in html
worker = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text() worker = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
assert "stackchain-dashboard-shell-v104" in worker assert "stackchain-dashboard-shell-v103" in worker

View File

@ -347,5 +347,5 @@ async def test_dashboard_syncs_every_later_change_and_exposes_account_status():
def test_later_sync_ships_atomically_in_the_offline_shell(): def test_later_sync_ships_atomically_in_the_offline_shell():
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text() source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/later-sync.js'" in source assert "BASE + 'static/later-sync.js'" in source

View File

@ -137,4 +137,4 @@ def test_markdown_work_bodies_are_mobile_safe_block_containers():
assert ".markdown-content { min-width:0; max-width:100%; overflow-wrap:anywhere;" in css assert ".markdown-content { min-width:0; max-width:100%; overflow-wrap:anywhere;" in css
assert ".markdown-content pre { max-width:100%; overflow-x:auto;" in css assert ".markdown-content pre { max-width:100%; overflow-x:auto;" in css
assert ".markdown-content a { min-height:44px;" in css assert ".markdown-content a { min-height:44px;" in css
assert "stackchain-dashboard-shell-v104" in worker assert "stackchain-dashboard-shell-v103" in worker

View File

@ -45,7 +45,7 @@ def test_offline_shell_contains_every_local_dashboard_runtime_asset():
shell_assets = set(re.findall(r"BASE \+ '([^']+)'", worker.split("async function sessionCsrf", 1)[0])) shell_assets = set(re.findall(r"BASE \+ '([^']+)'", worker.split("async function sessionCsrf", 1)[0]))
assert local_assets <= shell_assets, f"Offline shell is missing: {sorted(local_assets - shell_assets)}" assert local_assets <= shell_assets, f"Offline shell is missing: {sorted(local_assets - shell_assets)}"
assert "stackchain-dashboard-shell-v104" in worker assert "stackchain-dashboard-shell-v103" in worker
def test_all_conversation_composers_offer_accessible_mobile_mentions(): def test_all_conversation_composers_offer_accessible_mobile_mentions():

View File

@ -214,7 +214,7 @@ def test_mobile_dashboard_mounts_phone_safe_device_setup_flow():
assert "promptStorage:localStorage" in dashboard assert "promptStorage:localStorage" in dashboard
assert "pushControllerReady.then(ensureDeviceSetup)" in dashboard assert "pushControllerReady.then(ensureDeviceSetup)" in dashboard
assert "BASE + 'static/mobile-device-setup.js'" in worker assert "BASE + 'static/mobile-device-setup.js'" in worker
assert "stackchain-dashboard-shell-v104" in worker assert "stackchain-dashboard-shell-v103" in worker
assert ".device-setup-panel" in css assert ".device-setup-panel" in css
assert ".device-readiness-card" in css assert ".device-readiness-card" in css
assert "overflow-x:hidden" in css assert "overflow-x:hidden" in css

View File

@ -111,100 +111,6 @@ process.stdout.write(JSON.stringify({{first, ready, opened}}));
} }
def test_prepare_today_pass_persists_per_account_and_hands_off_using_live_counts():
script = f"""
const createStartDay = require({json.dumps(str(START_DAY))});
const saved = new Map();
const storage = {{
getItem: key => saved.has(key) ? saved.get(key) : null,
setItem: (key, value) => saved.set(key, value),
removeItem: key => saved.delete(key),
}};
let login = 'timmy';
let day = '2026-08-15';
let counts = {{agenda:2, attention:1, update:1, filed:1, today:3}};
const opened = [];
const handoffs = [];
const controller = createStartDay({{
storage,
getLogin: () => login,
getDay: () => day,
getCounts: () => counts,
openQueue: name => opened.push(name),
onHandoff: state => handoffs.push({{next:state.next, label:state.label}}),
}});
const started = controller.startNext();
counts = {{agenda:0, attention:0, update:1, filed:1, today:3}};
const handed = controller.completePhase('agenda');
const resumed = createStartDay({{
storage,
getLogin: () => login,
getDay: () => day,
getCounts: () => counts,
openQueue: name => opened.push(name),
}}).state();
login = 'alexander';
const isolated = createStartDay({{
storage,
getLogin: () => login,
getDay: () => day,
getCounts: () => counts,
openQueue: name => opened.push(name),
}}).state();
login = 'timmy';
day = '2026-08-16';
const expired = createStartDay({{
storage,
getLogin: () => login,
getDay: () => day,
getCounts: () => counts,
openQueue: name => opened.push(name),
}}).state();
day = '2026-08-15';
const finished = controller.finish();
const cleared = controller.state();
process.stdout.write(JSON.stringify({{started, handed, resumed, isolated, expired, finished, cleared, opened, handoffs}}));
"""
assert run_node(script) == {
"started": "agenda",
"handed": True,
"resumed": {"active": True, "next": "update", "label": "Review Updates"},
"isolated": {"active": False, "next": "update", "label": "Review Updates"},
"expired": {"active": False, "next": "update", "label": "Review Updates"},
"finished": True,
"cleared": {"active": False, "next": "update", "label": "Review Updates"},
"opened": ["agenda"],
"handoffs": [{"next": "update", "label": "Review Updates"}],
}
def test_prepare_today_checkpoint_uses_the_device_local_day():
script = f"""
process.env.TZ = 'Pacific/Honolulu';
const RealDate = Date;
global.Date = class extends RealDate {{
constructor(...args) {{ super(...(args.length ? args : ['2026-08-16T01:00:00Z'])); }}
}};
const createStartDay = require({json.dumps(str(START_DAY))});
const saved = new Map();
const storage = {{
getItem: key => saved.has(key) ? saved.get(key) : null,
setItem: (key, value) => saved.set(key, value),
removeItem: key => saved.delete(key),
}};
createStartDay({{
storage,
getLogin: () => 'timmy',
getCounts: () => ({{agenda:1}}),
openQueue: () => {{}},
}}).startNext();
process.stdout.write(JSON.stringify(JSON.parse(saved.get('stackchain.mobile-start-day.v1'))));
"""
assert run_node(script) == {"login": "timmy", "day": "2026-08-15"}
@pytest.mark.anyio @pytest.mark.anyio
async def test_dashboard_wires_thumb_safe_start_day_briefing_into_offline_mobile_bundle(): async def test_dashboard_wires_thumb_safe_start_day_briefing_into_offline_mobile_bundle():
html = await dashboard() html = await dashboard()
@ -214,21 +120,10 @@ async def test_dashboard_wires_thumb_safe_start_day_briefing_into_offline_mobile
assert 'id="mobile-start-day-summary"' in html assert 'id="mobile-start-day-summary"' in html
assert 'id="mobile-start-day-phases"' in html assert 'id="mobile-start-day-phases"' in html
assert 'id="mobile-start-day-action"' in html assert 'id="mobile-start-day-action"' in html
assert 'id="finish-mobile-start-day"' in html
assert '<script src="static/mobile-start-day.js"></script>' in html assert '<script src="static/mobile-start-day.js"></script>' in html
assert "const mobileStartDay = createMobileStartDay({" in html assert "const mobileStartDay = createMobileStartDay({" in html
assert "getLogin: () => confirmedOwnerLogin" in html assert "openQueue: name => name === 'find'" in html
assert "onHandoff: current =>" in html
assert "mobileStartDay.completePhase('agenda')" in html
assert "mobileStartDay.completePhase('update')" in html
assert "mobileStartDay.completePhase('filed')" in html
assert "mobileStartDay.finish()" in html
assert "openQueue: name =>" in html
assert "if (sheet.open) sheet.close();" in html
assert "name === 'find' ? qs('#find-work').click() : mobileQueueLauncher.open(name)" in html
assert "mobileStartDay.render();" in html assert "mobileStartDay.render();" in html
assert ".mobile-start-day-action { width:100%; min-height:48px;" in html assert ".mobile-start-day-action { width:100%; min-height:48px;" in html
assert ".mobile-start-day-finish { min-height:44px;" in html
assert "max-width:100%; overflow-wrap:anywhere;" in html assert "max-width:100%; overflow-wrap:anywhere;" in html
assert "BASE + 'static/mobile-start-day.js'" in service_worker assert "BASE + 'static/mobile-start-day.js'" in service_worker
assert "stackchain-dashboard-shell-v104" in service_worker

View File

@ -410,7 +410,7 @@ async def test_plan_today_wires_cancel_back_and_success_through_overlay_history(
def test_plan_today_controller_is_available_in_the_offline_shell(): def test_plan_today_controller_is_available_in_the_offline_shell():
source = SERVICE_WORKER.read_text() source = SERVICE_WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/plan-today.js'" in source assert "BASE + 'static/plan-today.js'" in source
assert "BASE + 'static/plan-today-readiness.js'" in source assert "BASE + 'static/plan-today-readiness.js'" in source
assert "BASE + 'static/plan-today-preview.js'" in source assert "BASE + 'static/plan-today-preview.js'" in source

View File

@ -152,7 +152,7 @@ async function dispatchPush(payload) {{
def test_resumable_today_session_ships_in_a_new_offline_shell(): def test_resumable_today_session_ships_in_a_new_offline_shell():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/my-work.js'" in source assert "BASE + 'static/my-work.js'" in source
assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/dashboard.js'" in source
assert "BASE + 'static/dashboard.css'" in source assert "BASE + 'static/dashboard.css'" in source
@ -161,14 +161,14 @@ def test_resumable_today_session_ships_in_a_new_offline_shell():
def test_ownership_exit_runtime_rolls_the_offline_shell_cache(): def test_ownership_exit_runtime_rolls_the_offline_shell_cache():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/dashboard.js'" in source
def test_offline_review_next_ships_today_completion_atomically(): def test_offline_review_next_ships_today_completion_atomically():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/today-completion.js'" in source assert "BASE + 'static/today-completion.js'" in source
assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/dashboard.js'" in source
@ -176,7 +176,7 @@ def test_offline_review_next_ships_today_completion_atomically():
def test_duplicate_aware_capture_ships_in_a_new_offline_shell(): def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/create-issue-sheet.js'" in source assert "BASE + 'static/create-issue-sheet.js'" in source
assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/dashboard.js'" in source
@ -184,14 +184,14 @@ def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
def test_exact_later_picker_ships_atomically_in_a_new_offline_shell(): def test_exact_later_picker_ships_atomically_in_a_new_offline_shell():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/later-picker.js'" in source assert "BASE + 'static/later-picker.js'" in source
def test_navigation_deadline_ships_in_a_new_shell_cache(): def test_navigation_deadline_ships_in_a_new_shell_cache():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/dashboard.css'" in source assert "BASE + 'static/dashboard.css'" in source
assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/dashboard.js'" in source
assert "BASE + 'static/install-app.js'" in source assert "BASE + 'static/install-app.js'" in source
@ -200,21 +200,21 @@ def test_navigation_deadline_ships_in_a_new_shell_cache():
def test_today_convergence_ships_in_a_new_shell_cache(): def test_today_convergence_ships_in_a_new_shell_cache():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/today-sync.js'" in source assert "BASE + 'static/today-sync.js'" in source
def test_mobile_search_viewport_ships_in_a_new_offline_shell(): def test_mobile_search_viewport_ships_in_a_new_offline_shell():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/mobile-search-viewport.js'" in source assert "BASE + 'static/mobile-search-viewport.js'" in source
def test_update_ownership_flow_ships_atomically_in_a_new_offline_shell(): def test_update_ownership_flow_ships_atomically_in_a_new_offline_shell():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/update-ownership.js'" in source assert "BASE + 'static/update-ownership.js'" in source
@ -800,7 +800,7 @@ def test_one_session_bound_csrf_proof_is_reused_for_a_background_drain():
def test_queue_today_ships_atomically_in_a_new_offline_shell(): def test_queue_today_ships_atomically_in_a_new_offline_shell():
source = WORKER.read_text() source = WORKER.read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/queue-today.js'" in source assert "BASE + 'static/queue-today.js'" in source

View File

@ -221,7 +221,7 @@ async def test_today_blocker_opens_existing_preview_and_preserves_readiness_gate
def test_readiness_runtime_is_available_in_offline_shell(): def test_readiness_runtime_is_available_in_offline_shell():
service_worker = SERVICE_WORKER.read_text() service_worker = SERVICE_WORKER.read_text()
assert "const CACHE = 'stackchain-dashboard-shell-v104';" in service_worker assert "const CACHE = 'stackchain-dashboard-shell-v103';" in service_worker
assert "BASE + 'static/today-readiness.js'" in service_worker assert "BASE + 'static/today-readiness.js'" in service_worker

View File

@ -127,7 +127,7 @@ sync.enqueueConfiguration(120, {{'issue:r:1:':60}});
def test_inflight_today_drain_ships_in_a_new_offline_shell(): def test_inflight_today_drain_ships_in_a_new_offline_shell():
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text() source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
assert "stackchain-dashboard-shell-v104" in source assert "stackchain-dashboard-shell-v103" in source
assert "BASE + 'static/today-sync.js'" in source assert "BASE + 'static/today-sync.js'" in source