feat: keep create and start durable (#389)
CI / lint (pull_request) Successful in 40s
CI / build-release (pull_request) Successful in 4s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-09 09:41:10 +00:00
1 parent f92794f571
commit 34664edb8b
17 files changed
+296 -36

No files matched your search

+32
View File
@@ -64,6 +64,38 @@ const fetchJson = async (url, options = {{}}) => {{
}
def test_closed_app_sync_retains_created_issue_for_create_and_start_recovery():
script = f"""
const createBackgroundIssueSync = require({json.dumps(str(SYNC))});
const item = {{
id:'capture-start',operationId:'capture-start',ownerLogin:'timmy',status:'queued',
repository:'stackchain/dashboard',title:'Resume',body:'',labelIds:[],
completionIntent:'create-and-start',
}};
let queued = item;
const state = {{completed:[]}};
const store = {{
claimNext:async () => queued ? (queued=null,{{...item}}) : null,
complete:async (id, deliveredIssue) => state.completed.push({{id,deliveredIssue}}),
release:async()=>{{}},fail:async()=>{{}},countBlocked:async()=>0,
}};
const fetchJson = async url => url === 'api/v1/background-identity' ? {{login:'timmy'}} :
{{repository:'stackchain/dashboard',number:389,title:'Resume'}};
(async()=>{{
await createBackgroundIssueSync({{store,fetchJson}}).flush();
process.stdout.write(JSON.stringify(state));
}})();
"""
output = run_node(script)
assert output["completed"] == [{
"id": "capture-start",
"deliveredIssue": {
"repository": "stackchain/dashboard", "number": 389, "title": "Resume"
},
}]
def test_closed_app_sync_returns_privacy_safe_actionable_delivery_receipts():
script = f"""
const createBackgroundIssueSync = require({json.dumps(str(SYNC))});
+40
View File
@@ -67,6 +67,18 @@ def test_mobile_capture_wires_distinct_create_and_start_intent_into_the_offline_
assert "BASE + 'static/create-and-start.js'" in worker
def test_create_and_start_intent_is_durable_and_has_a_one_tap_relaunch_continuation():
dashboard = DASHBOARD.read_text()
assert "completionIntent: 'create-and-start'" in dashboard
assert "result.completions || []" in dashboard
assert "(result.completions || []).forEach(completion =>" in dashboard
assert "issueOutbox.pendingCompletions(activeFlushLogin)" in dashboard
assert "issueOutbox.completeIntent(completion.id, activeFlushLogin)" in dashboard
assert 'class="draft-continue"' in dashboard
assert "Continue created work" in dashboard
def test_create_and_start_stops_before_session_when_today_sync_cannot_be_queued():
script = f"""
const createCreateAndStart = require({json.dumps(str(CREATE_AND_START))});
@@ -85,3 +97,31 @@ process.stdout.write(JSON.stringify({{result:flow.complete(issue), calls}}));
"result": "sync-unavailable",
"calls": ["Created and saved to Today on this device, but account sync is unavailable."],
}
def test_create_and_start_can_resume_after_today_sync_admission_recovers():
script = f"""
const createCreateAndStart = require({json.dumps(str(CREATE_AND_START))});
const calls=[];let admitted=false;let added=false;
const issue={{kind:'issue',repository:'stackchain/dashboard',number:389}};
const flow=createCreateAndStart({{
todayWork:{{limit:5,read:()=>[],identity:()=> 'issue:stackchain/dashboard:389:',add:()=>{{
const outcome=added?'exists':'added';added=true;return outcome;
}}}},
todaySync:{{enqueue:()=>admitted,flush:()=>calls.push('flush')}},
refresh:()=>calls.push('refresh'),warm:()=>calls.push('warm'),start:()=>calls.push('start'),
announce:message=>calls.push(message),
}});
const first=flow.complete(issue);
admitted=true;
const second=flow.complete(issue);
process.stdout.write(JSON.stringify({{first,second,calls}}));
"""
output = run_node(script)
assert output["first"] == "sync-unavailable"
assert output["second"] == "started"
assert output["calls"][-5:] == [
"refresh", "flush", "warm", "start",
"Created, added to Today, and ready to work.",
]
+20
View File
@@ -121,6 +121,26 @@ process.stdout.write(JSON.stringify(drafts));
assert output[1]["label"] == "Queued issue"
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))});
+84 -2
View File
@@ -68,7 +68,57 @@ process.stdout.write(JSON.stringify({{items:reloaded, stored:JSON.parse(values.g
]
assert [item["operationId"] for item in output["items"]] == ["op-1", "op-2"]
assert all(item["status"] == "queued" for item in output["items"])
assert output["stored"]["version"] == 2
assert output["stored"]["version"] == 3
def test_issue_outbox_persists_create_and_start_intent_and_returns_it_with_confirmation():
script = f"""
const createIssueOutbox = require({json.dumps(str(OUTBOX))});
const values = new Map();
const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}};
const outbox = createIssueOutbox({{
storage, getOwnerLogin:()=> 'timmy', createOperationId:()=> 'create-start-1',
fetchJson:async () => ({{repository:'stackchain/dashboard',number:389,title:'Resume me'}}),
}});
outbox.enqueue({{
repository:'stackchain/dashboard',title:'Resume me',body:'',completionIntent:'create-and-start'
}});
const reloaded = createIssueOutbox({{
storage, fetchJson:async () => ({{repository:'stackchain/dashboard',number:389,title:'Resume me'}})
}});
reloaded.flush('timmy').then(result => process.stdout.write(JSON.stringify({{
persisted:JSON.parse(values.get('stackchain.issue-outbox.v1')),
result,
}})));
"""
output = run_node(script)
assert output["persisted"]["version"] == 3
completion = output["result"]["completions"][0]
assert completion["intent"] == "create-and-start"
assert completion["ownerLogin"] == "timmy"
assert completion["operationId"] == "create-start-1"
assert completion["issue"]["number"] == 389
def test_editing_a_queued_issue_updates_its_completion_intent_without_rekeying_delivery():
script = f"""
const createIssueOutbox = require({json.dumps(str(OUTBOX))});
const values=new Map();let sequence=0;
const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}};
const outbox=createIssueOutbox({{
storage,getOwnerLogin:()=>'timmy',createOperationId:()=> 'intent-' + (++sequence)
}});
const queued=outbox.enqueue({{repository:'o/r',title:'Work',body:''}});
const started=outbox.update(queued.id,{{...queued,completionIntent:'create-and-start'}});
const ordinary=outbox.update(queued.id,{{...started,completionIntent:undefined}});
process.stdout.write(JSON.stringify({{started,ordinary}}));
"""
output = run_node(script)
assert output["started"]["completionIntent"] == "create-and-start"
assert "completionIntent" not in output["ordinary"]
assert output["started"]["operationId"] == output["ordinary"]["operationId"] == "intent-1"
def test_issue_outbox_flushes_sequentially_and_keeps_transient_failures_queued():
@@ -384,13 +434,45 @@ process.stdout.write(JSON.stringify(outbox.list()));
]
def test_page_preserves_background_create_and_start_until_the_matching_account_continues_it():
script = f"""
const createIssueOutbox = require({json.dumps(str(OUTBOX))});
const values=new Map();
const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}};
values.set('stackchain.issue-outbox.v1',JSON.stringify({{version:3,items:[{{
id:'resume',operationId:'resume',ownerLogin:'timmy',status:'queued',
repository:'stackchain/dashboard',title:'Resume',body:'',completionIntent:'create-and-start'
}}]}}));
const outbox=createIssueOutbox({{storage}});
outbox.reconcileBackground([{{
id:'resume',status:'sent',ownerLogin:'timmy',completionIntent:'create-and-start',
deliveredIssue:{{repository:'stackchain/dashboard',number:389,title:'Resume'}}
}}]);
const blocked=outbox.pendingCompletions('alexander');
const pending=outbox.pendingCompletions('timmy');
const retained=outbox.list();
outbox.completeIntent('resume','timmy');
process.stdout.write(JSON.stringify({{blocked,pending,retained,remaining:outbox.list()}}));
"""
output = run_node(script)
assert output["blocked"] == []
assert output["pending"] == [{
"id": "resume", "intent": "create-and-start", "ownerLogin": "timmy",
"operationId": "resume",
"issue": {"repository": "stackchain/dashboard", "number": 389, "title": "Resume"},
}]
assert output["retained"][0]["status"] == "completion"
assert output["remaining"] == []
@pytest.mark.anyio
async def test_mobile_dashboard_queues_offline_captures_and_exposes_outbox_actions():
html = await dashboard()
assert '<script src="static/issue-outbox.js"></script>' in html
assert "const issueOutbox = createIssueOutbox({" in html
assert "await issueOutbox.enqueueDurably(captureDraft)" in html
assert "await issueOutbox.enqueueDurably(durableDraft)" in html
assert "Saving for background delivery…" in html
assert "Saved for next launch; background delivery unavailable." in html
assert "throw new Error('Background Sync unavailable')" in html
+1 -1
View File
@@ -347,5 +347,5 @@ async def test_dashboard_syncs_every_later_change_and_exposes_account_status():
def test_later_sync_ships_atomically_in_the_offline_shell():
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
assert "stackchain-dashboard-shell-v58" in source
assert "stackchain-dashboard-shell-v59" in source
assert "BASE + 'static/later-sync.js'" in source
+1 -1
View File
@@ -137,4 +137,4 @@ def test_markdown_work_bodies_are_mobile_safe_block_containers():
assert ".markdown-content { min-width:0; max-width:100%; overflow-wrap:anywhere;" in css
assert ".markdown-content pre { max-width:100%; overflow-x:auto;" in css
assert ".markdown-content a { min-height:44px;" in css
assert "stackchain-dashboard-shell-v58" in worker
assert "stackchain-dashboard-shell-v59" in worker
+1 -1
View File
@@ -35,4 +35,4 @@ 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-v58" in worker
assert "stackchain-dashboard-shell-v59" in worker
+1 -1
View File
@@ -3290,7 +3290,7 @@ async def test_mobile_issue_capture_warns_before_queuing_a_possible_duplicate():
assert "issueCapture.needsDuplicateAcknowledgement(captureDraft)" in html
assert "issueCapture.acknowledgeDuplicates(captureDraft)" in html
assert html.index("issueCapture.needsDuplicateAcknowledgement(captureDraft)") < html.index(
"issueOutbox.enqueueDurably(captureDraft)"
"issueOutbox.enqueueDurably(durableDraft)"
)
assert ".create-issue-duplicate-card" in html
assert ".create-issue-duplicate-card a" in html and "min-height:44px" in html
+1 -1
View File
@@ -101,5 +101,5 @@ async def test_mobile_dashboard_wires_focused_plan_today_sheet():
def test_plan_today_controller_is_available_in_the_offline_shell():
source = SERVICE_WORKER.read_text()
assert "stackchain-dashboard-shell-v58" in source
assert "stackchain-dashboard-shell-v59" in source
assert "BASE + 'static/plan-today.js'" in source
+6 -6
View File
@@ -108,7 +108,7 @@ async function dispatchNotificationClick(route) {{
def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v58" in source
assert "stackchain-dashboard-shell-v59" in source
assert "BASE + 'static/create-issue-sheet.js'" in source
assert "BASE + 'static/dashboard.js'" in source
@@ -116,14 +116,14 @@ def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
def test_exact_later_picker_ships_atomically_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v58" in source
assert "stackchain-dashboard-shell-v59" 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-v58" in source
assert "stackchain-dashboard-shell-v59" in source
assert "BASE + 'static/dashboard.css'" in source
assert "BASE + 'static/dashboard.js'" in source
assert "BASE + 'static/install-app.js'" in source
@@ -132,21 +132,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-v58" in source
assert "stackchain-dashboard-shell-v59" 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-v58" in source
assert "stackchain-dashboard-shell-v59" 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-v58" in source
assert "stackchain-dashboard-shell-v59" in source
assert "BASE + 'static/update-ownership.js'" in source
+1 -1
View File
@@ -86,7 +86,7 @@ sync.enqueue('add', 'issue:r:1:');
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-v58" in source
assert "stackchain-dashboard-shell-v59" in source
assert "BASE + 'static/today-sync.js'" in source