import json
import subprocess
from pathlib import Path

import pytest


WORKER = Path(__file__).resolve().parents[1] / "frontend" / "service-worker.js"
PRIVATE_DATA_REGISTRY = WORKER.parent / "private-data-registry.js"


def run_worker_scenario(scenario: str) -> dict:
    harness = f"""
const fs = require('fs');
const vm = require('vm');
const listeners = {{}};
const state = {{ added: [], addAttempts: [], individuallyAdded: [], failedAdds: [], deleted: [], deletedDatabases: [], claimed: false, skipped: false, fetches: [], puts: [], migrated: [], activationOrder: [], oldCachedAssets: {{}}, sharedRecords: {{}}, todayCommands: {{}}, failTodayCommandPut: false, failSharedPut: false, backgroundFlushes: 0, backgroundResumes: 0, outboxPurges: 0, outboxLifecycle: [], notifications: [], closedNotifications: 0, focused: [], opened: [], failFetch: false, stallFetch: false, lateFetch: false, fetchAborted: false, fetchStatus: 200, fetchRedirected: false, cachedBody: null }};
const storedResponses = new Map();
storedResponses.set(
  'https://forge.example/dashboard/__offline-session-lease',
  new Response(JSON.stringify({{
    expires_at: Math.floor(Date.now() / 1000) + 3600,
    idle_expires_at: Math.floor(Date.now() / 1000) + 900,
  }})),
);
const cache = {{
  addAll: async urls => {{ state.added = urls; }},
  add: async url => {{
    state.addAttempts.push(url);
    if (state.failedAdds.includes(url)) throw new Error('optional asset unavailable');
    state.individuallyAdded.push(url);
  }},
  match: async request => {{
    const key = String(request.url || request);
    if (storedResponses.has(key)) return storedResponses.get(key).clone();
    return state.cachedBody === null ? null : new Response(state.cachedBody);
  }},
  put: async (request, response) => {{
    const key = String(request.url || request);
    state.puts.push(key);
    state.migrated.push(key);
    state.activationOrder.push('put:' + key);
    storedResponses.set(new URL(key, 'https://forge.example').href, response.clone());
  }},
}};
const oldCache = {{
  match: async request => {{
    const key = String(request.url || request);
    const body = state.oldCachedAssets[key];
    return body === undefined ? null : new Response(body);
  }},
}};
const context = {{
  URL, Request, Response, Headers, AbortController,
  setTimeout, clearTimeout, console,
  self: {{
    location: {{ href: 'https://forge.example/dashboard/service-worker.js', origin: 'https://forge.example' }},
    __STACKCHAIN_NAVIGATION_TIMEOUT_MS: 15,
    __STACKCHAIN_PUSH_ACTION_TIMEOUT_MS: 15,
    stackchainPrivateDatabases: require({json.dumps(str(PRIVATE_DATA_REGISTRY))}),
    __STACKCHAIN_SHARED_ATTACHMENT_STORE: {{
      put: async (id, value) => {{
        if (state.failSharedPut) throw new Error('quota');
        state.sharedRecords[id] = value;
      }},
      get: async id => state.sharedRecords[id] || null,
      delete: async id => {{ delete state.sharedRecords[id]; }},
    }},
    __STACKCHAIN_TODAY_ACTION_STORE: {{
      put: async (clientId, command) => {{
        if (state.failTodayCommandPut) throw new Error('quota');
        state.todayCommands[clientId] = command;
      }},
      claim: async (clientId, now) => {{
        const command = state.todayCommands[clientId] || null;
        delete state.todayCommands[clientId];
        return command && command.expiresAt > now ? command : null;
      }},
      purge: async () => {{ state.todayCommands = {{}}; }},
    }},
    addEventListener: (name, handler) => {{ listeners[name] = handler; }},
    skipWaiting: async () => {{ state.skipped = true; }},
    clients: {{
      claim: async () => {{ state.claimed = true; }},
      matchAll: async () => state.clientList || [],
      openWindow: async url => {{ state.opened.push(url); return state.openedClient || {{id:'opened-client',url}}; }},
    }},
    registration: {{
      showNotification: async (title, options) => state.notifications.push({{title,options}}),
      getNotifications: async () => [{{close:()=>{{state.closedNotifications += 1;}}}}],
    }},
    __issueSync: {{
      flush: async () => {{ state.backgroundFlushes += 1; state.outboxLifecycle.push('flush'); return state.flushResult; }},
      purge: async () => {{ state.outboxPurges += 1; state.outboxLifecycle.push('purge'); }},
      resume: async () => {{ state.backgroundResumes += 1; state.outboxLifecycle.push('resume'); }},
      getReceiptPreference: async login => state.receiptLogin === login,
    }},
  }},
  importScripts: () => {{}},
  indexedDB: {{deleteDatabase:name=>{{
    state.deletedDatabases.push(name);
    const request={{}};
    queueMicrotask(()=>request.onsuccess?.());
    return request;
  }}}},
  caches: {{
    open: async key => key === 'stackchain-dashboard-old' ? oldCache : cache,
    keys: async () => ['stackchain-dashboard-old', 'another-app-cache'],
    delete: async key => {{ state.deleted.push(key); state.activationOrder.push('delete:' + key); return true; }},
    match: async request => cache.match(request),
  }},
  fetch: async (request, options = {{}}) => {{
    state.fetches.push(String(request.url || request));
    if (state.failFetch) throw new Error('offline');
    if (state.stallFetch) return new Promise((resolve, reject) => {{
      options.signal?.addEventListener('abort', () => {{
        state.fetchAborted = true;
        reject(new Error('aborted'));
      }}, {{ once: true }});
    }});
    if (state.lateFetch) return new Promise(resolve => {{
      options.signal?.addEventListener('abort', () => {{ state.fetchAborted = true; }}, {{ once: true }});
      setTimeout(() => resolve(new Response('late network')), 50);
    }});
    const response = new Response('network', {{ status: state.fetchStatus }});
    Object.defineProperty(response, 'redirected', {{ value: state.fetchRedirected }});
    return response;
  }},
}};
vm.createContext(context);
vm.runInContext(fs.readFileSync({json.dumps(str(WORKER))}, 'utf8') + '\\nself.__testFetchJson = fetchJson; self.__testWithSessionCsrf = withSessionCsrf; self.__testOptionalFeatures = OPTIONAL_FEATURES;', context);
async function dispatch(name, request) {{
  let pending;
  let response;
  listeners[name]({{
    request,
    waitUntil: promise => {{ pending = promise; }},
    respondWith: promise => {{ response = promise; }},
  }});
  if (pending) await pending;
  return response ? await response : null;
}}
async function dispatchSync(tag) {{
  let pending;
  listeners.sync({{ tag, waitUntil: promise => {{ pending = promise; }} }});
  if (pending) await pending;
}}
async function dispatchMessage(data, ports = [], source = null) {{
  let pending;
  listeners.message({{ data, ports, source, waitUntil: promise => {{ pending = promise; }} }});
  if (pending) await pending;
}}
async function dispatchNotificationClick(route, action = '', notificationId = null, tag = null, url = null, actionToken = '') {{
  let pending;
  listeners.notificationclick({{
    action,
    notification: {{tag: tag || ('stackchain-update-' + notificationId), data: {{route, notificationId, ...(url ? {{url}} : {{}}), ...(actionToken ? {{actionToken}} : {{}})}}, close: () => {{ state.notificationClosed = true; }}}},
    waitUntil: promise => {{ pending = promise; }},
  }});
  if (pending) await pending;
}}
async function dispatchPush(payload) {{
  let pending;
  listeners.push({{
    data: {{json: () => payload}},
    waitUntil: promise => {{ pending = promise; }},
  }});
  if (pending) await pending;
}}
(async () => {{
{scenario}
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
    completed = subprocess.run(
        ["node", "-e", harness], capture_output=True, check=True, text=True
    )
    return json.loads(completed.stdout)


def test_private_today_action_mailbox_rolls_the_offline_shell():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" in source


def test_per_day_week_conflict_ui_rolls_the_offline_shell():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" in source
    assert "BASE + 'static/week-plan.js'" in source


def test_resumable_today_session_ships_in_a_new_offline_shell():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" in source
    assert "BASE + 'static/my-work.js'" in source
    assert "BASE + 'static/dashboard.js'" in source
    assert "BASE + 'static/dashboard.css'" in source


def test_mobile_conversation_photo_bundles_roll_the_offline_shell():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" 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


def test_photo_metadata_sanitizer_rolls_the_cached_optimizer_atomically():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" in source
    assert "BASE + 'static/issue-evidence-review.js'" in source
    assert "BASE + 'static/issue-attachment.js'" in source


def test_ownership_exit_runtime_rolls_the_offline_shell_cache():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" 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-v127" in source
    assert "BASE + 'static/today-completion.js'" in source
    assert "BASE + 'static/dashboard.js'" in source


def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" in source
    assert "BASE + 'static/create-issue-sheet.js'" in source
    assert "BASE + 'static/dashboard.js'" in source


def test_inline_checklist_step_flow_rolls_the_offline_shell_atomically():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" 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
    assert "BASE + 'static/dashboard.css'" in source


def test_exact_later_picker_ships_atomically_in_a_new_offline_shell():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" 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-v127" in source
    assert "BASE + 'static/dashboard.css'" in source
    assert "BASE + 'static/dashboard.js'" in source
    assert "BASE + 'static/install-app.js'" in source


def test_today_convergence_ships_in_a_new_shell_cache():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" 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-v127" 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-v127" in source
    assert "BASE + 'static/update-ownership.js'" in source


def test_image_share_target_stages_one_supported_image_and_redirects_to_capture():
    result = run_worker_scenario(
        """
  const image = new Blob(['pixels'], {type:'image/png'});
  Object.defineProperty(image, 'name', {value:'bug.png'});
  const form = new FormData();
  form.append('title', 'Broken checkout');
  form.append('text', 'Steps to reproduce');
  form.append('url', 'https://example.test/checkout');
  form.append('image', image, 'bug.png');
  const request = new Request('https://forge.example/dashboard/share-target', {
    method:'POST', body:form, headers:{'Sec-Fetch-Site':'none'},
  });
  Object.defineProperty(request, 'mode', {value:'navigate'});
  const response = await dispatch('fetch', request);
  const [id, record] = Object.entries(state.sharedRecords)[0];
  process.stdout.write(JSON.stringify({
    status:response.status, location:response.headers.get('Location'), id,
    title:record.title, text:record.text, url:record.url,
    filename:record.attachments[0].filename,
    contentType:record.attachments[0].contentType,
    size:record.attachments[0].blob.size,
  }));
"""
    )

    assert result == {
        "status": 303,
        "location": "/dashboard/?launch=new&shared=bundle",
        "id": "shared-image",
        "title": "Broken checkout",
        "text": "Steps to reproduce",
        "url": "https://example.test/checkout",
        "filename": "bug.png",
        "contentType": "image/png",
        "size": 6,
    }


def test_image_share_target_stages_ordered_evidence_bundle():
    result = run_worker_scenario(
        """
  const form = new FormData();
  form.append('image', new Blob(['one'], {type:'image/png'}), 'one.png');
  form.append('image', new Blob(['two'], {type:'image/jpeg'}), 'two.jpg');
  const request = new Request('https://forge.example/dashboard/share-target', {
    method:'POST', body:form, headers:{'Sec-Fetch-Site':'same-origin'},
  });
  Object.defineProperty(request, 'mode', {value:'navigate'});
  const response = await dispatch('fetch', request);
  const record=state.sharedRecords['shared-image'];
  process.stdout.write(JSON.stringify({
    status:response.status, location:response.headers.get('Location'),
    names:record.attachments.map(value=>value.filename),sizes:record.attachments.map(value=>value.blob.size),
  }));
"""
    )

    assert result == {
        "status": 303,
        "location": "/dashboard/?launch=new&shared=bundle",
        "names": ["one.png", "two.jpg"],
        "sizes": [3, 3],
    }


def test_share_target_rejects_cross_site_posts_without_touching_staged_bundle():
    result = run_worker_scenario(
        """
  state.sharedRecords['shared-image']={title:'Keep me',text:'private',url:'',attachments:[]};
  const form=new FormData();form.append('title','Replace me');
  const request=new Request('https://forge.example/dashboard/share-target',{
    method:'POST',body:form,headers:{'Sec-Fetch-Site':'cross-site'},
  });
  Object.defineProperty(request,'mode',{value:'navigate'});
  const response=await dispatch('fetch',request);
  process.stdout.write(JSON.stringify({status:response.status,record:state.sharedRecords['shared-image']}));
"""
    )

    assert result == {
        "status": 403,
        "record": {"title": "Keep me", "text": "private", "url": "", "attachments": []},
    }


def test_invalid_share_target_preserves_the_previous_atomic_bundle():
    result = run_worker_scenario(
        """
  state.sharedRecords['shared-image']={title:'Keep me',text:'private',url:'',attachments:[]};
  const form=new FormData();
  form.append('image',new Blob(['payload'],{type:'application/pdf'}),'secret.pdf');
  const request=new Request('https://forge.example/dashboard/share-target',{
    method:'POST',body:form,headers:{'Sec-Fetch-Site':'none'},
  });
  Object.defineProperty(request,'mode',{value:'navigate'});
  const response=await dispatch('fetch',request);
  process.stdout.write(JSON.stringify({location:response.headers.get('Location'),record:state.sharedRecords['shared-image']}));
"""
    )

    assert result == {
        "location": "/dashboard/?launch=new&shared=unsupported",
        "record": {"title": "Keep me", "text": "private", "url": "", "attachments": []},
    }


def test_share_target_storage_failure_preserves_bundle_and_returns_recovery_marker():
    result = run_worker_scenario(
        """
  state.sharedRecords['shared-image']={title:'Keep me',text:'private',url:'',attachments:[]};
  state.failSharedPut=true;
  const form=new FormData();form.append('title','New private content');
  const request=new Request('https://forge.example/dashboard/share-target',{
    method:'POST',body:form,headers:{'Sec-Fetch-Site':'none'},
  });
  Object.defineProperty(request,'mode',{value:'navigate'});
  const response=await dispatch('fetch',request);
  process.stdout.write(JSON.stringify({location:response.headers.get('Location'),record:state.sharedRecords['shared-image']}));
"""
    )

    assert result == {
        "location": "/dashboard/?launch=new&shared=unavailable",
        "record": {"title": "Keep me", "text": "private", "url": "", "attachments": []},
    }


def test_share_target_manifest_uses_dedicated_admission_path():
    manifest = json.loads((WORKER.parent / "manifest.webmanifest").read_text())

    assert manifest["share_target"]["action"] == "./share-target"


def test_background_sync_event_flushes_closed_app_issue_outbox_only_for_its_tag():
    result = run_worker_scenario(
        """
  await dispatchSync('stackchain-issue-outbox-v1');
  await dispatchSync('another-app-sync');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["backgroundFlushes"] == 1


def test_background_delivery_preserves_structured_uncertain_error():
    result = run_worker_scenario(
        """
  context.fetch=async()=>new Response(JSON.stringify({detail:{code:'delivery_uncertain',message:'Verify it was not posted before retrying.'}}),{status:422,headers:{'Content-Type':'application/json'}});
  const outcome=await context.self.__testFetchJson('/dashboard/api/v1/repos/o/r/issues',{method:'POST'})
    .then(()=>({ok:true}),error=>({status:error.status,code:error.code,message:error.message}));
  process.stdout.write(JSON.stringify(outcome));
"""
    )

    assert result == {
        "status": 422,
        "code": "delivery_uncertain",
        "message": "Verify it was not posted before retrying.",
    }


def test_revoked_background_session_purges_worker_data_and_notifies_dashboard_clients():
    result = run_worker_scenario(
        """
  state.clientMessages=[];
  state.clientList=[{postMessage:message=>state.clientMessages.push(message)}];
  context.fetch=async()=>new Response(JSON.stringify({detail:'Authentication required',code:'session_revoked'}),{status:401,headers:{'Content-Type':'application/json'}});
  const outcome=await context.self.__testFetchJson('/dashboard/api/v1/repos/o/r/issues',{method:'POST'})
    .then(()=>({ok:true}),error=>({status:error.status,code:error.code}));
  process.stdout.write(JSON.stringify({state,outcome}));
"""
    )

    assert result["outcome"] == {"status": 401, "code": "session_revoked"}
    assert result["state"]["outboxPurges"] == 1
    assert result["state"]["deletedDatabases"] == [
        "stackchain-background-outbox-v1",
        "stackchain-offline-work-v2",
        "stackchain-unfiled-captures-v1",
        "stackchain-voice-transcripts-v1",
        "stackchain-search-reply-drafts-v1",
        "stackchain-conversation-reply-drafts-v1",
        "stackchain-today-action-mailbox-v1",
    ]
    assert result["state"]["deleted"] == ["stackchain-dashboard-old"]
    assert result["state"]["clientMessages"] == [
        {"type": "stackchain-session-revoked"}
    ]


def test_idle_background_session_preserves_outbox_and_notifies_dashboard_clients():
    result = run_worker_scenario(
        """
  state.clientMessages=[];
  state.clientList=[{postMessage:message=>state.clientMessages.push(message)}];
  context.fetch=async()=>new Response(JSON.stringify({detail:'Authentication required',code:'session_idle'}),{status:401,headers:{'Content-Type':'application/json'}});
  const outcome=await context.self.__testFetchJson('/dashboard/api/v1/repos/o/r/issues',{method:'POST'})
    .then(()=>({ok:true}),error=>({status:error.status,code:error.code}));
  process.stdout.write(JSON.stringify({state,outcome}));
"""
    )

    assert result["outcome"] == {"status": 401, "code": "session_idle"}
    assert result["state"]["outboxPurges"] == 0
    assert result["state"]["deleted"] == []
    assert result["state"]["clientMessages"] == [
        {"type": "stackchain-session-idle"}
    ]


def test_authenticated_page_message_resumes_queued_background_delivery():
    result = run_worker_scenario(
        """
  await dispatchMessage({type:'stackchain-resume-outbox'});
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["backgroundFlushes"] == 1


def test_authenticated_resume_rearms_a_previously_purged_worker_before_flushing():
    result = run_worker_scenario(
        """
  const replies = [];
  await dispatchMessage({type:'stackchain-purge-outbox'}, [{postMessage:value=>replies.push(value)}]);
  await dispatchMessage({type:'stackchain-resume-outbox'});
  process.stdout.write(JSON.stringify({state,replies}));
"""
    )

    assert result["replies"] == [{"ok": True}]
    assert result["state"]["outboxLifecycle"] == ["purge", "resume", "flush"]
    assert result["state"]["backgroundResumes"] == 1


def test_active_today_message_replaces_one_privacy_safe_lock_screen_notification():
    result = run_worker_scenario(
        """
  await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:true,identity:'secret/repo#42',actionToken:'opaque-token-1234567890'});
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["notifications"] == [
        {
            "title": "Today session running",
            "options": {
                "body": "Your active Today timer is running.",
                "tag": "stackchain-today-session",
                "renotify": False,
                "silent": True,
                "actions": [
                    {"action": "pause-today", "title": "Pause"},
                    {"action": "finish-today", "title": "Finish current"},
                ],
                "data": {"route": "#/my-work/today", "actionToken": "opaque-token-1234567890"},
            },
        }
    ]
    assert "secret" not in json.dumps(result["notifications"])


def test_live_today_break_shows_return_time_and_safe_resume_controls():
    result = run_worker_scenario(
        """
  const deadline = Date.now() + 5 * 60 * 1000;
  await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:false,breakDeadlineAt:deadline,identity:'secret/repo#42',actionToken:'opaque-token-1234567890'});
  process.stdout.write(JSON.stringify(state));
"""
    )

    notification = result["notifications"][0]
    assert notification["title"] == "On a Today break"
    assert notification["options"]["body"].startswith("Return at ")
    assert notification["options"]["actions"] == [
        {"action": "resume-today", "title": "Resume now"},
        {"action": "open-today", "title": "Open Today"},
    ]
    assert notification["options"]["data"]["actionToken"] == "opaque-token-1234567890"
    assert "secret" not in json.dumps(notification)


def test_invalid_or_expired_break_deadline_falls_back_to_paused_controls():
    result = run_worker_scenario(
        """
  await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:false,breakDeadlineAt:Date.now()-1,actionToken:'opaque-token-1234567890'});
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["notifications"][0]["title"] == "Today session paused"
    assert result["notifications"][0]["options"]["actions"][0] == {
        "action": "resume-today", "title": "Resume"
    }


def test_inactive_today_message_closes_the_lock_screen_notification():
    result = run_worker_scenario(
        """
  await dispatchMessage({type:'stackchain-today-lock-screen',active:false,running:false});
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["notifications"] == []
    assert result["closedNotifications"] == 1


def test_private_data_purge_also_removes_the_today_lock_screen_notification():
    result = run_worker_scenario(
        """
  await dispatchMessage({type:'stackchain-purge-outbox'}, [{postMessage:()=>{}}]);
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["outboxPurges"] == 1
    assert result["closedNotifications"] == 1


def test_today_lock_screen_action_updates_an_open_dashboard_without_navigation():
    result = run_worker_scenario(
        """
  state.clientMessages=[];
  state.clientList=[{
    url:'https://forge.example/dashboard/#/my-work/today',
    postMessage:message=>state.clientMessages.push(message),
    focus:async()=>state.focused.push('today'),
  }];
  await dispatchNotificationClick('#/my-work/today','pause-today',null,'stackchain-today-session');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["clientMessages"] == [
        {"type": "stackchain-today-timer-action", "action": "pause"}
    ]
    assert result["focused"] == ["today"]
    assert result["opened"] == []


def test_today_lock_screen_action_opens_clean_route_and_privately_hands_off_once_when_closed():
    result = run_worker_scenario(
        """
  state.openedClient={id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today'};
  await dispatchNotificationClick('#/my-work/today','resume-today',null,'stackchain-today-session',null,'opaque-token-1234567890');
  const messages=[];
  const source={id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today',postMessage:message=>messages.push(message)};
  await dispatchMessage({type:'stackchain-claim-today-action'}, [], source);
  await dispatchMessage({type:'stackchain-claim-today-action'}, [], source);
  state.claimMessages=messages;
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["opened"] == [
        "https://forge.example/dashboard/#/my-work/today"
    ]
    assert "today_timer_action" not in result["opened"][0]
    assert "today_action_token" not in result["opened"][0]
    assert result["claimMessages"] == [{
        "type": "stackchain-today-timer-action",
        "action": "resume",
        "actionToken": "opaque-token-1234567890",
    }]
    assert result["todayCommands"] == {}


def test_today_lock_screen_private_handoff_rejects_wrong_client_and_expires_closed():
    result = run_worker_scenario(
        """
  state.openedClient={id:'intended-client',url:'https://forge.example/dashboard/#/my-work/today'};
  await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890');
  const wrong=[];
  await dispatchMessage({type:'stackchain-claim-today-action'}, [], {id:'other-client',url:'https://forge.example/dashboard/#/my-work/today',postMessage:message=>wrong.push(message)});
  state.todayCommands['intended-client'].expiresAt=0;
  const expired=[];
  await dispatchMessage({type:'stackchain-claim-today-action'}, [], {id:'intended-client',url:'https://forge.example/dashboard/#/my-work/today',postMessage:message=>expired.push(message)});
  process.stdout.write(JSON.stringify({wrong,expired,commands:state.todayCommands}));
"""
    )

    assert result == {"wrong": [], "expired": [], "commands": {}}


def test_today_action_mailbox_persistence_failure_opens_clean_route_without_mutation():
    result = run_worker_scenario(
        """
  state.failTodayCommandPut=true;
  state.openedClient={id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today'};
  const outcome=await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890')
    .then(()=> 'safe', error=>error.message);
  process.stdout.write(JSON.stringify({outcome,opened:state.opened,commands:state.todayCommands}));
"""
    )

    assert result == {
        "outcome": "safe",
        "opened": ["https://forge.example/dashboard/#/my-work/today"],
        "commands": {},
    }


def test_private_data_purge_removes_pending_today_action_handoffs():
    result = run_worker_scenario(
        """
  state.openedClient={id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today'};
  await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890');
  await dispatchMessage({type:'stackchain-purge-outbox'}, [{postMessage:()=>{}}]);
  const messages=[];
  await dispatchMessage({type:'stackchain-claim-today-action'}, [], {id:'cold-client',url:'https://forge.example/dashboard/#/my-work/today',postMessage:message=>messages.push(message)});
  process.stdout.write(JSON.stringify({messages,commands:state.todayCommands}));
"""
    )

    assert result == {"messages": [], "commands": {}}


def test_finish_today_lock_screen_action_forwards_only_its_opaque_token():
    result = run_worker_scenario(
        """
  state.clientMessages=[];
  state.clientList=[{
    url:'https://forge.example/dashboard/#/my-work/today',
    postMessage:message=>state.clientMessages.push(message),
    focus:async()=>state.focused.push('today'),
  }];
  await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["clientMessages"] == [{
        "type": "stackchain-today-timer-action",
        "action": "complete",
        "actionToken": "opaque-token-1234567890",
    }]


def test_break_resume_action_forwards_opaque_token_to_reject_stale_breaks():
    result = run_worker_scenario(
        """
  state.clientMessages=[];
  state.clientList=[{
    url:'https://forge.example/dashboard/#/my-work/today',
    postMessage:message=>state.clientMessages.push(message),
    focus:async()=>state.focused.push('today'),
  }];
  await dispatchNotificationClick('#/my-work/today','resume-today',null,'stackchain-today-session',null,'opaque-token-1234567890');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["clientMessages"] == [{
        "type": "stackchain-today-timer-action",
        "action": "resume",
        "actionToken": "opaque-token-1234567890",
    }]


def test_background_mutation_abort_also_cancels_stalled_csrf_lookup():
    result = run_worker_scenario(
        """
  let mutations=0;
  context.fetch=(request, options={})=>{
    if(String(request).includes('/api/v1/session')){
      return new Promise((resolve,reject)=>options.signal?.addEventListener('abort',()=>reject(new Error('aborted')), {once:true}));
    }
    mutations+=1;
    return Promise.resolve(new Response('{}',{status:200,headers:{'Content-Type':'application/json'}}));
  };
  const controller=new AbortController();
  const request=context.self.__testWithSessionCsrf(() =>
    context.self.__testFetchJson('/dashboard/api/v1/repos/o/r/issues',{method:'POST',signal:controller.signal})
  ).then(()=> 'completed', error=>error.message);
  controller.abort();
  const outcome=await Promise.race([request,new Promise(resolve=>setTimeout(()=>resolve('blocked'),40))]);
  process.stdout.write(JSON.stringify({outcome,mutations}));
  process.exit(0);
"""
    )

    assert result == {"outcome": "aborted", "mutations": 0}


def test_device_purge_message_stops_worker_outbox_and_acknowledges_completion():
    result = run_worker_scenario(
        """
  const replies = [];
  await dispatchMessage({type:'stackchain-purge-outbox'}, [{postMessage: value => replies.push(value)}]);
  process.stdout.write(JSON.stringify({state,replies}));
"""
    )

    assert result["state"]["outboxPurges"] == 1
    assert result["state"]["deletedDatabases"] == [
        "stackchain-background-outbox-v1",
        "stackchain-offline-work-v2",
        "stackchain-unfiled-captures-v1",
        "stackchain-voice-transcripts-v1",
        "stackchain-search-reply-drafts-v1",
        "stackchain-conversation-reply-drafts-v1",
        "stackchain-today-action-mailbox-v1",
    ]
    assert result["replies"] == [{"ok": True}]


def test_opted_in_background_sync_notifies_privately_and_receipt_tap_focuses_route():
    result = run_worker_scenario(
        """
  state.receiptLogin = 'timmy';
  state.flushResult = {login:'timmy', receipts:[
    {id:'capture-1',status:'confirmed',kind:'issue',route:'#/my-work/issue/stackchain/api/44'},
    {id:'bad-1',status:'attention',kind:'message',route:'#/my-work/drafts'},
    {id:'review-1',status:'authorization',kind:'message',route:'#/my-work/drafts'},
  ]};
  state.clientList = [{url:'https://forge.example/dashboard/', navigate:async function(url){ this.url=url; }, focus:async function(){ state.focused.push(this.url); }}];
  await dispatchSync('stackchain-issue-outbox-v1');
  await dispatchNotificationClick('#/my-work/issue/stackchain/api/44');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["notifications"] == [
        {
            "title": "Queued issue created",
            "options": {
                "body": "Tap to open it in Stackchain.",
                "tag": "stackchain-delivery-capture-1",
                "data": {"route": "#/my-work/issue/stackchain/api/44"},
            },
        },
        {
            "title": "Queued work needs attention",
            "options": {
                "body": "Tap to review it in Drafts.",
                "tag": "stackchain-delivery-bad-1",
                "data": {"route": "#/my-work/drafts"},
            },
        },
        {
            "title": "Queued review needs authorization",
            "options": {
                "body": "Tap to authorize it in the Delivery center.",
                "tag": "stackchain-delivery-review-1",
                "data": {"route": "#/my-work/drafts"},
            },
        },
    ]
    assert result["focused"] == [
        "https://forge.example/dashboard/#/my-work/issue/stackchain/api/44"
    ]
    assert result["opened"] == []
    assert result["notificationClosed"] is True


def test_created_issue_receipt_tap_opens_the_confirmed_canonical_gitea_url():
    result = run_worker_scenario(
        """
  const issueUrl = 'https://forge.example/git/stackchain/api/issues/44';
  state.receiptLogin = 'timmy';
  state.flushResult = {login:'timmy', receipts:[
    {id:'capture-1',status:'confirmed',kind:'issue',url:issueUrl},
  ]};
  state.clientList = [{url:'https://forge.example/dashboard/', navigate:async function(url){ this.url=url; }, focus:async function(){ state.focused.push(this.url); }}];
  await dispatchSync('stackchain-issue-outbox-v1');
  await dispatchNotificationClick('', '', null, 'stackchain-delivery-capture-1', issueUrl);
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["notifications"] == [{
        "title": "Queued issue created",
        "options": {
            "body": "Tap to open it in Stackchain.",
            "tag": "stackchain-delivery-capture-1",
            "data": {"url": "https://forge.example/git/stackchain/api/issues/44"},
        },
    }]
    assert result["focused"] == [
        "https://forge.example/git/stackchain/api/issues/44"
    ]
    assert result["opened"] == []
    assert result["notificationClosed"] is True


def test_inbound_push_renders_generic_update_and_tap_opens_existing_workflow():
    result = run_worker_scenario(
        """
  await dispatchPush({
    title:'New work update', body:'Tap to review it in Stackchain.',
    tag:'stackchain-update-42', route:'#/my-work/update/42', notification_id:42,
    repository:'must-not-render',
  });
  await dispatchNotificationClick('#/my-work/update/42');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["notifications"] == [{
        "title": "New work update",
        "options": {
            "body": "Tap to review it in Stackchain.",
            "tag": "stackchain-update-42",
            "actions": [
                {"action": "mark-read", "title": "Mark read"},
                {"action": "tomorrow", "title": "Tomorrow"},
            ],
            "data": {
                "route": "#/my-work/update/42",
                "notificationId": 42,
            },
        },
    }]
    assert result["opened"] == [
        "https://forge.example/dashboard/#/my-work/update/42"
    ]
    assert "must-not-render" not in json.dumps(result["notifications"])


def test_start_day_push_is_private_and_prepare_action_opens_cached_launch_route():
    result = run_worker_scenario(
        """
  await dispatchPush({
    title:'must-not-render', body:'private/repo#42',
    tag:'stackchain-start-day-2026-08-20', route:'#/my-work/start-day',
    plan_date:'2026-08-20',
  });
  await dispatchNotificationClick(
    '#/my-work/start-day','prepare-today',null,'stackchain-start-day-2026-08-20'
  );
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["notifications"] == [{
        "title": "Your planned day is ready",
        "options": {
            "body": "Open Stackchain to prepare Today.",
            "tag": "stackchain-start-day-2026-08-20",
            "actions": [{"action": "prepare-today", "title": "Prepare Today"}],
            "data": {"route": "#/my-work/start-day"},
        },
    }]
    assert result["opened"] == [
        "https://forge.example/dashboard/#/my-work/start-day"
    ]
    assert "private/repo" not in json.dumps(result["notifications"])


def test_update_digest_push_opens_unread_inbox_without_item_actions_or_private_copy():
    result = run_worker_scenario(
        """
  await dispatchPush({
    title:'must-not-render', body:'private details must-not-render',
    tag:'stackchain-update-digest', route:'#/my-work/updates', update_count:7,
  });
  await dispatchNotificationClick('#/my-work/updates');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["notifications"] == [{
        "title": "7 new work updates",
        "options": {
            "body": "Tap to review them in Stackchain.",
            "tag": "stackchain-update-digest",
            "data": {"route": "#/my-work/updates"},
        },
    }]
    assert result["opened"] == [
        "https://forge.example/dashboard/#/my-work/updates"
    ]
    assert "must-not-render" not in json.dumps(result["notifications"])


def test_deadline_digest_push_offers_protect_today_and_snooze_without_rendering_private_copy():
    result = run_worker_scenario(
        """
  await dispatchPush({
    title:'must-not-render', body:'private details must-not-render',
    tag:'stackchain-deadline-digest-2026-08-13', route:'#/my-work/agenda',
    protect_route:'#/my-work/agenda/protect-today', deadline_count:3,
  });
  await dispatchNotificationClick('#/my-work/agenda/protect-today', 'protect-today', null, 'stackchain-deadline-digest-2026-08-13');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["notifications"] == [{
        "title": "3 deadlines need attention",
        "options": {
            "body": "Open Agenda to review or replan them.",
            "tag": "stackchain-deadline-digest-2026-08-13",
            "actions": [
                {"action": "protect-today", "title": "Protect Today"},
                {"action": "snooze-deadline", "title": "Remind in 1 hour"},
            ],
            "data": {
                "route": "#/my-work/agenda",
                "protectRoute": "#/my-work/agenda/protect-today",
            },
        },
    }]
    assert result["opened"] == [
        "https://forge.example/dashboard/#/my-work/agenda/protect-today"
    ]
    assert "must-not-render" not in json.dumps(result["notifications"])


def test_deadline_digest_snooze_action_records_device_bound_reminder_without_opening_app():
    result = run_worker_scenario(
        """
  await dispatchPush({
    tag:'stackchain-deadline-digest-2026-08-13', route:'#/my-work/agenda',
    protect_route:'#/my-work/agenda/protect-today', deadline_count:1,
  });
  const calls = [];
  context.fetch = async (request, options = {}) => {
    const url = String(request.url || request);
    const headers = new Headers(options.headers || {});
    calls.push({url, method:String(options.method || 'GET'), csrf:headers.get('X-CSRF-Token')});
    if (url.endsWith('/api/v1/session')) {
      return new Response(JSON.stringify({csrf_token:'session-proof'}), {
        status:200, headers:{'Content-Type':'application/json'},
      });
    }
    return new Response(JSON.stringify({snoozed:true}), {
      status:200, headers:{'Content-Type':'application/json'},
    });
  };
  await dispatchNotificationClick('#/my-work/agenda', 'snooze-deadline', null, 'stackchain-deadline-digest-2026-08-13');
  process.stdout.write(JSON.stringify({state,calls}));
"""
    )

    assert result["calls"] == [
        {
            "url": "https://forge.example/dashboard/api/v1/session",
            "method": "GET",
            "csrf": None,
        },
        {
            "url": "https://forge.example/dashboard/api/v1/push-subscription/deadlines/snooze",
            "method": "PATCH",
            "csrf": "session-proof",
        },
    ]
    assert result["state"]["notificationClosed"] is True
    assert result["state"]["opened"] == []


def test_deadline_digest_failed_snooze_opens_agenda_instead_of_losing_reminder():
    result = run_worker_scenario(
        """
  context.fetch = async () => { throw new Error('offline'); };
  await dispatchNotificationClick(
    '#/my-work/agenda', 'snooze-deadline', null,
    'stackchain-deadline-digest-2026-08-13'
  );
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["opened"] == ["https://forge.example/dashboard/#/my-work/agenda"]
    assert result["notificationClosed"] is True


def test_push_mark_read_action_confirms_authenticated_mutation_without_opening_app():
    result = run_worker_scenario(
        """
  const calls = [];
  context.fetch = async (request, options = {}) => {
    const url = String(request.url || request);
    const headers = new Headers(options.headers || {});
    calls.push({url, method:String(options.method || 'GET'), csrf:headers.get('X-CSRF-Token')});
    if (url.endsWith('/api/v1/session')) {
      return new Response(JSON.stringify({csrf_token:'session-proof'}), {
        status:200, headers:{'Content-Type':'application/json'},
      });
    }
    return new Response(JSON.stringify({ok:true}), {
      status:200, headers:{'Content-Type':'application/json'},
    });
  };
  await dispatchNotificationClick('#/my-work/update/42', 'mark-read', 42);
  process.stdout.write(JSON.stringify({state,calls}));
"""
    )

    assert result["calls"] == [
        {
            "url": "https://forge.example/dashboard/api/v1/session",
            "method": "GET",
            "csrf": None,
        },
        {
            "url": "https://forge.example/dashboard/api/v1/notifications/42/read",
            "method": "PATCH",
            "csrf": "session-proof",
        },
    ]
    assert result["state"]["notificationClosed"] is True
    assert result["state"]["opened"] == []
    assert result["state"]["focused"] == []


def test_push_tomorrow_action_defers_privately_without_opening_or_marking_read():
    result = run_worker_scenario(
        """
  const calls = [];
  context.self.__STACKCHAIN_NOW = () => new Date('2026-08-11T18:30:00.000Z');
  context.fetch = async (request, options = {}) => {
    const url = String(request.url || request);
    const headers = new Headers(options.headers || {});
    calls.push({
      url, method:String(options.method || 'GET'), csrf:headers.get('X-CSRF-Token'),
      body:options.body ? JSON.parse(options.body) : null,
    });
    if (url.endsWith('/api/v1/session')) {
      return new Response(JSON.stringify({csrf_token:'session-proof'}), {
        status:200, headers:{'Content-Type':'application/json'},
      });
    }
    return new Response(JSON.stringify({id:42,status:'deferred'}), {
      status:200, headers:{'Content-Type':'application/json'},
    });
  };
  await dispatchNotificationClick('#/my-work/update/42', 'tomorrow', 42, 'stackchain-update-42');
  process.stdout.write(JSON.stringify({state,calls}));
"""
    )

    assert result["calls"] == [
        {
            "url": "https://forge.example/dashboard/api/v1/session",
            "method": "GET",
            "csrf": None,
            "body": None,
        },
        {
            "url": "https://forge.example/dashboard/api/v1/notifications/42/later",
            "method": "PATCH",
            "csrf": "session-proof",
            "body": {"wake_at": "2026-08-12T09:00:00.000Z"},
        },
    ]
    assert result["state"]["notificationClosed"] is True
    assert result["state"]["opened"] == []
    assert all(not call["url"].endswith("/read") for call in result["calls"])


def test_push_mark_read_failure_opens_existing_update_reader():
    result = run_worker_scenario(
        """
  state.clientList = [{
    url:'https://forge.example/dashboard/',
    navigate:async function(url){ this.url=url; },
    focus:async function(){ state.focused.push(this.url); },
  }];
  context.fetch = async (request, options = {}) => {
    const url = String(request.url || request);
    if (url.endsWith('/api/v1/session')) {
      return new Response(JSON.stringify({csrf_token:'session-proof'}), {
        status:200, headers:{'Content-Type':'application/json'},
      });
    }
    return new Response(JSON.stringify({error:'unavailable'}), {
      status:503, headers:{'Content-Type':'application/json'},
    });
  };
  await dispatchNotificationClick('#/my-work/update/42', 'mark-read', 42);
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["focused"] == [
        "https://forge.example/dashboard/#/my-work/update/42"
    ]
    assert result["opened"] == []
    assert result["notificationClosed"] is True


def test_push_mark_read_rejects_mismatched_notification_identity():
    result = run_worker_scenario(
        """
  const calls = [];
  context.fetch = async (request, options = {}) => {
    calls.push({url:String(request.url || request), method:String(options.method || 'GET')});
    return new Response(JSON.stringify({csrf_token:'session-proof'}), {
      status:200, headers:{'Content-Type':'application/json'},
    });
  };
  await dispatchNotificationClick('#/my-work/update/42', 'mark-read', 43);
  process.stdout.write(JSON.stringify({state,calls}));
"""
    )

    assert result["calls"] == []
    assert result["state"].get("notificationClosed") is not True
    assert result["state"]["opened"] == []


def test_push_mark_read_timeout_aborts_request_and_opens_update_reader():
    result = run_worker_scenario(
        """
  state.clientList = [{
    url:'https://forge.example/dashboard/',
    navigate:async function(url){ this.url=url; },
    focus:async function(){ state.focused.push(this.url); },
  }];
  context.fetch = async (request, options = {}) => {
    const url = String(request.url || request);
    if (url.endsWith('/api/v1/session')) {
      return new Response(JSON.stringify({csrf_token:'session-proof'}), {
        status:200, headers:{'Content-Type':'application/json'},
      });
    }
    return new Promise((resolve, reject) => {
      options.signal?.addEventListener('abort', () => {
        state.actionFetchAborted = true;
        reject(new Error('aborted'));
      }, {once:true});
    });
  };
  dispatchNotificationClick('#/my-work/update/42', 'mark-read', 42);
  await new Promise(resolve => setTimeout(resolve, 50));
  process.stdout.write(JSON.stringify(state));
  process.exit(0);
"""
    )

    assert result["actionFetchAborted"] is True
    assert result["focused"] == [
        "https://forge.example/dashboard/#/my-work/update/42"
    ]
    assert result["notificationClosed"] is True


def test_background_mutations_obtain_session_bound_csrf_proof():
    source = WORKER.read_text()

    assert "async function sessionCsrf(signal)" in source
    assert "BASE + 'api/v1/session'" in source
    assert "signal," in source
    assert "headers.set('X-CSRF-Token', csrf)" in source


def test_one_session_bound_csrf_proof_is_reused_for_a_background_drain():
    source = WORKER.read_text()

    assert "async function withSessionCsrf(work)" in source
    assert "batchedCsrfController = new AbortController()" in source
    assert "batchedCsrf = sessionCsrf(batchedCsrfController.signal)" in source
    assert "batchedCsrfController.abort()" in source
    assert "await (batchedCsrf || sessionCsrf(options.signal))" in source
    assert "if (options.signal?.aborted)" in source
    assert "batch: withSessionCsrf" in source


def test_queue_today_ships_atomically_in_a_new_offline_shell():
    source = WORKER.read_text()

    assert "stackchain-dashboard-shell-v127" in source
    assert "BASE + 'static/queue-today.js'" in source


def test_install_precaches_complete_subpath_scoped_app_shell():
    result = run_worker_scenario(
        """
  await dispatch('install');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["skipped"] is True
    assert result["added"][0] == "/dashboard/"
    assert set(result["added"]) == {
        "/dashboard/",
        "/dashboard/manifest.webmanifest",
        "/dashboard/static/dashboard.css",
        "/dashboard/static/dashboard.js",
        "/dashboard/static/icons/stackchain-192.png",
        "/dashboard/static/icons/stackchain-512.png",
        "/dashboard/static/session.js",
        "/dashboard/static/sign-out-review.js",
        "/dashboard/static/feature-loader.js",
        "/dashboard/static/workspace-bootstrap.js",
        "/dashboard/static/conversation-action-hydrator.js",
        "/dashboard/static/security-center.js",
        "/dashboard/static/markdown.js",
        "/dashboard/static/commands.js",
        "/dashboard/static/saved-searches.js",
        "/dashboard/static/search-preview.js",
        "/dashboard/static/search-reply-draft-store.js",
        "/dashboard/static/conversation-reply-draft-store.js",
        "/dashboard/static/conversation-photo-drafts.js",
        "/dashboard/static/search-defer.js",
        "/dashboard/static/widgets.js",
        "/dashboard/static/drafts.js",
        "/dashboard/static/unfiled-captures.js",
        "/dashboard/static/unfiled-draft-sync.js",
        "/dashboard/static/shared-image-capture.js",
        "/dashboard/static/draft-filing-session.js",
        "/dashboard/static/draft-capacity-dialog.js",
        "/dashboard/static/outbox-coordinator.js",
        "/dashboard/static/issue-outbox.js",
        "/dashboard/static/reconnect-outboxes.js",
        "/dashboard/static/authored-outbox.js",
        "/dashboard/static/offline-issue-close.js",
        "/dashboard/static/offline-issue-blocker.js",
        "/dashboard/static/notification-read-outbox.js",
        "/dashboard/static/offline-work.js",
        "/dashboard/static/offline-today.js",
        "/dashboard/static/my-work.js",
        "/dashboard/static/agenda-replan.js",
        "/dashboard/static/agenda-calendar.js",
        "/dashboard/static/protect-today.js",
        "/dashboard/static/notification-undo.js",
        "/dashboard/static/card-planning.js",
        "/dashboard/static/work-selection.js",
        "/dashboard/static/today-work.js",
        "/dashboard/static/today-timer.js",
        "/dashboard/static/today-break.js",
        "/dashboard/static/today-progress.js",
        "/dashboard/static/today-lock-screen.js",
        "/dashboard/static/today-session-sync.js",
        "/dashboard/static/today-recap.js",
        "/dashboard/static/today-wrap-up.js",
        "/dashboard/static/today-summary.js",
        "/dashboard/static/today-handoff.js",
        "/dashboard/static/today-completion.js",
        "/dashboard/static/today-readiness.js",
        "/dashboard/static/comment-next.js",
        "/dashboard/static/update-reply-read-next.js",
        "/dashboard/static/plan-today.js",
        "/dashboard/static/plan-today-readiness.js",
        "/dashboard/static/plan-today-preview.js",
        "/dashboard/static/tomorrow-plan.js",
        "/dashboard/static/week-plan.js",
        "/dashboard/static/today-sync.js",
        "/dashboard/static/today-rollover.js",
        "/dashboard/static/update-ownership.js",
        "/dashboard/static/update-follow-up.js",
        "/dashboard/static/later-work.js",
        "/dashboard/static/later-sync.js",
        "/dashboard/static/later-and-start.js",
        "/dashboard/static/detail-defer.js",
        "/dashboard/static/later-picker.js",
        "/dashboard/static/pick-work.js",
        "/dashboard/static/batch-find-work.js",
        "/dashboard/static/search-batch-plan.js",
        "/dashboard/static/conversation.js",
        "/dashboard/static/comment-actions.js",
        "/dashboard/static/issue-evidence-review.js",
        "/dashboard/static/issue-evidence-editor.js",
        "/dashboard/static/issue-attachment.js",
        "/dashboard/static/issue-filing-review.js",
        "/dashboard/static/issue-sheet.js",
        "/dashboard/static/mobile-issue-detail-nav.js",
        "/dashboard/static/mobile-update-detail-nav.js",
        "/dashboard/static/mobile-review-detail-nav.js",
        "/dashboard/static/mobile-search-preview-nav.js",
        "/dashboard/static/mobile-plan-today-nav.js",
        "/dashboard/static/mobile-find-work-nav.js",
        "/dashboard/static/mobile-pull-refresh.js",
        "/dashboard/static/checklist-conflict.js",
        "/dashboard/static/voice-transcript-store.js",
        "/dashboard/static/voice-issue-capture.js",
        "/dashboard/static/voice-conversation-capture.js",
        "/dashboard/static/create-issue-sheet.js",
        "/dashboard/static/mobile-create-issue-nav.js",
        "/dashboard/static/create-and-start.js",
        "/dashboard/static/assign-and-start.js",
        "/dashboard/static/filed-claim.js",
        "/dashboard/static/queue-today.js",
        "/dashboard/static/pull-sheet.js",
        "/dashboard/static/review-sheet.js",
        "/dashboard/static/release-receipt.js",
        "/dashboard/static/work-route.js",
        "/dashboard/static/task-overlay-history.js",
        "/dashboard/static/context-poller.js",
        "/dashboard/static/live-data-status.js",
        "/dashboard/static/mobile-today-command-bar.js",
        "/dashboard/static/mobile-task-dock.js",
        "/dashboard/static/mobile-first-task.js",
        "/dashboard/static/mobile-work-entry.js",
        "/dashboard/static/mobile-queue-launcher.js",
        "/dashboard/static/mobile-delivery-recovery.js",
        "/dashboard/static/mobile-start-day.js",
        "/dashboard/static/update-triage-session.js",
        "/dashboard/static/update-review-handoff.js",
        "/dashboard/static/update-read-position.js",
        "/dashboard/static/work-detail-position.js",
        "/dashboard/static/update-triage-launcher.js",
        "/dashboard/static/update-triage-gesture.js",
        "/dashboard/static/update-decision-transaction.js",
        "/dashboard/static/agenda-session-launcher.js",
        "/dashboard/static/mobile-launch.js",
        "/dashboard/static/mobile-insights.js",
        "/dashboard/static/mobile-app-shortcuts.js",
        "/dashboard/static/install-app.js",
        "/dashboard/static/private-data-inventory.js",
        "/dashboard/static/private-device-data.js",
        "/dashboard/static/device-storage.js",
        "/dashboard/static/mobile-device-setup.js",
        "/dashboard/static/mobile-search-viewport.js",
        "/dashboard/static/mobile-composer-viewport.js",
        "/dashboard/static/mention-composer.js",
        "/dashboard/static/push-notifications.js",
        "/dashboard/static/issue-filing-receipt.js",
        "/dashboard/static/background-issue-sync.js",
    }


def test_activate_deletes_only_stale_stackchain_caches():
    result = run_worker_scenario(
        """
  await dispatch('activate');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["claimed"] is True
    assert result["deleted"] == ["stackchain-dashboard-old"]


def test_offline_activation_migrates_cached_optional_feature_before_deleting_old_cache():
    result = run_worker_scenario(
        """
  const asset = '/dashboard/feature-issue-capture-test.js';
  state.oldCachedAssets[asset] = 'previous cached feature';
  state.failedAdds = [asset];
  context.self.__testOptionalFeatures.push(asset);
  await dispatch('activate');
  state.failFetch = true;
  const response = await dispatch('fetch', {
    method: 'GET', mode: 'cors',
    url: 'https://forge.example/dashboard/feature-issue-capture-test.js',
  });
  process.stdout.write(JSON.stringify({ body: await response.text(), state }));
"""
    )

    assert result["body"] == "previous cached feature"
    state = result["state"]
    assert state["migrated"] == [
        "/dashboard/feature-issue-capture-test.js"
    ]
    assert state["individuallyAdded"] == []
    assert "/dashboard/feature-issue-capture-test.js" not in state["addAttempts"]
    assert state["activationOrder"] == [
        "put:/dashboard/feature-issue-capture-test.js",
        "delete:stackchain-dashboard-old",
    ]
    assert state["claimed"] is True


def test_successful_optional_feature_fetch_is_cached_for_offline_reopen():
    result = run_worker_scenario(
        """
  const asset = '/dashboard/feature-today-timer-test.js';
  context.self.__testOptionalFeatures.push(asset);
  const response = await dispatch('fetch', {
    method: 'GET', mode: 'cors',
    url: 'https://forge.example/dashboard/feature-today-timer-test.js',
  });
  process.stdout.write(JSON.stringify({ body: await response.text(), state }));
"""
    )

    assert result["body"] == "network"
    assert result["state"]["puts"] == [
        "https://forge.example/dashboard/feature-today-timer-test.js"
    ]


def test_activate_warms_optional_features_without_blocking_siblings_or_claim():
    result = run_worker_scenario(
        """
  state.failedAdds = ['/dashboard/feature-security-center-test.js'];
  context.self.__testOptionalFeatures.push(
    '/dashboard/feature-issue-capture-test.js',
    '/dashboard/feature-security-center-test.js',
    '/dashboard/feature-pull-workflow-test.js',
  );
  await dispatch('activate');
  process.stdout.write(JSON.stringify(state));
"""
    )

    assert result["claimed"] is True
    assert result["individuallyAdded"] == [
        "/dashboard/feature-issue-capture-test.js",
        "/dashboard/feature-pull-workflow-test.js",
    ]


def test_warmed_optional_feature_is_served_from_cache_while_offline():
    result = run_worker_scenario(
        """
  context.self.__testOptionalFeatures.push('/dashboard/feature-issue-capture-test.js');
  state.cachedBody = 'cached feature';
  state.failFetch = true;
  const response = await dispatch('fetch', {
    method: 'GET', mode: 'cors',
    url: 'https://forge.example/dashboard/feature-issue-capture-test.js',
  });
  process.stdout.write(JSON.stringify({ body: await response.text(), state }));
"""
    )

    assert result["body"] == "cached feature"
    assert result["state"]["fetches"] == []


def test_offline_navigation_returns_cached_shell_for_share_target_url():
    result = run_worker_scenario(
        """
  state.failFetch = true;
  state.cachedBody = 'cached dashboard';
  const response = await dispatch('fetch', {
    method: 'GET', mode: 'navigate',
    url: 'https://forge.example/dashboard/?title=Shared&url=https%3A%2F%2Fexample.com',
  });
  process.stdout.write(JSON.stringify({ body: await response.text(), state }));
"""
    )

    assert result["body"] == "cached dashboard"
    assert result["state"]["fetches"] == [
        "https://forge.example/dashboard/?title=Shared&url=https%3A%2F%2Fexample.com"
    ]


def test_expired_offline_lease_purges_private_worker_data_and_refuses_cached_shell():
    result = run_worker_scenario(
        """
  await dispatchMessage({type:'stackchain-session-lease', expiresAt:1, idleExpiresAt:1});
  state.failFetch = true;
  state.cachedBody = 'private cached dashboard';
  const response = await dispatch('fetch', {
    method:'GET', mode:'navigate', url:'https://forge.example/dashboard/',
  });
  process.stdout.write(JSON.stringify({
    status: response.status,
    body: await response.text(),
    state,
  }));
"""
    )

    assert result["status"] == 401
    assert result["body"] == "Your Stackchain session expired. Reconnect and sign in."
    assert result["state"]["outboxPurges"] == 1
    assert result["state"]["deletedDatabases"] == [
        "stackchain-background-outbox-v1",
        "stackchain-offline-work-v2",
        "stackchain-unfiled-captures-v1",
        "stackchain-voice-transcripts-v1",
        "stackchain-search-reply-drafts-v1",
        "stackchain-conversation-reply-drafts-v1",
        "stackchain-today-action-mailbox-v1",
    ]
    assert result["state"]["deleted"] == ["stackchain-dashboard-old"]
    assert "private cached dashboard" not in result["body"]


def test_expired_idle_lease_locks_cached_shell_without_purging_recoverable_work():
    result = run_worker_scenario(
        """
  state.clientList = [{postMessage: message => { state.clientMessage = message; }}];
  await dispatchMessage({
    type:'stackchain-session-lease',
    expiresAt:Math.floor(Date.now() / 1000) + 3600,
    idleExpiresAt:1,
  });
  state.failFetch = true;
  state.cachedBody = 'private cached dashboard';
  const response = await dispatch('fetch', {
    method:'GET', mode:'navigate', url:'https://forge.example/dashboard/',
  });
  process.stdout.write(JSON.stringify({
    state, status:response.status, body:await response.text(),
    cacheControl:response.headers.get('Cache-Control'),
  }));
"""
    )

    assert result["status"] == 401
    assert result["body"] == "Your Stackchain session is locked. Reconnect and sign in."
    assert result["cacheControl"] == "no-store"
    assert result["state"]["outboxPurges"] == 0
    assert result["state"]["deleted"] == []
    assert result["state"]["clientMessage"] == {"type": "stackchain-session-idle"}
    assert "private cached dashboard" not in result["body"]


def test_stalled_navigation_is_aborted_and_returns_cached_shell_within_deadline():
    result = run_worker_scenario(
        """
  state.stallFetch = true;
  state.cachedBody = 'cached dashboard';
  const response = await Promise.race([
    dispatch('fetch', {
      method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/',
    }),
    new Promise(resolve => setTimeout(() => resolve(null), 80)),
  ]);
  process.stdout.write(JSON.stringify({
    timedOut: response === null,
    body: response ? await response.text() : null,
    state,
  }));
"""
    )

    assert result["timedOut"] is False
    assert result["body"] == "cached dashboard"
    assert result["state"]["fetchAborted"] is True
    assert result["state"]["puts"] == []


def test_stalled_navigation_without_cached_shell_returns_deterministic_504():
    result = run_worker_scenario(
        """
  state.stallFetch = true;
  const response = await Promise.race([
    dispatch('fetch', {
      method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/',
    }),
    new Promise(resolve => setTimeout(() => resolve(null), 80)),
  ]);
  process.stdout.write(JSON.stringify({
    status: response?.status || null,
    body: response ? await response.text() : null,
    contentType: response?.headers.get('Content-Type') || null,
    state,
  }));
"""
    )

    assert result["status"] == 504
    assert result["body"] == "Stackchain is offline and the dashboard is not cached yet. Reconnect and try again."
    assert result["contentType"] == "text/plain; charset=utf-8"
    assert result["state"]["fetchAborted"] is True


def test_navigation_deadline_wins_when_fetch_ignores_abort_and_prevents_late_cache_write():
    result = run_worker_scenario(
        """
  state.lateFetch = true;
  state.cachedBody = 'cached dashboard';
  const response = await Promise.race([
    dispatch('fetch', {
      method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/',
    }),
    new Promise(resolve => setTimeout(() => resolve(null), 35)),
  ]);
  const body = response ? await response.text() : null;
  await new Promise(resolve => setTimeout(resolve, 60));
  process.stdout.write(JSON.stringify({timedOut: response === null, body, state}));
"""
    )

    assert result["timedOut"] is False
    assert result["body"] == "cached dashboard"
    assert result["state"]["fetchAborted"] is True
    assert result["state"]["puts"] == []


def test_redirected_login_navigation_does_not_replace_cached_dashboard_shell():
    result = run_worker_scenario(
        """
  state.fetchRedirected = true;
  const response = await dispatch('fetch', {
    method: 'GET', mode: 'navigate',
    url: 'https://forge.example/dashboard/?title=Shared',
  });
  process.stdout.write(JSON.stringify({ status: response.status, state }));
"""
    )

    assert result["status"] == 200
    assert result["state"]["puts"] == []


def test_cross_origin_navigation_is_not_intercepted_or_cached():
    result = run_worker_scenario(
        """
  const response = await dispatch('fetch', {
    method: 'GET', mode: 'navigate', url: 'https://outside.example/page',
  });
  process.stdout.write(JSON.stringify({ intercepted: response !== null, state }));
"""
    )

    assert result["intercepted"] is False
    assert result["state"]["fetches"] == []
    assert result["state"]["puts"] == []


@pytest.mark.parametrize("status", [500, 502, 503, 504])
def test_server_outage_navigation_returns_working_cached_shell_without_replacing_it(status):
    result = run_worker_scenario(
        f"""
  state.fetchStatus = {status};
  state.cachedBody = 'cached dashboard';
  const response = await dispatch('fetch', {{
    method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/',
  }});
  process.stdout.write(JSON.stringify({{ status: response.status, body: await response.text(), state }}));
"""
    )

    assert result["status"] == 200
    assert result["body"] == "cached dashboard"
    assert result["state"]["puts"] == []


def test_server_outage_without_cached_shell_preserves_network_error():
    result = run_worker_scenario(
        """
  state.fetchStatus = 503;
  const response = await dispatch('fetch', {
    method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/',
  });
  process.stdout.write(JSON.stringify({ status: response.status, body: await response.text(), state }));
"""
    )

    assert result["status"] == 503
    assert result["body"] == "network"
    assert result["state"]["puts"] == []


def test_client_error_navigation_is_not_hidden_by_cached_shell():
    result = run_worker_scenario(
        """
  state.fetchStatus = 401;
  state.cachedBody = 'cached dashboard';
  const response = await dispatch('fetch', {
    method: 'GET', mode: 'navigate', url: 'https://forge.example/dashboard/',
  });
  process.stdout.write(JSON.stringify({ status: response.status, body: await response.text(), state }));
"""
    )

    assert result["status"] == 401
    assert result["body"] == "network"
    assert result["state"]["puts"] == []
