import json import subprocess from pathlib import Path import pytest from tests.dashboard_bundle import dashboard DRAFTS = Path(__file__).parents[1] / "frontend" / "drafts.js" def run_node(script: str): result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) return json.loads(result.stdout) def test_draft_inbox_discovers_existing_formats_orders_activity_and_ignores_corruption(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values = new Map([ ['stackchain.issue-capture.v1', JSON.stringify({{repository:'stackchain/api',title:'Ship recovery',body:'Detailed plan',labelIds:[]}})], ['stackchain.issue-comment.v1:stackchain/api#17', 'Please add a regression test'], ['stackchain.pull-comment.v1:stackchain/web#9', 'Looks good on mobile'], ['stackchain.update-reply.v1.44', 'I can take this'], ['stackchain.review-draft.v1:stackchain/web#12@abc123', JSON.stringify({{notes:{{'app.js':'Handle offline state'}},comments:[],summary:'Needs one fix',decision:'request_changes'}})], ['stackchain.issue-content.v1:stackchain/api#18', '{{broken-json'], ['stackchain.issue-comment.v1:stackchain/api#20:operation', 'not-a-draft'], ]); const storage = {{ get length() {{ return values.size; }}, key: index => Array.from(values.keys())[index] || null, getItem: key => values.has(key) ? values.get(key) : null, setItem: (key, value) => values.set(key, value), removeItem: key => values.delete(key), }}; let clock = 1000; const inbox = createDraftInbox({{storage, now:() => ++clock}}); const first = inbox.list(); values.set('stackchain.issue-comment.v1:stackchain/api#17', 'Newest comment text'); const second = inbox.list(); process.stdout.write(JSON.stringify({{ kinds:first.map(item => item.kind).sort(), newest:second[0], count:second.length, }})); """ output = run_node(script) assert output["kinds"] == ["issue-comment", "new-issue", "pull-comment", "review", "update-reply"] assert output["count"] == 5 assert output["newest"]["kind"] == "issue-comment" assert output["newest"]["repository"] == "stackchain/api" assert output["newest"]["number"] == 17 assert output["newest"]["preview"] == "Newest comment text" assert output["newest"]["route"] == {"kind": "issue", "repository": "stackchain/api", "number": 17} def test_draft_discard_removes_only_selected_content_and_operation_identity(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values = new Map([ ['stackchain.issue-comment.v1:stackchain/api#17', 'One'], ['stackchain.issue-comment.v1:stackchain/api#17:operation', 'operation-one'], ['stackchain.pull-comment.v1:stackchain/web#9', 'Two'], ]); const storage = {{ get length() {{ return values.size; }}, key:i => Array.from(values.keys())[i] || null, getItem:key => values.has(key) ? values.get(key) : null, setItem:(key,value) => values.set(key,value), removeItem:key => values.delete(key), }}; const inbox = createDraftInbox({{storage, now:() => 1000}}); const selected = inbox.list().find(item => item.kind === 'issue-comment'); inbox.discard(selected.id); process.stdout.write(JSON.stringify({{remaining:inbox.list(), keys:Array.from(values.keys()).sort()}})); """ output = run_node(script) assert [item["kind"] for item in output["remaining"]] == ["pull-comment"] assert "stackchain.issue-comment.v1:stackchain/api#17" not in output["keys"] assert "stackchain.issue-comment.v1:stackchain/api#17:operation" not in output["keys"] assert "stackchain.pull-comment.v1:stackchain/web#9" in output["keys"] def test_draft_inbox_recovers_issue_edit_and_review_content_without_exposing_empty_records(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values = new Map([ ['stackchain.issue-content.v1:stackchain/api#18', JSON.stringify({{title:'Clarify acceptance',body:'Describe mobile flow',expectedUpdatedAt:'2026-08-07T10:00:00Z'}})], ['stackchain.review-draft.v1:stackchain/web#12@abc123', JSON.stringify({{notes:{{}},comments:[],summary:'',decision:'approve'}})], ['stackchain.pull-comment.v1:stackchain/web#10', ' '], ]); const storage = {{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; const drafts = createDraftInbox({{storage,now:()=>42}}).list(); process.stdout.write(JSON.stringify(drafts)); """ output = run_node(script) assert len(output) == 1 assert output[0]["kind"] == "issue-edit" assert output[0]["preview"] == "Clarify acceptance — Describe mobile flow" assert output[0]["route"] == {"kind": "issue", "repository": "stackchain/api", "number": 18} def test_draft_inbox_expands_issue_outbox_items_with_actionable_states(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values = new Map([['stackchain.issue-outbox.v1', JSON.stringify({{version:1,items:[ {{id:'queued-1',repository:'stackchain/api',title:'Offline capture',body:'Context',status:'queued',queuedAt:100}}, {{id:'attention-2',repository:'stackchain/web',title:'Fix labels',body:'Details',status:'attention',error:'Unknown label',queuedAt:200}}, ]}})]]); const storage = {{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; const drafts = createDraftInbox({{storage,now:()=>300}}).list(); process.stdout.write(JSON.stringify(drafts)); """ output = run_node(script) assert [item["outbox_id"] for item in output] == ["attention-2", "queued-1"] assert [item["status"] for item in output] == ["attention", "queued"] assert output[0]["label"] == "Needs attention" assert output[1]["label"] == "Queued issue" def test_draft_inbox_identifies_account_bound_checklist_conflicts_for_review(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const record = {{version:2,items:[{{ id:'check-1',kind:'issue-content',ownerLogin:'timmy',status:'attention',repository:'o/r',number:7, title:'Ship',baseBody:'- [ ] Build',body:'- [x] Build',expectedUpdatedAt:'old',error:'Issue changed',queuedAt:12, }}]}}; const values = new Map([['stackchain.authored-outbox.v1', JSON.stringify(record)]]); const storage = {{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const owned = createDraftInbox({{storage,getCurrentLogin:()=>'timmy'}}).list()[0]; const other = createDraftInbox({{storage,getCurrentLogin:()=>'alexander'}}).list()[0]; process.stdout.write(JSON.stringify({{owned,other}})); """ output = run_node(script) assert output["owned"]["label"] == "Checklist conflict" assert output["owned"]["checklist_conflict"] is True assert output["owned"]["quarantined"] is False assert output["other"]["checklist_conflict"] is False assert output["other"]["quarantined"] is True def test_draft_inbox_exposes_queued_review_with_review_route_and_actionable_state(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values=new Map([['stackchain.authored-outbox.v1',JSON.stringify({{version:2,items:[{{ id:'review-1',kind:'pull-review',repository:'stackchain/web',number:8,body:'Looks good', decision:'approve',expectedHeadSha:'abc',comments:[{{path:'app.js',body:'Nice',new_position:4}}],ownerLogin:'timmy',status:'queued',queuedAt:200 }}]}})]]); const storage={{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const item=createDraftInbox({{storage,getCurrentLogin:()=>'timmy'}}).list()[0]; process.stdout.write(JSON.stringify(item)); """ output = run_node(script) assert output["label"] == "Queued review" assert output["outbox_kind"] == "pull-review" assert output["title"] == "stackchain/web#8" assert output["route"] == {"kind": "review", "repository": "stackchain/web", "number": 8} assert output["copy_text"] == "Looks good\n\nInline feedback\napp.js · new line 4: Nice" assert output["quarantined"] is False def test_draft_inbox_exposes_held_review_as_awaiting_foreground_authorization(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values=new Map([['stackchain.authored-outbox.v1',JSON.stringify({{version:2,items:[{{ id:'review-1',kind:'pull-review',repository:'stackchain/web',number:8,body:'Looks good', decision:'approve',expectedHeadSha:'abc',comments:[],ownerLogin:'timmy', status:'authorization',error:'Fresh authorization required',queuedAt:200 }}]}})]]); const storage={{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const inbox=createDraftInbox({{storage,getCurrentLogin:()=>'timmy'}}); process.stdout.write(JSON.stringify({{item:inbox.list()[0],partition:inbox.partition()}})); """ output = run_node(script) assert output["item"]["status"] == "authorization" assert output["item"]["label"] == "Review awaiting authorization" assert output["item"]["authorization_required"] is True assert output["partition"]["counts"]["authorization"] == 1 assert output["partition"]["retryable"] == [] def test_draft_inbox_exposes_queued_issue_closure_as_awaiting_authorization(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values=new Map([['stackchain.authored-outbox.v1',JSON.stringify({{version:2,items:[{{ id:'close-1',kind:'issue-close',repository:'stackchain/dashboard',number:27,body:'', ownerLogin:'timmy',status:'queued',queuedAt:200 }}]}})]]); const storage={{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const inbox=createDraftInbox({{storage,getCurrentLogin:()=>'timmy'}}); const item=inbox.list()[0]; process.stdout.write(JSON.stringify({{item,partition:inbox.partition()}})); """ output = run_node(script) item = output["item"] assert item["label"] == "Awaiting authorization" assert item["status"] == "authorization" assert item["authorization_required"] is True assert item["outbox_kind"] == "issue-close" assert item["title"] == "stackchain/dashboard#27" assert item["route"] == {"kind": "issue", "repository": "stackchain/dashboard", "number": 27} assert item["quarantined"] is False assert output["partition"]["counts"] == { "waiting": 0, "sending": 0, "attention": 0, "authorization": 1 } assert output["partition"]["retryable"] == [] def test_draft_inbox_exposes_created_issue_waiting_for_create_and_start_continuation(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values = new Map([['stackchain.issue-outbox.v1', JSON.stringify({{version:3,items:[{{ id:'continue-1',operationId:'continue-1',repository:'stackchain/dashboard', title:'Resume this issue',body:'',ownerLogin:'timmy',status:'completion',queuedAt:200, completionIntent:'create-and-start',deliveredIssue:{{repository:'stackchain/dashboard',number:389,title:'Resume this issue'}} }}]}})]]); const storage = {{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null}}; const item = createDraftInbox({{storage,getCurrentLogin:()=>'timmy'}}).list()[0]; process.stdout.write(JSON.stringify(item)); """ output = run_node(script) assert output["status"] == "completion" assert output["label"] == "Created · ready to start" assert output["continuation"] is True assert output["quarantined"] is False def test_draft_inbox_exposes_uncertain_delivery_for_explicit_user_verification(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values = new Map([['stackchain.authored-outbox.v1', JSON.stringify({{version:2,items:[ {{id:'uncertain',kind:'issue-comment',repository:'o/r',number:7,body:'Possibly posted',ownerLogin:'timmy',status:'attention',deliveryState:'uncertain',error:'Verify it was not posted before retrying.',queuedAt:200}} ]}})]]); const storage = {{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const drafts = createDraftInbox({{storage,getCurrentLogin:()=>'timmy'}}).list(); process.stdout.write(JSON.stringify(drafts[0])); """ output = run_node(script) assert output["delivery_state"] == "uncertain" assert output["label"] == "Verify delivery" def test_draft_inbox_marks_mismatched_and_legacy_outbox_content_copy_only(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values = new Map([ ['stackchain.issue-outbox.v1', JSON.stringify({{version:2,items:[ {{id:'other',repository:'o/r',title:'Other issue',body:'Private context',ownerLogin:'alexander',status:'queued'}}, {{id:'legacy',repository:'o/r',title:'Legacy issue',body:'Old context',status:'queued'}} ]}})], ['stackchain.authored-outbox.v1', JSON.stringify({{version:2,items:[ {{id:'mine',kind:'issue-comment',repository:'o/r',number:2,body:'My comment',ownerLogin:'timmy',status:'queued'}} ]}})], ]); const storage = {{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const drafts = createDraftInbox({{storage,getCurrentLogin:()=>'timmy'}}).list(); process.stdout.write(JSON.stringify(drafts)); """ output = run_node(script) by_id = {item["outbox_id"]: item for item in output} assert by_id["other"]["quarantined"] is True assert by_id["other"]["ownership"] == "Queued by alexander — current account is timmy" assert by_id["other"]["copy_text"] == "Other issue\n\nPrivate context" assert by_id["legacy"]["ownership"] == "Queued by an unknown account — current account is timmy" assert by_id["mine"]["quarantined"] is False def test_draft_inbox_partitions_delivery_center_and_counts_only_safe_waiting_retries(): script = f""" const createDraftInbox = require({json.dumps(str(DRAFTS))}); const values = new Map([ ['stackchain.issue-comment.v1:stackchain/api#17', 'Unfinished comment'], ['stackchain.issue-outbox.v1', JSON.stringify({{version:3,items:[ {{id:'waiting',repository:'o/r',title:'Waiting issue',body:'Body',ownerLogin:'timmy',status:'queued',queuedAt:100,lastAttemptAt:90,lastAttemptError:'Network unavailable'}}, {{id:'sending',repository:'o/r',title:'Sending issue',body:'Body',ownerLogin:'timmy',status:'sending',queuedAt:200}}, {{id:'attention',repository:'o/r',title:'Broken issue',body:'Body',ownerLogin:'timmy',status:'attention',queuedAt:300}}, {{id:'other-user',repository:'o/r',title:'Private issue',body:'Body',ownerLogin:'alexander',status:'queued',queuedAt:400}} ]}})], ['stackchain.authored-outbox.v1', JSON.stringify({{version:2,items:[ {{id:'uncertain',kind:'issue-comment',repository:'o/r',number:4,body:'Maybe sent',ownerLogin:'timmy',status:'attention',deliveryState:'uncertain',queuedAt:500}} ]}})], ]); const storage = {{get length(){{return values.size}},key:i=>Array.from(values.keys())[i]||null,getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const inbox = createDraftInbox({{storage,getCurrentLogin:()=>'timmy'}}); const partition=inbox.partition(); process.stdout.write(JSON.stringify({{...partition,labels:partition.deliveries.map(inbox.deliveryLabel)}})); """ output = run_node(script) assert len(output["drafts"]) == 1 assert len(output["deliveries"]) == 5 assert output["counts"] == {"waiting": 2, "sending": 1, "attention": 2, "authorization": 0} assert output["labels"] == [ "Needs attention", "Identity protected", "Needs attention", "Sending", "Queued for sync" ] assert [item["outbox_id"] for item in output["retryable"]] == ["waiting"] waiting = next(item for item in output["deliveries"] if item["outbox_id"] == "waiting") assert waiting["last_attempt_at"] == 90 assert waiting["last_attempt_error"] == "Network unavailable" @pytest.mark.anyio async def test_mobile_dashboard_exposes_touch_safe_draft_recovery_lane(): html = await dashboard() assert '' in html assert 'data-work-filter="draft"' in html assert 'data-work-count="draft"' in html assert 'class="draft-resume"' in html assert 'class="draft-discard"' in html assert 'Discard this unfinished draft?' in html assert '.draft-actions button, .draft-filing-session-actions button, .agenda-replan-actions button { min-height:44px;' in html assert 'createDraftInbox({ storage: localStorage' in html assert "captureDraft.repository && !issueCaptureRepositories.includes(captureDraft.repository)" in html assert "Verified not posted — retry" in html assert "const closureOutbox = item.outbox_kind === 'issue-close';" in html assert "item.kind === 'authored-outbox' && closureOutbox" in html assert "payload.detail?.message" in html assert "error.code = payload.detail?.code" in html @pytest.mark.anyio async def test_mobile_dashboard_renders_delivery_center_separately_and_retries_waiting_work(): html = await dashboard() assert 'class="delivery-center"' in html assert 'id="retry-waiting-deliveries"' in html assert 'Waiting ' in html assert 'Sending ' in html assert 'Needs attention ' in html assert 'Authorize ' in html assert "const deliveryCenter = draftInbox.partition(displayedDrafts);" in html assert "await Promise.all([issueOutbox.flush(activeFlushLogin), authoredOutbox.flush(activeFlushLogin)])" in html assert "deliveryCenter.retryable.length" in html assert "draftInbox.deliveryLabel(item)" in html assert "if (item.status === 'sending') return 'Sending'" in DRAFTS.read_text() assert "item.last_attempt_error ? 'Last attempt '" in html assert '.delivery-center { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;' in html assert '.delivery-center button { min-height:44px;' in html @pytest.mark.anyio async def test_mobile_dashboard_requires_an_explicit_authorize_and_close_gesture(): html = await dashboard() assert 'class="draft-authorize"' in html assert '>Authorize & close' in html assert "list.querySelectorAll('.draft-authorize')" in html assert "authoredOutbox.retry(item.outbox_id, activeFlushLogin)" in html assert "if (item.status === 'authorization') return 'Awaiting authorization'" in DRAFTS.read_text() @pytest.mark.anyio async def test_dashboard_only_flushes_account_bound_outboxes_after_a_fresh_identity_snapshot(): html = await dashboard() assert "let confirmedOwnerLogin = '';" in html assert "let activeFlushLogin = '';" in html assert "getOwnerLogin: () => confirmedOwnerLogin" in html assert "getCurrentLogin: () => activeFlushLogin" in html assert "const contextIdentityFresh = !snapshot.context.error && !contextFreshness?.stale &&" in html assert "!contextFreshness?.degraded && !contextFreshness?.revalidating;" in html assert "activeFlushLogin = contextIdentityFresh ? String(snapshot.context.user?.login || '').trim() : '';" in html assert "issueOutbox.flush(activeFlushLogin)" in html assert "authoredOutbox.flush(activeFlushLogin)" in html assert "activeFlushLogin = '';" in html assert 'class="draft-copy"' in html assert "navigator.clipboard.writeText(item.copy_text)" in html assert "item.quarantined" in html