From b845f8ba958a550f58e3d24d504cacae406deca5 Mon Sep 17 00:00:00 2001 From: timmy Date: Mon, 17 Aug 2026 13:34:58 +0000 Subject: [PATCH] feat: make Prepare Today the primary mobile entry (Closes #1024) --- frontend/dashboard.js | 20 +++++++- frontend/mobile-start-day.js | 39 +++++++++++++-- frontend/mobile-task-dock.js | 9 ++-- frontend/mobile-work-entry.js | 4 ++ frontend/service-worker.js | 2 +- tests/test_comment_next.py | 2 +- tests/test_later_sync.py | 2 +- tests/test_markdown_renderer.py | 2 +- tests/test_mobile_composer_integration.py | 2 +- tests/test_mobile_device_setup.py | 2 +- tests/test_mobile_insights.py | 2 +- tests/test_mobile_start_day.py | 58 ++++++++++++++++++++++- tests/test_mobile_task_dock.py | 53 +++++++++++++++++++++ tests/test_plan_today.py | 2 +- tests/test_service_worker.py | 28 +++++------ tests/test_today_readiness.py | 2 +- tests/test_today_sync.py | 2 +- 17 files changed, 198 insertions(+), 33 deletions(-) diff --git a/frontend/dashboard.js b/frontend/dashboard.js index 6c02429..1c6cc14 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -97,6 +97,7 @@ (authored ? applyAuthoredOutboxResult : applyOutboxResult)(result); } let mobileQueueCounts = {}; + let mobilePreparationItems = {}; const mobileDeliveryRecovery = createMobileDeliveryRecovery({ getItems: () => draftInbox.partition(lastDrafts).deliveries, getIndex: item => lastDrafts.indexOf(item), @@ -131,21 +132,31 @@ getCounts: () => mobileQueueCounts, openFindWork: () => qs('#find-work').click(), }); + function openMobileStartDay() { + const state = mobileStartDay.state(); + mobileStartDay.render(); + qs('#mobile-queue-heading').textContent = state.active ? 'Resume Prepare Today' : 'Prepare Today'; + const sheet = qs('#mobile-queue-sheet'); + if (!sheet.open) qs('#mobile-queue-sheet').showModal(); + qs('#mobile-start-day-action').focus(); + } const mobileWorkEntry = createMobileWorkEntry({ isTodayActive: () => workSession.checkpointed(), isTodayResumable: () => workSession.resumable(), getTodayCount: () => todayMyWork.length, getEligibleCount: () => activeMyWork.length, + getPreparationState: () => mobileStartDay.state(), queueLauncher: mobileQueueLauncher, continueToday: continueTodaySession, resumeToday: resumeTodaySession, startToday: startTodaySession, planToday: () => openPlanToday(mobileTaskButtons.work), - + prepareToday: openMobileStartDay, findWork: () => qs('#find-work').click(), }); const mobileStartDay = createMobileStartDay({ getCounts: () => mobileQueueCounts, + getPhaseItems: () => mobilePreparationItems, getLogin: () => confirmedOwnerLogin, openQueue: name => { const sheet = qs('#mobile-queue-sheet'); @@ -3053,6 +3064,13 @@ counts.later = laterMyWork.length; counts.draft = lastDrafts.length; counts.delivery = draftInbox.partition(lastDrafts).actionable; + mobilePreparationItems = { + delivery:draftInbox.partition(lastDrafts).deliveries, + agenda:agendaMyWork(activeMyWork), + attention:activeMyWork.filter(item => item.needs_attention), + update:activeMyWork.filter(item => item.has_update), + filed:activeMyWork.filter(item => item.is_filed), + }; if (!launchFilterResolved) { selectedWorkFilter = mobileLaunch.chooseFilter({ saved: savedWorkFilter, today: counts.today, attention: counts.attention, diff --git a/frontend/mobile-start-day.js b/frontend/mobile-start-day.js index e3ccae2..eed6a45 100644 --- a/frontend/mobile-start-day.js +++ b/frontend/mobile-start-day.js @@ -11,11 +11,44 @@ ['update', 'Updates'], ['filed', 'Filed'], ]; + const anonymousItems = new WeakMap(); + let anonymousItemSequence = 0; function count(value) { return Math.max(0, Number(value) || 0); } + function itemIdentity(item) { + if (item === null || item === undefined) return ''; + if (typeof item !== 'object') return String(item); + const explicit = item.outbox_id || item.id || item.key || item.url; + if (explicit !== null && explicit !== undefined && String(explicit)) { + return String(item.kind || 'item') + ':' + String(explicit); + } + if (!anonymousItems.has(item)) anonymousItems.set(item, 'anonymous:' + (++anonymousItemSequence)); + return anonymousItems.get(item); + } + + function reviewPhases(counts) { + if (!options.getPhaseItems) { + return reviewOrder + .map(([name, label]) => ({name, label, count: count(counts[name])})) + .filter(phase => phase.count > 0); + } + const items = options.getPhaseItems() || {}; + const seen = new Set(); + return reviewOrder.flatMap(([name, label]) => { + let phaseCount = 0; + (items[name] || []).forEach(item => { + const identity = itemIdentity(item); + if (!identity || seen.has(identity)) return; + seen.add(identity); + phaseCount += 1; + }); + return phaseCount ? [{name, label, count:phaseCount}] : []; + }); + } + function localDay() { const now = new Date(); const pad = value => String(value).padStart(2, '0'); @@ -69,12 +102,10 @@ function briefing() { const counts = options.getCounts ? options.getCounts() : {}; - const phases = reviewOrder - .map(([name, label]) => ({name, label, count: count(counts[name])})) - .filter(phase => phase.count > 0); + const phases = reviewPhases(counts); const total = phases.reduce((sum, phase) => sum + phase.count, 0); const today = count(counts.today); - const delivery = count(counts.delivery); + 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' : diff --git a/frontend/mobile-task-dock.js b/frontend/mobile-task-dock.js index a76fdda..db85bc2 100644 --- a/frontend/mobile-task-dock.js +++ b/frontend/mobile-task-dock.js @@ -66,13 +66,16 @@ function updateWork(mode) { const text = { continue:'Continue', resume:'Resume', start:'Start', plan:'Plan', find:'Find', + prepare:'Prepare', 'prepare-resume':'Resume prep', delivery:'Delivery', attention:'Attention', update:'Updates', agenda:'Agenda', filed:'Filed', later:'Later', draft:'Drafts', }[mode] || 'Work'; const queue = ['Delivery', 'Attention', 'Updates', 'Agenda', 'Filed', 'Later', 'Drafts'].includes(text); if (options.workLabel) options.workLabel.textContent = text; - if (buttons.work) buttons.work.setAttribute('aria-label', queue - ? (mode === 'update' ? 'Resume ' : 'Open ') + text - : mode === 'find' ? 'Find work' : text + (mode === 'work' ? '' : ' Today')); + const actionLabel = mode === 'prepare' ? 'Prepare Today' : + mode === 'prepare-resume' ? 'Resume preparation' : + queue ? (mode === 'update' ? 'Resume ' : 'Open ') + text : + mode === 'find' ? 'Find work' : text + (mode === 'work' ? '' : ' Today'); + if (buttons.work) buttons.work.setAttribute('aria-label', actionLabel); } function updateAttention(count) { diff --git a/frontend/mobile-work-entry.js b/frontend/mobile-work-entry.js index a8952b3..1645f7c 100644 --- a/frontend/mobile-work-entry.js +++ b/frontend/mobile-work-entry.js @@ -4,6 +4,9 @@ })(typeof self !== 'undefined' ? self : this, function createMobileWorkEntry(options) { function mode() { if (options.isTodayActive()) return 'continue'; + const preparation = options.getPreparationState?.(); + if (preparation?.active) return 'prepare-resume'; + if (preparation?.next && !['today', 'find'].includes(preparation.next)) return 'prepare'; const recommended = options.queueLauncher && options.queueLauncher.recommend(); if (recommended && !['today', 'find'].includes(recommended.name)) return recommended.name; if (options.getTodayCount() > 0 && options.isTodayResumable()) return 'resume'; @@ -15,6 +18,7 @@ function open() { const current = mode(); if (current === 'continue') options.continueToday(); + else if (['prepare', 'prepare-resume'].includes(current)) options.prepareToday(); else if (current === 'resume') options.resumeToday(); else if (current === 'start') options.startToday(); else if (current === 'plan') options.planToday(); diff --git a/frontend/service-worker.js b/frontend/service-worker.js index 1388fd9..9a89135 100644 --- a/frontend/service-worker.js +++ b/frontend/service-worker.js @@ -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-v117'; +const CACHE = 'stackchain-dashboard-shell-v118'; 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; diff --git a/tests/test_comment_next.py b/tests/test_comment_next.py index 78b96e6..cc87091 100644 --- a/tests/test_comment_next.py +++ b/tests/test_comment_next.py @@ -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 button { min-height:44px;' in html worker = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text() - assert "stackchain-dashboard-shell-v117" in worker + assert "stackchain-dashboard-shell-v118" in worker diff --git a/tests/test_later_sync.py b/tests/test_later_sync.py index 8f382c8..6c6ba0e 100644 --- a/tests/test_later_sync.py +++ b/tests/test_later_sync.py @@ -435,5 +435,5 @@ async def test_dashboard_syncs_every_later_change_and_exposes_account_status(): def test_later_sync_ships_atomically_in_the_offline_shell(): source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/later-sync.js'" in source diff --git a/tests/test_markdown_renderer.py b/tests/test_markdown_renderer.py index 2755693..c405dc4 100644 --- a/tests/test_markdown_renderer.py +++ b/tests/test_markdown_renderer.py @@ -256,4 +256,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 pre { max-width:100%; overflow-x:auto;" in css assert ".markdown-content a { min-height:44px;" in css - assert "stackchain-dashboard-shell-v117" in worker + assert "stackchain-dashboard-shell-v118" in worker diff --git a/tests/test_mobile_composer_integration.py b/tests/test_mobile_composer_integration.py index 1e78924..32a3ad3 100644 --- a/tests/test_mobile_composer_integration.py +++ b/tests/test_mobile_composer_integration.py @@ -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])) assert local_assets <= shell_assets, f"Offline shell is missing: {sorted(local_assets - shell_assets)}" - assert "stackchain-dashboard-shell-v117" in worker + assert "stackchain-dashboard-shell-v118" in worker def test_all_conversation_composers_offer_accessible_mobile_mentions(): diff --git a/tests/test_mobile_device_setup.py b/tests/test_mobile_device_setup.py index ec8b3c4..c650b9f 100644 --- a/tests/test_mobile_device_setup.py +++ b/tests/test_mobile_device_setup.py @@ -223,7 +223,7 @@ def test_mobile_dashboard_mounts_phone_safe_device_setup_flow(): assert "promptStorage:localStorage" in dashboard assert "pushControllerReady.then(ensureDeviceSetup)" in dashboard assert "BASE + 'static/mobile-device-setup.js'" in worker - assert "stackchain-dashboard-shell-v117" in worker + assert "stackchain-dashboard-shell-v118" in worker assert ".device-setup-panel" in css assert ".device-readiness-card" in css assert "overflow-x:hidden" in css diff --git a/tests/test_mobile_insights.py b/tests/test_mobile_insights.py index 76f4121..436d153 100644 --- a/tests/test_mobile_insights.py +++ b/tests/test_mobile_insights.py @@ -174,5 +174,5 @@ async def test_mobile_home_progressively_discloses_secondary_panels_as_insights( def test_mobile_insights_rolls_into_the_offline_shell(): worker = (CONTROLLER.parent / "service-worker.js").read_text() - assert "stackchain-dashboard-shell-v117" in worker + assert "stackchain-dashboard-shell-v118" in worker assert "BASE + 'static/mobile-insights.js'" in worker diff --git a/tests/test_mobile_start_day.py b/tests/test_mobile_start_day.py index 634f4eb..65ff718 100644 --- a/tests/test_mobile_start_day.py +++ b/tests/test_mobile_start_day.py @@ -110,6 +110,42 @@ process.stdout.write(JSON.stringify({{blocked, handed, opened, handoffs}})); assert output["handoffs"] == ["agenda"] +def test_prepare_today_counts_each_work_identity_in_only_its_highest_priority_phase(): + script = f""" +const createStartDay = require({json.dumps(str(START_DAY))}); +const agendaUpdate = {{kind:'issue', key:'lab#1'}}; +const attentionUpdate = {{kind:'pull', key:'lab#2'}}; +const filed = {{kind:'issue', key:'lab#3'}}; +const delivery = {{outbox_id:'draft-4'}}; +const controller = createStartDay({{ + getCounts: () => ({{today:2}}), + getPhaseItems: () => ({{ + delivery:[delivery], + agenda:[agendaUpdate], + attention:[agendaUpdate, attentionUpdate], + update:[agendaUpdate, attentionUpdate], + filed:[filed], + }}), + openQueue: () => {{}}, +}}); +process.stdout.write(JSON.stringify(controller.briefing())); +""" + + output = run_node(script) + assert output == { + "total": 4, + "next": "delivery", + "label": "Review Delivery", + "summary": "1 delivery needs action before Today · 3 other items · 2 planned", + "phases": [ + {"name": "delivery", "label": "Delivery recovery", "count": 1}, + {"name": "agenda", "label": "Agenda", "count": 1}, + {"name": "attention", "label": "Attention", "count": 1}, + {"name": "filed", "label": "Filed", "count": 1}, + ], + } + + def test_start_day_view_renders_refreshed_phases_and_launches_primary_action(): script = f""" const createStartDay = require({json.dumps(str(START_DAY))}); @@ -322,4 +358,24 @@ async def test_dashboard_wires_thumb_safe_start_day_briefing_into_offline_mobile assert ".mobile-start-day-finish { min-height:44px;" in html assert "max-width:100%; overflow-wrap:anywhere;" in html assert "BASE + 'static/mobile-start-day.js'" in service_worker - assert "stackchain-dashboard-shell-v117" in service_worker + assert "stackchain-dashboard-shell-v118" in service_worker + + +@pytest.mark.anyio +async def test_primary_mobile_work_action_opens_a_unique_item_prepare_today_briefing(): + html = await dashboard() + + assert "getPreparationState: () => mobileStartDay.state()" in html + assert "prepareToday: openMobileStartDay" in html + assert "function openMobileStartDay()" in html + assert "qs('#mobile-queue-heading').textContent = state.active ? 'Resume Prepare Today' : 'Prepare Today';" in html + assert "qs('#mobile-queue-sheet').showModal();" in html + assert "qs('#mobile-start-day-action').focus();" in html + assert "let mobilePreparationItems = {};" in html + assert "getPhaseItems: () => mobilePreparationItems" in html + assert "mobilePreparationItems = {" in html + assert "delivery:draftInbox.partition(lastDrafts).deliveries" in html + assert "agenda:agendaMyWork(activeMyWork)" in html + assert "attention:activeMyWork.filter(item => item.needs_attention)" in html + assert "update:activeMyWork.filter(item => item.has_update)" in html + assert "filed:activeMyWork.filter(item => item.is_filed)" in html diff --git a/tests/test_mobile_task_dock.py b/tests/test_mobile_task_dock.py index 023ca97..49a2a20 100644 --- a/tests/test_mobile_task_dock.py +++ b/tests/test_mobile_task_dock.py @@ -85,6 +85,37 @@ process.stdout.write(JSON.stringify({{modes, calls}})); } +def test_mobile_work_entry_opens_prepare_today_before_direct_queue_work(): + script = f""" +const createEntry = require({json.dumps(str(ENTRY))}); +const calls = []; +const state = {{active:true, preparation:{{active:false, next:'attention'}}}}; +const entry = createEntry({{ + isTodayActive: () => state.active, + isTodayResumable: () => false, + getTodayCount: () => 0, + getEligibleCount: () => 3, + getPreparationState: () => state.preparation, + queueLauncher: {{recommend: () => ({{name:'attention'}}), open: name => calls.push('queue:' + name)}}, + continueToday: () => calls.push('continue'), + prepareToday: () => calls.push('prepare'), + planToday: () => calls.push('plan'), + findWork: () => calls.push('find'), +}}); +const modes = [entry.open()]; +state.active = false; modes.push(entry.open()); +state.preparation.active = true; modes.push(entry.open()); +process.stdout.write(JSON.stringify({{modes, calls}})); +""" + result = subprocess.run(["node", "-e", script], capture_output=True, text=True) + + assert result.returncode == 0, result.stderr + assert json.loads(result.stdout) == { + "modes": ["continue", "prepare", "prepare-resume"], + "calls": ["continue", "prepare", "prepare"], + } + + def test_mobile_work_entry_plans_eligible_work_before_find_fallback(): script = f""" const createEntry = require({json.dumps(str(ENTRY))}); @@ -651,6 +682,28 @@ process.stdout.write(JSON.stringify(labels)); } +def test_mobile_task_dock_labels_prepare_today_as_the_primary_action(): + script = f""" +const createDock = require({json.dumps(str(DOCK))}); +const work = {{attributes: {{}}, setAttribute(name, value) {{ this.attributes[name] = value; }}}}; +const workLabel = {{textContent:''}}; +const dock = createDock({{nav:{{}}, buttons:{{work}}, workLabel}}); +const labels = {{}}; +for (const mode of ['prepare','prepare-resume']) {{ + dock.updateWork(mode); + labels[mode] = [workLabel.textContent, work.attributes['aria-label']]; +}} +process.stdout.write(JSON.stringify(labels)); +""" + result = subprocess.run(["node", "-e", script], capture_output=True, text=True) + + assert result.returncode == 0, result.stderr + assert json.loads(result.stdout) == { + "prepare": ["Prepare", "Prepare Today"], + "prepare-resume": ["Resume prep", "Resume preparation"], + } + + @pytest.mark.anyio async def test_dashboard_renders_accessible_mobile_queue_completion_handoff(): html = await dashboard() diff --git a/tests/test_plan_today.py b/tests/test_plan_today.py index d2a6f9d..d2f60d3 100644 --- a/tests/test_plan_today.py +++ b/tests/test_plan_today.py @@ -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(): source = SERVICE_WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/plan-today.js'" in source assert "BASE + 'static/plan-today-readiness.js'" in source assert "BASE + 'static/plan-today-preview.js'" in source diff --git a/tests/test_service_worker.py b/tests/test_service_worker.py index f3dfe2c..2e61d7d 100644 --- a/tests/test_service_worker.py +++ b/tests/test_service_worker.py @@ -165,13 +165,13 @@ async function dispatchPush(payload) {{ def test_offline_activation_migration_rolls_the_shell_cache(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source def test_resumable_today_session_ships_in_a_new_offline_shell(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/my-work.js'" in source assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/dashboard.css'" in source @@ -180,7 +180,7 @@ def test_resumable_today_session_ships_in_a_new_offline_shell(): def test_mobile_conversation_photo_bundles_roll_the_offline_shell(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/authored-outbox.js'" in source assert "BASE + 'static/background-issue-sync.js'" in source @@ -189,7 +189,7 @@ def test_mobile_conversation_photo_bundles_roll_the_offline_shell(): def test_photo_metadata_sanitizer_rolls_the_cached_optimizer_atomically(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/issue-evidence-review.js'" in source assert "BASE + 'static/issue-attachment.js'" in source @@ -197,14 +197,14 @@ def test_photo_metadata_sanitizer_rolls_the_cached_optimizer_atomically(): def test_ownership_exit_runtime_rolls_the_offline_shell_cache(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/dashboard.js'" in source def test_offline_review_next_ships_today_completion_atomically(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/today-completion.js'" in source assert "BASE + 'static/dashboard.js'" in source @@ -212,7 +212,7 @@ def test_offline_review_next_ships_today_completion_atomically(): def test_duplicate_aware_capture_ships_in_a_new_offline_shell(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/create-issue-sheet.js'" in source assert "BASE + 'static/dashboard.js'" in source @@ -220,7 +220,7 @@ def test_duplicate_aware_capture_ships_in_a_new_offline_shell(): def test_inline_checklist_step_flow_rolls_the_offline_shell_atomically(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/issue-sheet.js'" in source assert "BASE + 'static/checklist-conflict.js'" in source assert "BASE + 'static/dashboard.js'" in source @@ -230,14 +230,14 @@ def test_inline_checklist_step_flow_rolls_the_offline_shell_atomically(): def test_exact_later_picker_ships_atomically_in_a_new_offline_shell(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/later-picker.js'" in source def test_navigation_deadline_ships_in_a_new_shell_cache(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/dashboard.css'" in source assert "BASE + 'static/dashboard.js'" in source assert "BASE + 'static/install-app.js'" in source @@ -246,21 +246,21 @@ def test_navigation_deadline_ships_in_a_new_shell_cache(): def test_today_convergence_ships_in_a_new_shell_cache(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/today-sync.js'" in source def test_mobile_search_viewport_ships_in_a_new_offline_shell(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/mobile-search-viewport.js'" in source def test_update_ownership_flow_ships_atomically_in_a_new_offline_shell(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/update-ownership.js'" in source @@ -930,7 +930,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(): source = WORKER.read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/queue-today.js'" in source diff --git a/tests/test_today_readiness.py b/tests/test_today_readiness.py index 40642c4..2e2dcf3 100644 --- a/tests/test_today_readiness.py +++ b/tests/test_today_readiness.py @@ -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(): service_worker = SERVICE_WORKER.read_text() - assert "const CACHE = 'stackchain-dashboard-shell-v117';" in service_worker + assert "const CACHE = 'stackchain-dashboard-shell-v118';" in service_worker assert "BASE + 'static/today-readiness.js'" in service_worker diff --git a/tests/test_today_sync.py b/tests/test_today_sync.py index edc7ad7..c170b0d 100644 --- a/tests/test_today_sync.py +++ b/tests/test_today_sync.py @@ -127,7 +127,7 @@ sync.enqueueConfiguration(120, {{'issue:r:1:':60}}); def test_inflight_today_drain_ships_in_a_new_offline_shell(): source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text() - assert "stackchain-dashboard-shell-v117" in source + assert "stackchain-dashboard-shell-v118" in source assert "BASE + 'static/today-sync.js'" in source -- 2.43.0