'
) in html
assert '.card-planning > summary { display:none;' in html
assert '.card-planning:not([open]) > .card-planning-actions { display:grid;' in html
mobile = html.index('@media (max-width: 600px)')
assert '.card-planning > summary { min-height:44px; display:flex;' in html[mobile:]
assert '.card-planning:not([open]) > .card-planning-actions { display:none;' in html[mobile:]
assert "BASE + 'static/card-planning.js'" in service_worker
def test_unassigned_issue_update_claims_once_and_becomes_today_ready():
script = f"""
const createUpdateOwnership = require({json.dumps(str(UPDATE_OWNERSHIP))});
let claims = 0;
let finishClaim;
const states = [];
const controller = createUpdateOwnership({{
claim: () => {{ claims += 1; return new Promise(resolve => {{ finishClaim = resolve; }}); }},
addToday: () => 'added',
onClaimed: item => states.push({{status:'reconciled', item}}),
onState: state => states.push(state),
}});
controller.open({{
repository:'stackchain/api', title:'Retry deploy',
issue:{{number:7, assignees:[], claimable:true}},
}}, {{notification_id:42, updated_at:'2026-08-08T12:00:00Z'}});
const first = controller.act();
const second = controller.act();
if (first !== second || claims !== 1) throw new Error('claim was not single-flight');
finishClaim({{number:7, title:'Retry deploy', assignees:['timmy'], state:'open'}});
(async () => {{
await first;
const added = await controller.act();
process.stdout.write(JSON.stringify({{claims, added, states}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["claims"] == 1
assert output["added"] == "added"
assert output["states"][0] == {"action": "claim", "busy": False, "message": ""}
assert {"action": "claim", "busy": True, "message": "Assigning…"} in output["states"]
reconciled = next(state for state in output["states"] if state.get("status") == "reconciled")
assert reconciled["item"]["notification_id"] == 42
assert reconciled["item"]["repository"] == "stackchain/api"
assert reconciled["item"]["kind"] == "issue"
assert output["states"][-1] == {
"action": "today", "busy": False, "message": "Added to Today."
}
def test_update_ownership_conflict_removes_stale_action_without_reconciling():
script = f"""
const createUpdateOwnership = require({json.dumps(str(UPDATE_OWNERSHIP))});
const states = [];
let reconciled = 0;
const conflict = new Error('server wording'); conflict.status = 409;
const controller = createUpdateOwnership({{
claim: () => Promise.reject(conflict), addToday: () => 'added',
onClaimed: () => {{ reconciled += 1; }}, onState: state => states.push(state),
}});
controller.open({{repository:'stackchain/api', issue:{{number:7, claimable:true}}}}, {{notification_id:42}});
(async () => {{
const result = await controller.act();
process.stdout.write(JSON.stringify({{result, reconciled, states}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["result"] == "conflict"
assert output["reconciled"] == 0
assert output["states"][-1] == {
"action": "hidden",
"busy": False,
"message": "Someone else claimed or closed this issue. The update is still unread.",
}
def test_unread_issue_update_claims_and_starts_once_without_marking_it_read():
script = f"""
const createUpdateOwnership = require({json.dumps(str(UPDATE_OWNERSHIP))});
let claims = 0;
let starts = 0;
let finishClaim;
const reconciled = [];
const states = [];
const controller = createUpdateOwnership({{
available: () => true,
claim: () => {{ claims += 1; return new Promise(resolve => {{ finishClaim = resolve; }}); }},
addToday: () => 'added',
start: item => {{ starts += 1; return item.notification_id === 42 ? 'started' : 'broken'; }},
recover: () => {{ throw new Error('recovery should not run'); }},
onClaimed: item => reconciled.push(item),
onStartState: state => states.push(state),
onState: () => {{}},
}});
controller.open({{
repository:'stackchain/api', title:'Retry deploy',
issue:{{number:7, assignees:[], claimable:true}},
}}, {{notification_id:42, updated_at:'2026-08-08T12:00:00Z'}});
const first = controller.start();
const second = controller.start();
if (first !== second || claims !== 1) throw new Error('start was not single-flight');
finishClaim({{number:7, title:'Retry deploy', assignees:['timmy'], state:'open'}});
(async () => {{
const result = await first;
process.stdout.write(JSON.stringify({{result, claims, starts, reconciled, states}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["result"] == "started"
assert output["claims"] == 1
assert output["starts"] == 1
assert len(output["reconciled"]) == 1
assert output["reconciled"][0]["notification_id"] == 42
assert output["reconciled"][0]["has_update"] is True
assert output["states"][-1] == {
"action": "hidden",
"busy": False,
"message": "Assigned, added to Today, and ready to work. The update is still unread.",
}
def test_unread_update_start_checks_today_capacity_before_claiming():
script = f"""
const createUpdateOwnership = require({json.dumps(str(UPDATE_OWNERSHIP))});
let claims = 0;
const states = [];
const controller = createUpdateOwnership({{
available: () => false,
claim: () => {{ claims += 1; return Promise.resolve({{number:7}}); }},
addToday: () => 'added', start: () => 'started',
onStartState: state => states.push(state), onState: () => {{}},
}});
controller.open({{repository:'stackchain/api', issue:{{number:7, claimable:true}}}}, {{notification_id:42}});
(async () => {{
const result = await controller.start();
process.stdout.write(JSON.stringify({{result, claims, states}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["result"] == "full"
assert output["claims"] == 0
assert output["states"][-1] == {
"action": "start",
"busy": False,
"message": "Today is limited to 5 items. Remove one before taking ownership.",
}
def test_unread_update_opens_owned_issue_when_today_start_throws():
script = f"""
const createUpdateOwnership = require({json.dumps(str(UPDATE_OWNERSHIP))});
let recovered = null;
const states = [];
const controller = createUpdateOwnership({{
available: () => true,
claim: () => Promise.resolve({{number:7, title:'Retry deploy'}}),
addToday: () => 'added',
start: () => {{ throw new Error('storage failed'); }},
recover: item => {{ recovered = item; }},
onStartState: state => states.push(state), onState: () => {{}},
}});
controller.open({{repository:'stackchain/api', issue:{{number:7, claimable:true}}}}, {{notification_id:42}});
(async () => {{
const result = await controller.start();
process.stdout.write(JSON.stringify({{result, recovered, states}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["result"] == "recovery"
assert output["recovered"]["number"] == 7
assert output["states"][-1] == {
"action": "hidden",
"busy": False,
"message": "Assigned to you, but Today could not start. The update is still unread; the owned issue is open so you can recover.",
}
@pytest.mark.anyio
async def test_mobile_update_sheet_wires_phone_safe_ownership_to_my_work_and_today():
html = await dashboard()
assert '' in html
assert 'id="update-ownership-action"' in html
assert 'id="update-ownership-start"' in html
assert '>Take ownership & start' in html
assert 'hidden aria-describedby="update-sheet-status"' in html
assert 'const updateOwnership = createUpdateOwnership({' in html
assert 'available: () => createAndStart.available()' in html
assert 'const outcome = createAndStart.complete(item)' in html
assert "openRoutedWork(item, qs('#update-ownership-start'))" in html
assert 'updateOwnership.open(detail, selectedUpdate)' in html
assert "qs('#update-ownership-action').addEventListener('click'" in html
assert "qs('#update-ownership-start').addEventListener('click', () => updateOwnership.start())" in html
assert "lastContextSnapshot.issues" in html
assert "todayWork.add(item)" in html
assert '.update-sheet-actions button, .update-sheet-actions a { min-height:44px;' in html
assert '.update-ownership-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr));' in html
assert '.update-sheet-panel { width:100%; border-left:0; padding:14px; overflow-x:hidden;' in html
def test_work_routes_round_trip_all_sheet_kinds_and_reject_unsafe_fragments():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
const inputs = [
{{kind:'issue',repository:'stackchain/api',number:17}},
{{kind:'filed',repository:'stackchain/api',number:18}},
{{kind:'issue',repository:'stackchain/api',number:19,is_filed:true,is_assigned:false}},
{{kind:'pull',repository:'stackchain/dashboard',number:42}},
{{kind:'review',repository:'stackchain/dashboard',number:42}},
{{kind:'update',notification_id:913}},
];
process.stdout.write(JSON.stringify({{
paths: inputs.map(routes.serialize),
parsed: inputs.map(item => routes.parse(routes.serialize(item))),
invalid: [
'#/my-work/issue/../../etc/1',
'#/my-work/issue/stackchain/api/not-a-number',
'#/my-work/update/-1',
'#/other/issue/stackchain/api/1',
].map(routes.parse),
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"paths": [
"#/my-work/issue/stackchain/api/17",
"#/my-work/filed/stackchain/api/18",
"#/my-work/filed/stackchain/api/19",
"#/my-work/pull/stackchain/dashboard/42",
"#/my-work/review/stackchain/dashboard/42",
"#/my-work/update/913",
],
"parsed": [
{"kind": "issue", "repository": "stackchain/api", "number": 17},
{"kind": "filed", "repository": "stackchain/api", "number": 18},
{"kind": "filed", "repository": "stackchain/api", "number": 19},
{"kind": "pull", "repository": "stackchain/dashboard", "number": 42},
{"kind": "review", "repository": "stackchain/dashboard", "number": 42},
{"kind": "update", "notification_id": 913},
],
"invalid": [None, None, None, None],
}
def test_work_route_controller_restores_direct_links_and_uses_history_for_close():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
const listeners = {{}};
const location = {{hash:'#/my-work/issue/stackchain/api/17', href:'https://forge.example/dashboard/#/my-work/issue/stackchain/api/17'}};
const calls = [];
const history = {{
state: null,
pushState(state, _, hash) {{ this.state = state; location.hash = hash; calls.push(['push', hash]); }},
replaceState(state, _, hash) {{ this.state = state; location.hash = hash; calls.push(['replace', hash]); }},
back() {{ calls.push(['back']); location.hash = ''; listeners.popstate(); }},
}};
const controller = routes.createController({{
location, history,
eventTarget: {{addEventListener(name, fn) {{ listeners[name] = fn; }}}},
onOpen(item) {{ calls.push(['open', item.kind, item.repository, item.number || item.notification_id]); }},
onClose() {{ calls.push(['close']); }},
onInvalid() {{ calls.push(['invalid']); }},
}});
controller.start();
controller.setItems([{{kind:'issue',repository:'stackchain/api',number:17}}]);
controller.close();
controller.open({{kind:'pull',repository:'stackchain/dashboard',number:9}});
controller.open({{kind:'review',repository:'stackchain/dashboard',number:10}}, {{replace:true}});
process.stdout.write(JSON.stringify({{calls, hash:location.hash}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["calls"] == [
["open", "issue", "stackchain/api", 17],
["back"],
["close"],
["push", "#/my-work/pull/stackchain/dashboard/9"],
["open", "pull", "stackchain/dashboard", 9],
["replace", "#/my-work/review/stackchain/dashboard/10"],
["open", "review", "stackchain/dashboard", 10],
]
assert output["hash"] == "#/my-work/review/stackchain/dashboard/10"
def test_updates_inbox_route_survives_hydration_and_detail_back_navigation():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
const listeners = {{}};
const location = {{hash:'#/my-work/updates'}};
const calls = [];
const stack = ['#/my-work/updates'];
let cursor = 0;
const history = {{
pushState(state, _, hash) {{ stack.splice(cursor + 1); stack.push(hash); cursor += 1; location.hash = hash; }},
replaceState(state, _, hash) {{ stack[cursor] = hash; location.hash = hash; }},
back() {{ cursor -= 1; location.hash = stack[cursor]; listeners.popstate(); }},
}};
const controller = routes.createController({{
location, history,
eventTarget: {{addEventListener(name, fn) {{ listeners[name] = fn; }}}},
onQueue: queue => calls.push(['queue', queue]),
onOpen: item => calls.push(['open', item.notification_id]),
onClose: () => calls.push(['close']),
onInvalid: () => calls.push(['invalid']),
}});
controller.start();
controller.setItems([]);
controller.open({{kind:'update', notification_id:42}});
controller.close();
process.stdout.write(JSON.stringify({{calls, hash:location.hash, parsed:routes.parse(location.hash)}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"calls": [
["queue", "update"],
["open", 42],
["close"],
["queue", "update"],
],
"hash": "#/my-work/updates",
"parsed": {"kind": "queue", "filter": "update"},
}
def test_every_mobile_queue_route_survives_hydration_and_detail_back_navigation():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
const canonical = ['today', 'agenda', 'attention', 'filed', 'updates', 'later', 'drafts'];
const parsed = canonical.map(name => [name, routes.parse('#/my-work/' + name)]);
const listeners = {{}};
const location = {{hash:'#/my-work/agenda'}};
const calls = [];
const stack = ['#/my-work/agenda'];
let cursor = 0;
const history = {{
pushState(state, _, hash) {{ stack.splice(cursor + 1); stack.push(hash); cursor += 1; location.hash = hash; }},
replaceState(state, _, hash) {{ stack[cursor] = hash; location.hash = hash; }},
back() {{ cursor -= 1; location.hash = stack[cursor]; listeners.popstate(); }},
}};
const controller = routes.createController({{
location, history,
eventTarget: {{addEventListener(name, fn) {{ listeners[name] = fn; }}}},
onQueue: queue => calls.push(['queue', queue]),
onOpen: item => calls.push(['open', item.number]),
onClose: () => calls.push(['close']),
onInvalid: () => calls.push(['invalid']),
}});
controller.start();
controller.setItems([{{kind:'issue', repository:'stackchain/dashboard', number:42}}]);
controller.open({{kind:'issue', repository:'stackchain/dashboard', number:42}});
controller.close();
controller.queue('draft');
process.stdout.write(JSON.stringify({{parsed, calls, hash:location.hash}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"parsed": [
["today", {"kind": "queue", "filter": "today"}],
["agenda", {"kind": "queue", "filter": "agenda"}],
["attention", {"kind": "queue", "filter": "attention"}],
["filed", {"kind": "queue", "filter": "filed"}],
["updates", {"kind": "queue", "filter": "update"}],
["later", {"kind": "queue", "filter": "later"}],
["drafts", {"kind": "queue", "filter": "draft"}],
],
"calls": [
["queue", "agenda"],
["open", 42],
["close"],
["queue", "agenda"],
["queue", "draft"],
],
"hash": "#/my-work/drafts",
}
def test_mobile_queue_routes_reject_unknown_or_nested_fragments():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
process.stdout.write(JSON.stringify([
routes.parse('#/my-work/tomorrow'),
routes.parse('#/my-work/today/extra'),
routes.parse('#/my-work/drafts/1'),
]));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == [None, None, None]
def test_work_route_controller_resolves_cold_routes_without_erasing_the_fragment():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
const location = {{hash:'#/my-work/issue/stackchain/api/87'}};
const calls = [];
const controller = routes.createController({{
location,
history: {{pushState() {{}}, replaceState() {{}}, back() {{}}}},
eventTarget: {{addEventListener() {{}}}},
resolve: async route => {{
calls.push(['resolve', route.kind, route.repository, route.number]);
return {{kind:'issue', repository:'stackchain/api', number:87, title:'Older assigned issue'}};
}},
onResolving: route => calls.push(['resolving', route.number]),
onOpen: item => calls.push(['open', item.number, item.title]),
onClose() {{}},
onInvalid: () => calls.push(['invalid']),
onError: () => calls.push(['error']),
}});
(async () => {{
controller.start();
controller.setItems([]);
await new Promise(resolve => setTimeout(resolve, 0));
process.stdout.write(JSON.stringify({{calls, hash:location.hash}}));
}})();
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"calls": [
["resolving", 87],
["resolve", "issue", "stackchain/api", 87],
["open", 87, "Older assigned issue"],
],
"hash": "#/my-work/issue/stackchain/api/87",
}
def test_work_route_controller_dismisses_only_confirmed_unavailable_routes():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
const location = {{hash:'#/my-work/update/913'}};
const calls = [];
const unavailable = new Error('gone'); unavailable.unavailable = true;
const controller = routes.createController({{
location,
history: {{pushState() {{}}, replaceState() {{}}, back() {{}}}},
eventTarget: {{addEventListener() {{}}}},
resolve: async () => {{ throw unavailable; }},
onResolving() {{}}, onOpen() {{}}, onClose() {{}},
onInvalid: () => calls.push('invalid'),
onError: () => calls.push('error'),
}});
(async () => {{
controller.start(); controller.setItems([]);
await new Promise(resolve => setTimeout(resolve, 0));
process.stdout.write(JSON.stringify(calls));
}})();
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == ["invalid"]
def test_work_route_share_prefers_native_share_and_falls_back_to_clipboard():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
const calls = [];
(async () => {{
const native = await routes.share('https://forge.example/dashboard/#/my-work/update/9', {{
share: async payload => calls.push(['native', payload.url]),
}}, null);
const fallback = await routes.share('https://forge.example/dashboard/#/my-work/update/10', {{}}, {{
writeText: async text => calls.push(['clipboard', text]),
}});
process.stdout.write(JSON.stringify({{native, fallback, calls}}));
}})();
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"native": "shared",
"fallback": "copied",
"calls": [
["native", "https://forge.example/dashboard/#/my-work/update/9"],
["clipboard", "https://forge.example/dashboard/#/my-work/update/10"],
],
}
@pytest.mark.anyio
async def test_dashboard_opens_delegated_filings_with_follow_up_only_capabilities():
html = await dashboard()
assert "else if (item.kind === 'issue' || item.kind === 'filed') openIssueSheet(item, issueTrigger);" in html
assert "const readOnly = issueController.readOnly(item);" in html
assert "qs('#issue-sheet').classList.toggle('read-only', readOnly);" in html
assert "#issue-sheet.read-only .issue-comment-composer" not in html
assert "#issue-sheet.read-only .issue-attachment-controls" in html
assert "#issue-sheet.read-only #issue-planning" in html
assert "#issue-sheet.read-only #issue-handoff" in html
assert "#issue-sheet.read-only #release-issue" in html
assert "qs('#close-issue').hidden = readOnly && !withdrawable" in html
assert "#issue-sheet.read-only .detail-defer" in html
assert "if (readOnly) paintIssueConversation(issueConversation.snapshot(), null);" in html
assert "if (readOnly) qs('#load-older-issue-comments').hidden = true;" not in html
assert "readOnly ? 'Filed issue ready · follow-up enabled'" in html
assert "paintIssueConversation(await issueConversation.loadOlder(), null)" in html
assert ".issue-comment-composer textarea" in html
assert ".issue-comment-composer button" in html
@pytest.mark.anyio
async def test_dashboard_wires_addressable_work_sheets_back_navigation_and_share():
html = await dashboard()
assert '' in html
assert 'const workRoute = createWorkRoute.createController({' in html
assert 'workRoute.setItems(lastMyWork);' in html
assert "api('api/v1/work-route?' + params.toString())" in html
assert "Loading shared work item…" in html
assert 'id="retry-work-route"' in html
assert 'href="' + "' + escAttr(createWorkRoute.serialize(" in html
assert '.read-update { min-height:44px; width:100%; display:flex;' in html
assert 'workRoute.close();' in html
assert html.count('class="share-work-route"') == 4
assert 'createWorkRoute.share(window.location.href, navigator, navigator.clipboard)' in html
assert 'Route unavailable · this item is no longer in My Work.' in html
assert "onQueue: openWorkQueueRoute" in html
assert "selectWorkQueue(filter, { preserveRoute:true })" in html
assert "workRoute.queue(filter)" in html
assert "openDeliveryReceiptRoute" not in html
def test_my_work_queue_prioritizes_labels_then_reviews_and_keeps_repo_identity():
payload = {
"user": {"login": "timmy"},
"issues": [
{
"id": 1,
"number": 7,
"title": "Assigned issue",
"state": "open",
"repository": "stackchain/mobile",
"labels": [],
"assignees": ["timmy"],
"updated_at": "2026-08-06T12:00:00Z",
"url": "https://forge.example/mobile/issues/7",
},
{
"id": 2,
"number": 7,
"title": "Priority issue",
"state": "open",
"repository": "stackchain/api",
"labels": ["P0"],
"assignees": [],
"updated_at": "2026-08-06T11:00:00Z",
"url": "https://forge.example/api/issues/7",
},
],
"pull_requests": [
{
"id": 3,
"number": 4,
"title": "Review PR",
"state": "open",
"repository": "stackchain/web",
"work_reasons": ["review_requested"],
"updated_at": "2026-08-06T13:00:00Z",
"url": "https://forge.example/web/pulls/4",
}
],
}
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const queue = buildMyWork({json.dumps(payload)});
process.stdout.write(JSON.stringify(queue));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
queue = json.loads(result.stdout)
assert [item["title"] for item in queue] == [
"Priority issue",
"Review PR",
"Assigned issue",
]
assert queue[0]["key"] == "stackchain/api#7"
assert queue[0]["reason"] == "P0 priority"
assert queue[1]["reason"] == "Needs your review"
assert queue[1]["is_review"] is True
assert queue[2]["reason"] == "Assigned to you"
def test_my_work_surfaces_and_ranks_due_issues_after_p0_before_ordinary_work():
payload = {
"user": {"login": "timmy"},
"issues": [
{"number": 1, "title": "Ordinary", "repository": "stackchain/api",
"labels": [], "assignees": ["timmy"], "updated_at": "2026-08-07T12:00:00Z"},
{"number": 2, "title": "Due today", "repository": "stackchain/api",
"labels": [], "assignees": ["timmy"], "due_date": "2026-08-07T23:59:59Z"},
{"number": 3, "title": "Overdue", "repository": "stackchain/api",
"labels": [], "assignees": ["timmy"], "due_date": "2026-08-06T23:59:59Z"},
{"number": 4, "title": "P0 future", "repository": "stackchain/api",
"labels": ["P0"], "assignees": ["timmy"], "due_date": "2026-08-10T23:59:59Z"},
],
"pull_requests": [],
}
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const queue = buildMyWork({json.dumps(payload)}, new Date('2026-08-07T12:00:00Z'));
process.stdout.write(JSON.stringify(queue.map(item => ({{title:item.title,reason:item.reason,due_label:item.due_label}}))));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == [
{"title": "P0 future", "reason": "P0 priority", "due_label": "Due Aug 10"},
{"title": "Overdue", "reason": "Overdue", "due_label": "Overdue"},
{"title": "Due today", "reason": "Due today", "due_label": "Due today"},
{"title": "Ordinary", "reason": "Assigned to you"},
]
def test_attention_includes_only_assigned_deadline_and_priority_critical_work():
payload = {
"user": {"login": "timmy"},
"issues": [
{"number": 1, "title": "Assigned P0", "repository": "stackchain/api",
"labels": ["P0"], "assignees": ["timmy"], "due_date": "2026-08-10T23:59:59Z"},
{"number": 2, "title": "Assigned overdue", "repository": "stackchain/api",
"labels": [], "assignees": ["timmy"], "due_date": "2026-08-06T23:59:59Z"},
{"number": 3, "title": "Assigned due today", "repository": "stackchain/api",
"labels": [], "assignees": ["timmy"], "due_date": "2026-08-07T23:59:59Z"},
{"number": 4, "title": "Assigned future", "repository": "stackchain/api",
"labels": [], "assignees": ["timmy"], "due_date": "2026-08-10T23:59:59Z"},
{"number": 5, "title": "Unassigned P0", "repository": "stackchain/api",
"labels": ["critical"], "assignees": [], "due_date": "2026-08-10T23:59:59Z"},
{"number": 6, "title": "Unassigned overdue", "repository": "stackchain/api",
"labels": [], "assignees": [], "due_date": "2026-08-06T23:59:59Z"},
],
"pull_requests": [
{"number": 7, "title": "Updated review", "repository": "stackchain/web",
"labels": [], "assignees": [], "work_reasons": ["review_requested"]},
],
"notifications": [
{"id": 70, "number": 7, "repository": "stackchain/web", "unread": True,
"subject_type": "PullRequest", "url": "https://forge.example/web/pulls/7"},
],
}
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const items = buildMyWork({json.dumps(payload)}, new Date('2026-08-07T12:00:00Z'));
const attention = buildMyWork.filterMyWork(items, 'attention');
process.stdout.write(JSON.stringify({{
titles: attention.map(item => item.title),
count: buildMyWork.countMyWork(items).attention,
reasons: Object.fromEntries(attention.map(item => [item.title, item.attention_reason])),
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"titles": ["Assigned P0", "Updated review", "Assigned overdue", "Assigned due today"],
"count": 4,
"reasons": {
"Assigned P0": "P0 priority",
"Updated review": "Unread update",
"Assigned overdue": "Overdue",
"Assigned due today": "Due today",
},
}
def test_confirmed_issue_labels_replace_snapshot_and_reprioritize_queue():
payload = {
"user": {"login": "timmy"},
"issues": [
{"number": 1, "title": "Older", "repository": "stackchain/api",
"labels": [], "assignees": ["timmy"], "updated_at": "2026-08-06T10:00:00Z"},
{"number": 2, "title": "Newer", "repository": "stackchain/api",
"labels": [], "assignees": ["timmy"], "updated_at": "2026-08-06T12:00:00Z"},
],
"pull_requests": [],
}
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const original = {json.dumps(payload)};
const updated = buildMyWork.replaceIssueLabels(original, 'stackchain/api', 1, ['P0']);
process.stdout.write(JSON.stringify({{
titles: buildMyWork(updated).map(item => item.title),
labels: updated.issues[0].labels,
original: original.issues[0].labels,
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"titles": ["Older", "Newer"],
"labels": ["P0"],
"original": [],
}
def test_confirmed_issue_content_updates_my_work_without_mutating_snapshot():
payload = {
"issues": [{"number": 17, "repository": "stackchain/api", "title": "Old", "body": "Old body"}],
"pull_requests": [],
}
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const original = {json.dumps(payload)};
const updated = buildMyWork.replaceIssueContent(
original, 'stackchain/api', 17,
{{title:'Clarified',body:'New body',updated_at:'2026-08-07T10:01:00Z'}}
);
process.stdout.write(JSON.stringify({{updated:updated.issues[0],original:original.issues[0]}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"updated": {
"number": 17, "repository": "stackchain/api", "title": "Clarified",
"body": "New body", "updated_at": "2026-08-07T10:01:00Z",
},
"original": {
"number": 17, "repository": "stackchain/api", "title": "Old", "body": "Old body",
},
}
def test_issue_sheet_identifies_delegated_filings_as_read_only():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const controller = createIssueSheet({{fetchJson:async () => ({{}}), storage:null}});
process.stdout.write(JSON.stringify([
controller.readOnly({{kind:'filed',is_filed:true,is_assigned:false}}),
controller.readOnly({{kind:'filed',is_filed:true,is_assigned:true,is_completed:true}}),
controller.readOnly({{kind:'issue',is_filed:true,is_assigned:true}}),
controller.readOnly({{kind:'issue',is_assigned:true}}),
]));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == [True, True, False, False]
def test_issue_sheet_loads_filed_items_through_read_only_access():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const urls = [];
const controller = createIssueSheet({{
fetchJson: async url => {{ urls.push(url); return {{}}; }}, storage:null,
}});
Promise.all([
controller.load({{kind:'issue',repository:'stackchain/api',number:17,is_assigned:true}}),
controller.load({{kind:'filed',repository:'stackchain/api',number:18,is_filed:true,is_assigned:false}}),
]).then(() => process.stdout.write(JSON.stringify(urls)));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == [
"api/v1/repos/stackchain/api/issues/17/detail",
"api/v1/repos/stackchain/api/issues/18/detail?access=filed",
]
def test_issue_sheet_withdraws_filed_item_through_author_access_as_single_flight():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const calls = [];
let finish;
const controller = createIssueSheet({{
fetchJson: (url, options={{}}) => {{
calls.push({{url, method:options.method}});
return new Promise(resolve => {{ finish = resolve; }});
}},
storage:null,
}});
const filed = {{kind:'filed',repository:'stackchain/api',number:18,is_filed:true,is_assigned:false}};
const first = controller.close(filed);
const duplicate = controller.close(filed);
finish({{number:18,state:'closed',updated_at:'2026-08-15T10:00:00Z'}});
Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{
calls, same:first === duplicate, state:results[0].state
}})));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"calls": [{
"url": "api/v1/repos/stackchain/api/issues/18/close?access=filed",
"method": "PATCH",
}],
"same": True,
"state": "closed",
}
@pytest.mark.anyio
async def test_mobile_filed_detail_exposes_only_a_withdrawal_action_for_open_delegated_issue():
source = await dashboard()
css = (ISSUE_SHEET.parent / "dashboard.css").read_text()
assert "const withdrawable = item.is_filed && !item.is_assigned && !item.is_completed && item.state === 'open';" in source
assert "qs('#close-issue').hidden = readOnly && !withdrawable" in source
assert "withdrawable ? 'Withdraw issue'" in source
close_handler = source.split("qs('#close-issue').addEventListener('click'", 1)[1].split(
"qs('#close-pull-sheet').addEventListener", 1
)[0]
assert "Withdraw " in close_handler
assert "closes the delegated request" in close_handler
assert "is_completed:true" in close_handler
assert "#issue-sheet.read-only #issue-planning" in css
assert ".issue-sheet-actions button" in css and "min-height:44px" in css
@pytest.mark.anyio
async def test_mobile_filed_detail_exposes_change_delegate_flow_for_open_assigned_filing():
source = await dashboard()
css = (ISSUE_SHEET.parent / "dashboard.css").read_text()
html = (ISSUE_SHEET.parent / "index.html").read_text()
assert "const reassignable = item.is_filed && !item.is_completed && item.state === 'open' &&" in source
assert "classList.toggle('filed-reassignable', reassignable)" in source
assert "reassignable ? 'Change delegate' : 'Hand off to teammate'" in source
assert "issueController.reassign(selectedIssue, recipient)" in source
assert "Change delegate from @" in source and " to @" in source
assert "Assigned to ' + recipient" in source
assert "#issue-sheet.read-only.filed-reassignable #issue-handoff" in css
assert ".issue-handoff select, .issue-handoff button { min-height:44px; }" in css
assert 'id="issue-handoff-summary"' in html
def test_issue_sheet_uses_author_access_for_filed_conversation_and_follow_up():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const createConversationPager = require({json.dumps(str(ISSUE_SHEET.parent / "conversation.js"))});
const calls = [];
const controller = createIssueSheet({{
fetchJson: async (url, options={{}}) => {{
calls.push({{url, method:options.method || 'GET', key:options.headers?.['Idempotency-Key'] || ''}});
if (options.method === 'POST') return {{id:22, body:'Clarifying detail'}};
return {{comments:[], page:1, older_page:null, total:0}};
}},
storage:null,
createOperationId:() => 'filed-followup-876',
createConversationPager,
}});
const filed = {{kind:'filed',repository:'stackchain/api',number:18,is_filed:true,is_assigned:false}};
const assigned = {{kind:'issue',repository:'stackchain/api',number:17,is_assigned:true}};
const initial = {{comments:[], page:2, older_page:1, total:0}};
Promise.all([
controller.conversation(filed, initial).loadOlder(),
controller.comment(filed, 'Clarifying detail'),
controller.conversation(assigned, initial).loadOlder(),
]).then(() => process.stdout.write(JSON.stringify(calls)));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == [
{
"url": "api/v1/repos/stackchain/api/issues/18/comments?page=1&limit=20&access=filed",
"method": "GET",
"key": "",
},
{
"url": "api/v1/repos/stackchain/api/issues/18/comments?access=filed",
"method": "POST",
"key": "filed-followup-876",
},
{
"url": "api/v1/repos/stackchain/api/issues/17/comments?page=1&limit=20",
"method": "GET",
"key": "",
},
]
def test_issue_sheet_reassigns_filed_delegate_with_reviewed_owner_as_single_flight():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const calls = [];
let finish;
const controller = createIssueSheet({{
fetchJson: (url, options={{}}) => {{
calls.push({{url, method:options.method || 'GET', body:options.body ? JSON.parse(options.body) : null}});
if (url.includes('handoff-candidates')) return Promise.resolve([
{{login:'alex',name:'Alexander'}}, {{login:'casey',name:'Casey'}}
]);
return new Promise(resolve => {{ finish = resolve; }});
}}, storage:null,
}});
const filed = {{repository:'stackchain/api',number:18,is_filed:true,is_assigned:false,assignees:['alex']}};
(async () => {{
const candidates = await controller.loadHandoffCandidates(filed);
const first = controller.reassign(filed, 'casey');
const duplicate = controller.reassign(filed, 'casey');
finish({{number:18,repository:'stackchain/api',state:'open',assignees:['casey'],recipient:'casey',previous_assignees:['alex']}});
const results = await Promise.all([first, duplicate]);
process.stdout.write(JSON.stringify({{calls,candidates,same:first===duplicate,result:results[0]}}));
}})();
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"calls": [
{
"url": "api/v1/repos/stackchain/api/issues/18/handoff-candidates?access=filed",
"method": "GET", "body": None,
},
{
"url": "api/v1/repos/stackchain/api/issues/18/reassign",
"method": "PATCH",
"body": {"recipient": "casey", "expected_assignees": ["alex"]},
},
],
"candidates": [
{"login": "alex", "name": "Alexander"},
{"login": "casey", "name": "Casey"},
],
"same": True,
"result": {
"number": 18, "repository": "stackchain/api", "state": "open",
"assignees": ["casey"], "recipient": "casey", "previous_assignees": ["alex"],
},
}
def test_issue_release_is_single_flight_and_requires_confirmed_unassignment():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
let calls = 0;
let finish;
const controller = createIssueSheet({{
fetchJson: () => {{
calls += 1;
return new Promise(resolve => {{ finish = resolve; }});
}},
storage: null,
}});
const item = {{repository:'stackchain/api', number:17}};
const first = controller.release(item, 'timmy');
const duplicate = controller.release(item, 'timmy');
finish({{number:17, repository:'stackchain/api', assignees:[], available:true}});
Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{
calls, same: first === duplicate, results
}})));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["calls"] == 1
assert output["same"] is True
assert output["results"][0]["available"] is True
def test_related_issue_draft_reuses_plan_but_resets_issue_specific_work():
script = f"""
const createIssueSheet = require({json.dumps(str(CREATE_ISSUE_SHEET))});
const related = createIssueSheet.buildRelatedDraft({{
repository:'stackchain/dashboard', title:'Completed discovery', body:'Filled private report',
labelIds:[7, 7, 9], milestoneId:4, dueDate:'2026-08-20',
assignee:'alex', assigneeName:'Alexander', unassigned:false,
templateId:'bug.yml', templateName:'Bug report', capturedBody:'Original notes',
blockers:[{{repository:'stackchain/api',number:2}}], estimateMinutes:45,
completionIntent:'create-and-start', operationId:'already-delivered',
attachment:{{name:'secret.png'}}, duplicateAcknowledged:true,
}}, {{id:'bug.yml', name:'Bug report', body:'## What happened?\\n\\n## Expected'}});
process.stdout.write(JSON.stringify(related));
"""
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
assert result.returncode == 0, result.stderr
output = json.loads(result.stdout)
assert output == {
"repository": "stackchain/dashboard",
"title": "",
"body": "## What happened?\n\n## Expected",
"labelIds": [7, 9],
"milestoneId": 4,
"dueDate": "2026-08-20",
"assignee": "alex",
"assigneeName": "Alexander",
"templateId": "bug.yml",
"templateName": "Bug report",
"capturedBody": "",
}
def test_issue_release_rejects_response_that_still_assigns_current_user():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const controller = createIssueSheet({{
fetchJson: async () => ({{number:17, assignees:['timmy']}}), storage: null,
}});
controller.release({{repository:'stackchain/api', number:17}}, 'timmy')
.then(() => process.stdout.write('unexpected'))
.catch(error => process.stdout.write(error.message));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert result.stdout == "Issue release was not confirmed."
def test_issue_handoff_is_single_flight_and_requires_confirmed_transfer():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
let calls = [];
let finish;
const controller = createIssueSheet({{
fetchJson: (url, options) => {{
calls.push({{url, options}});
return new Promise(resolve => {{ finish = resolve; }});
}},
storage: null,
}});
const item = {{repository:'stackchain/api', number:17}};
const first = controller.handoff(item, 'alex', 'timmy');
const duplicate = controller.handoff(item, 'alex', 'timmy');
finish({{number:17, repository:'stackchain/api', recipient:'alex', assignees:['alex']}});
Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{
calls, same: first === duplicate, results
}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["same"] is True
assert len(output["calls"]) == 1
assert output["calls"][0]["url"].endswith("/issues/17/handoff")
assert output["calls"][0]["options"]["method"] == "PATCH"
assert json.loads(output["calls"][0]["options"]["body"]) == {"recipient": "alex"}
assert output["results"][0]["assignees"] == ["alex"]
def test_issue_handoff_candidates_use_the_assigned_issue_route():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
let call;
const controller = createIssueSheet({{
fetchJson: async (url, options) => {{ call = {{url, options}}; return [{{login:'alex', name:'Alexander'}}]; }},
storage: null,
}});
controller.loadHandoffCandidates({{repository:'stackchain/api', number:17}})
.then(result => process.stdout.write(JSON.stringify({{call, result}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["call"]["url"].endswith("/issues/17/handoff-candidates")
assert output["call"]["options"]["headers"]["Accept"] == "application/json"
assert output["result"] == [{"login": "alex", "name": "Alexander"}]
def test_issue_blocker_mutation_is_single_flight_and_requires_canonical_confirmation():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
let calls = [];
let finish;
const controller = createIssueSheet({{
storage:null,
fetchJson:(url, options) => {{ calls.push({{url, options}}); return new Promise(resolve => finish = resolve); }},
}});
const item = {{repository:'stackchain/dashboard', number:17}};
const blocker = {{repository:'stackchain/api', number:9}};
const first = controller.updateBlocker(item, blocker, false);
const duplicate = controller.updateBlocker(item, blocker, false);
finish({{repository:item.repository, number:item.number, dependencies_available:true,
dependencies:[{{...blocker,title:'Restore API',state:'open'}}]}});
Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{
calls:calls.map(call => ({{url:call.url,method:call.options.method,body:JSON.parse(call.options.body)}})),
same:first === duplicate, result:results[0]
}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["same"] is True
assert output["calls"] == [{
"url": "api/v1/repos/stackchain/dashboard/issues/17/blockers",
"method": "POST",
"body": {"repository": "stackchain/api", "number": 9},
}]
assert output["result"]["dependencies"][0]["number"] == 9
def test_issue_blocker_removal_rejects_unconfirmed_canonical_state():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const controller = createIssueSheet({{
storage:null,
fetchJson:async () => ({{dependencies_available:true,
dependencies:[{{repository:'stackchain/api',number:9,state:'open'}}]}}),
}});
controller.updateBlocker(
{{repository:'stackchain/dashboard',number:17}},
{{repository:'stackchain/api',number:9}}, true
).then(() => process.stdout.write('unexpected'))
.catch(error => process.stdout.write(error.message));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert result.stdout == "Blocker change was not confirmed."
@pytest.mark.anyio
async def test_mobile_issue_sheet_manages_blockers_with_search_and_touch_safe_controls():
html = await dashboard()
assert 'id="manage-issue-blockers"' in html
assert 'id="issue-blocker-search" type="search"' in html
assert 'id="issue-blocker-results" role="listbox"' in html
assert 'id="cancel-issue-blocker"' in html
assert "issueController.updateBlocker(selectedIssue, blocker, false)" in html
assert "issueController.updateBlocker(selectedIssue, blocker, true)" in html
assert "api('api/v1/search?q='" in html
assert "result.kind === 'issue' && result.state === 'open'" in html
assert "renderPlanIssueDependencies(selectedIssueDetail)" in html
assert ".issue-blocker-manager :is(input,button) { min-height:44px;" in html
assert "width:100%; max-width:100%; box-sizing:border-box" in html
assert "overflow-wrap:anywhere" in html
@pytest.mark.anyio
async def test_mobile_issue_detail_toggles_checklist_with_touch_safe_recovery():
html = await dashboard()
controller = ISSUE_SHEET.read_text()
assert "issueController.renderTasks(qs('#issue-sheet-body'), detail," in html
assert "!issueController.readOnly(selectedIssue) && detail.state === 'open'" in html
assert "enqueueDurably:message => authoredOutbox.enqueueDurably(message)" in html
assert "issueController.bindTaskToggles({" in html
assert "container.addEventListener('change', async event =>" in controller
assert "event.target.closest('input.task-list-toggle')" in controller
assert "state.item, state.detail, Number(control.dataset.taskIndex), control.checked" in controller
assert "current:()=>({item:selectedIssue,detail:selectedIssueDetail,offline:selectedIssueOffline})" in html
assert "state.offline ? this.queueTask" in controller
assert "status.textContent = 'Checklist queued. Pending sync.'" in controller
assert "buildMyWork.replaceIssueContent" in html
assert "status.textContent = 'Checklist updated.'" in controller
assert "restore(state.detail)" in controller
assert "error.message + ' Checklist was not changed; reload latest or use Edit issue.'" in controller
assert '.task-list-toggle { min-width:44px; min-height:44px;' in html
assert '.checklist-pending .task-list-toggle { opacity:.65;' in html
@pytest.mark.anyio
async def test_mobile_issue_detail_adds_a_checklist_step_inline_with_accessible_touch_controls():
html = await dashboard()
assert 'id="open-add-checklist-step"' in html
assert 'id="add-checklist-step-form"' in html
assert 'id="add-checklist-step" type="text"' in html
assert 'id="save-checklist-step" type="submit"' in html
assert 'id="cancel-checklist-step"' in html
assert 'id="add-checklist-step-status" class="small" aria-live="assertive"' in html
assert "(selectedIssueOffline ? issueController.queueAddedTask : issueController.addTask).call(" in html
assert "applyIssueContent(state.item, state.detail, state.offline ? result.detail : result)" in html
assert "Checklist step queued. Pending sync." in html
assert "qs('#add-checklist-step').focus()" in html
assert "qs('#open-add-checklist-step').focus()" in html
assert '.checklist-add button, .checklist-add input { min-height:44px;' in html
assert '#issue-sheet.read-only .checklist-add { display:none;' in html
assert 'grid-template-columns:minmax(0,1fr) auto auto' in html
@pytest.mark.anyio
async def test_mobile_drafts_reviews_and_retries_an_unambiguous_checklist_conflict():
html = await dashboard()
assert "mergeChecklistConflict: mergeChecklistConflict" in html
assert "const checklistConflict = item.checklist_conflict === true" in html
assert 'class="draft-review-checklist"' in html
assert ">Review changes" in html
assert "const latest = await issueController.load({" in html
assert "authoredOutbox.resolveIssueContentConflict(item.outbox_id, latest, activeFlushLogin)" in html
assert "window.confirm('Apply ' + preview.changes.length + ' checklist change'" in html
assert "await authoredOutbox.retry(item.outbox_id, activeFlushLogin)" in html
assert "could not be matched safely" in html
assert "checklist-conflict.js" in FRONTEND_BUNDLE.read_text()
def test_completed_issue_checklist_offers_close_and_next_during_today():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const controller = createIssueSheet({{storage:null}});
const container = {{querySelectorAll:() => [{{checked:true}}, {{checked:true}}]}};
process.stdout.write(JSON.stringify(controller.checklistCompletion(container, {{
interactive:true, today:true, offline:false,
}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output == {
"visible": True,
"status": "Checklist complete",
"label": "Close & next",
}
def test_checklist_completion_stays_hidden_when_kept_open():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const controller = createIssueSheet({{storage:null}});
const container = {{querySelectorAll:() => [{{checked:true}}]}};
process.stdout.write(JSON.stringify(controller.checklistCompletion(container, {{
interactive:true, today:false, offline:false, dismissed:true,
}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["visible"] is False
def test_checklist_completion_copy_matches_online_and_offline_close_modes():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const controller = createIssueSheet({{storage:null}});
const container = {{querySelectorAll:() => [{{checked:true}}]}};
process.stdout.write(JSON.stringify({{
outside:controller.checklistCompletion(container, {{interactive:true,today:false,offline:false}}),
offlineToday:controller.checklistCompletion(container, {{interactive:true,today:true,offline:true}}),
offlineOutside:controller.checklistCompletion(container, {{interactive:true,today:false,offline:true}}),
}}));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["outside"]["label"] == "Close issue"
assert output["offlineToday"] == {
"visible": True,
"status": "Checklist complete · pending sync",
"label": "Queue close & next",
}
assert output["offlineOutside"]["label"] == "Queue issue closure"
@pytest.mark.anyio
async def test_mobile_checklist_completion_bar_reuses_confirmed_close_flow():
html = await dashboard()
assert 'id="checklist-completion"' in html
assert 'id="checklist-completion-status" role="status"' in html
assert 'id="complete-checklist-issue"' in html
assert 'id="keep-checklist-issue-open"' in html
assert "function renderChecklistCompletion(detail)" in html
assert "issueController.checklistCompletion(qs('#issue-sheet-body')" in html
assert "qs('#complete-checklist-issue').addEventListener('click', () =>" in html
assert "qs('#close-issue').click()" in html
assert "dismissedChecklistBody = selectedIssueDetail?.body" in html
assert ".checklist-completion { position:fixed;" in html
assert ".checklist-completion button { min-height:44px;" in html
assert "padding-bottom:calc(118px + env(safe-area-inset-bottom))" in html
def test_offline_issue_checklist_toggle_waits_for_durable_admission_and_returns_pending_detail():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
let admit; const queued = [];
const controller = createIssueSheet({{
storage:null,
toggleTask:(body, index, checked) => body.replace(index === 0 ? '[ ]' : '[X]', checked ? '[x]' : '[ ]'),
enqueueDurably:message => {{ queued.push(message); return new Promise(resolve => admit = resolve); }},
}});
const item = {{repository:'stackchain/api', number:17}};
const detail = {{title:'Release', body:'- [ ] Build\\n- [X] Ship', updated_at:'2026-08-15T10:00:00Z'}};
let settled = false;
const pending = controller.queueTask(item, detail, 0, true).then(result => {{ settled = true; return result; }});
const before = settled;
admit({{item:{{id:'queued-check'}}}});
pending.then(result => process.stdout.write(JSON.stringify({{before,queued,result}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["before"] is False
assert output["queued"] == [{
"kind": "issue-content", "repository": "stackchain/api", "number": 17,
"title": "Release", "baseBody": "- [ ] Build\n- [X] Ship",
"body": "- [x] Build\n- [X] Ship",
"expectedUpdatedAt": "2026-08-15T10:00:00Z",
}]
assert output["result"] == {
"queued": True,
"detail": {
"title": "Release", "body": "- [x] Build\n- [X] Ship",
"updated_at": "2026-08-15T10:00:00Z", "checklist_pending": True,
},
}
def test_offline_issue_checklist_restores_account_bound_pending_body_after_reload():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const controller = createIssueSheet({{storage:null}});
const detail = {{title:'Release',body:'- [ ] Build',updated_at:'old'}};
const items = [
{{kind:'issue-content',repository:'o/r',number:7,ownerLogin:'alexander',title:'Wrong',body:'- [x] Wrong',expectedUpdatedAt:'wrong',status:'queued'}},
{{kind:'issue-content',repository:'o/r',number:7,ownerLogin:'timmy',title:'Release',body:'- [x] Build',expectedUpdatedAt:'old',status:'queued'}},
];
process.stdout.write(JSON.stringify({{
restored:controller.pendingTask({{repository:'o/r',number:7}}, detail, items, 'timmy'),
isolated:controller.pendingTask({{repository:'o/r',number:7}}, detail, items, 'hou3'),
}}));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["restored"] == {
"title": "Release", "body": "- [x] Build", "updated_at": "old",
"checklist_pending": True,
}
assert output["isolated"] == {
"title": "Release", "body": "- [ ] Build", "updated_at": "old",
}
def test_pending_offline_checklist_remains_interactive_for_the_next_change():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
let options = null; const classes = [];
const container = {{
classList:{{toggle:(name,present)=>classes.push([name,present])}},
innerHTML:'',
}};
const controller = createIssueSheet({{
storage:null,
renderMarkdown:(body, received)=>{{options=received;return '
';}}
}});
controller.renderTasks(container, {{body:'- [x] Build\\n- [ ] Test',checklist_pending:true}}, true);
process.stdout.write(JSON.stringify({{options,classes,html:container.innerHTML}}));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["options"] == {"interactiveTasks": True}
assert output["classes"] == [["checklist-pending", True]]
assert "task-list-toggle" in output["html"]
def test_issue_checklist_toggle_submits_exact_revision_checked_body_once():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
let calls = [];
let finish;
const controller = createIssueSheet({{
storage:null,
toggleTask:(body, index, checked) => body.replace(index === 1 ? '[X]' : '[ ]', checked ? '[x]' : '[ ]'),
fetchJson:(url, options) => {{
calls.push({{url, options}});
return new Promise(resolve => {{ finish = resolve; }});
}},
}});
const item = {{repository:'stackchain/api', number:17}};
const detail = {{title:'Release', body:'- [ ] Build\\n- [X] Ship', updated_at:'2026-08-15T10:00:00Z'}};
const first = controller.toggleTask(item, detail, 1, true);
const duplicate = controller.toggleTask(item, detail, 1, true);
finish({{number:17,title:'Release',body:'- [ ] Build\\n- [x] Ship',updated_at:'2026-08-15T10:01:00Z'}});
Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{
calls:calls.map(call => ({{url:call.url,body:JSON.parse(call.options.body)}})),
same:first === duplicate,
confirmed:results[0],
}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["same"] is True
assert output["calls"] == [{
"url": "api/v1/repos/stackchain/api/issues/17/content",
"body": {
"title": "Release",
"body": "- [ ] Build\n- [x] Ship",
"expected_updated_at": "2026-08-15T10:00:00Z",
},
}]
assert output["confirmed"]["updated_at"] == "2026-08-15T10:01:00Z"
def test_issue_sheet_adds_a_validated_unchecked_step_without_replacing_existing_content():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const calls = [];
const controller = createIssueSheet({{
storage:null,
fetchJson:(url, options) => {{
calls.push({{url, body:JSON.parse(options.body)}});
return Promise.resolve({{number:17,title:'Release',body:'Intro\\n\\n- [x] Build\\n- [ ] Verify rollback',updated_at:'new'}});
}},
}});
const item = {{repository:'stackchain/api',number:17}};
const detail = {{title:'Release',body:'Intro\\n\\n- [x] Build',updated_at:'old'}};
Promise.all([
controller.addTask(item, detail, ' Verify rollback '),
controller.addTask(item, detail, ' build ').catch(error => ({{error:error.message}})),
controller.addTask(item, detail, ' ').catch(error => ({{error:error.message}})),
]).then(results => process.stdout.write(JSON.stringify({{calls,results}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["calls"] == [{
"url": "api/v1/repos/stackchain/api/issues/17/content",
"body": {
"title": "Release",
"body": "Intro\n\n- [x] Build\n- [ ] Verify rollback",
"expected_updated_at": "old",
},
}]
assert output["results"][0]["body"].endswith("- [ ] Verify rollback")
assert output["results"][1] == {"error": "That checklist step already exists."}
assert output["results"][2] == {"error": "Enter a checklist step."}
def test_offline_issue_sheet_adds_a_step_only_after_durable_admission():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
let admit; const queued = [];
const controller = createIssueSheet({{
storage:null,
enqueueDurably:message => {{ queued.push(message); return new Promise(resolve => admit=resolve); }},
}});
const item = {{repository:'stackchain/api',number:17}};
const detail = {{title:'Release',body:'Intro',updated_at:'old'}};
let settled = false;
const pending = controller.queueAddedTask(item, detail, 'Verify rollback').then(result => {{settled=true;return result;}});
const before = settled;
admit({{item:{{id:'add-step'}}}});
pending.then(result => process.stdout.write(JSON.stringify({{before,queued,result}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output["before"] is False
assert output["queued"] == [{
"kind": "issue-content", "repository": "stackchain/api", "number": 17,
"title": "Release", "baseBody": "Intro", "body": "Intro\n- [ ] Verify rollback",
"expectedUpdatedAt": "old",
}]
assert output["result"] == {
"queued": True,
"detail": {
"title": "Release", "body": "Intro\n- [ ] Verify rollback",
"updated_at": "old", "checklist_pending": True,
},
}
def test_issue_content_edit_is_single_flight_and_keeps_scoped_draft_until_confirmed():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const values = new Map();
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
let calls = [];
let finish;
const controller = createIssueSheet({{
storage,
fetchJson:(url, options) => {{
calls.push({{url, options}});
return new Promise(resolve => {{ finish = resolve; }});
}},
}});
const item = {{repository:'stackchain/api', number:17}};
const draft = {{title:'Clarified scope', body:'Updated body', expectedUpdatedAt:'2026-08-07T10:00:00Z'}};
controller.saveEditDraft(item, draft);
const first = controller.updateContent(item, draft);
const duplicate = controller.updateContent(item, draft);
const during = controller.loadEditDraft(item);
finish({{repository:'stackchain/api',number:17,title:'Clarified scope',body:'Updated body',updated_at:'2026-08-07T10:01:00Z'}});
Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{
calls:calls.map(call => ({{url:call.url,method:call.options.method,body:JSON.parse(call.options.body)}})),
same:first === duplicate, during, after:controller.loadEditDraft(item), results
}})));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["calls"] == [{
"url": "api/v1/repos/stackchain/api/issues/17/content",
"method": "PATCH",
"body": {
"title": "Clarified scope", "body": "Updated body",
"expected_updated_at": "2026-08-07T10:00:00Z",
},
}]
assert output["same"] is True
assert output["during"] == {
"title": "Clarified scope", "body": "Updated body",
"expectedUpdatedAt": "2026-08-07T10:00:00Z",
}
assert output["after"] is None
assert output["results"][0]["updated_at"] == "2026-08-07T10:01:00Z"
def test_filed_issue_content_edit_uses_author_scoped_endpoint_and_keeps_draft_on_failure():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const values = new Map();
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
let call;
const controller = createIssueSheet({{
storage,
fetchJson:(url, options) => {{ call={{url,options}}; return Promise.reject(new Error('conflict')); }},
}});
const item = {{repository:'stackchain/api', number:17, is_filed:true, is_assigned:false, state:'open'}};
const draft = {{title:'Clarified filing', body:'Canonical body', expectedUpdatedAt:'2026-08-07T10:00:00Z'}};
controller.updateContent(item, draft).catch(() => process.stdout.write(JSON.stringify({{
url:call.url, body:JSON.parse(call.options.body), draft:controller.loadEditDraft(item)
}})));
"""
output = json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
assert output == {
"url": "api/v1/repos/stackchain/api/issues/17/content?access=filed",
"body": {
"title": "Clarified filing", "body": "Canonical body",
"expected_updated_at": "2026-08-07T10:00:00Z",
},
"draft": {
"title": "Clarified filing", "body": "Canonical body",
"expectedUpdatedAt": "2026-08-07T10:00:00Z",
},
}
def test_issue_due_date_update_is_single_flight_and_keeps_draft_until_confirmed():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const values = new Map();
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
let calls = [];
let finish;
const controller = createIssueSheet({{
storage,
fetchJson:(url, options) => {{
calls.push({{url, options}});
return new Promise(resolve => {{ finish = resolve; }});
}},
}});
const item = {{repository:'stackchain/api', number:17}};
const first = controller.updateDueDate(item, '2026-08-09T23:59:59Z');
const duplicate = controller.updateDueDate(item, '2026-08-09T23:59:59Z');
const during = controller.loadDueDateDraft(item);
finish({{repository:'stackchain/api',number:17,state:'open',due_date:'2026-08-09T23:59:59Z'}});
Promise.all([first, duplicate]).then(results => process.stdout.write(JSON.stringify({{
calls:calls.map(call => ({{url:call.url,method:call.options.method,body:JSON.parse(call.options.body)}})),
same:first === duplicate, during, after:controller.loadDueDateDraft(item), results
}})));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert output["calls"] == [{
"url": "api/v1/repos/stackchain/api/issues/17/due-date",
"method": "PATCH",
"body": {"due_date": "2026-08-09T23:59:59Z"},
}]
assert output["same"] is True
assert output["during"] == "2026-08-09T23:59:59Z"
assert output["after"] is None
assert output["results"][0]["due_date"] == "2026-08-09T23:59:59Z"
@pytest.mark.anyio
async def test_mobile_issue_sheet_exposes_touch_sized_due_date_editor_and_card_badge():
html = await dashboard()
assert 'id="issue-due-date" type="date"' in html
assert 'id="save-issue-due-date"' in html
assert 'id="clear-issue-due-date"' in html
assert 'class="pill due-badge"' in html
assert '.issue-due-editor input, .issue-due-editor button { min-height:44px;' in html
assert 'padding-bottom:calc(10px + env(safe-area-inset-bottom))' in html
@pytest.mark.anyio
async def test_mobile_issue_sheet_exposes_touch_safe_teammate_handoff():
html = await dashboard()
assert 'id="issue-handoff-recipient"' in html
assert 'id="load-issue-handoff"' in html
assert 'id="confirm-issue-handoff"' in html
assert 'id="issue-handoff-status" class="small" aria-live="assertive"' in html
assert '.issue-handoff select, .issue-handoff button { min-height:44px;' in html
assert "issueController.loadHandoffCandidates(selectedIssue)" in html
assert "issueController.handoff(selectedIssue, recipient" in html
@pytest.mark.anyio
async def test_new_issue_sheet_exposes_touch_safe_release_planning_controls():
html = await dashboard()
assert 'id="create-issue-milestone"' in html
assert 'id="create-issue-due-date" type="date"' in html
assert 'id="create-issue-milestone-status" aria-live="polite"' in html
assert '.create-issue-form select, .create-issue-form input[type="date"] { min-height:44px;' in html
assert 'issueFilingMetadata.load(repository, selected)' in html
assert "issueOwnerPicker.draft(selectedIssueLabelIds(), issueCaptureBlockers" in html
@pytest.mark.anyio
async def test_dashboard_launches_share_capture_with_draft_conflict_choices():
html = await dashboard()
assert '
Unplanned' in html
assert 'id="issue-milestone"' in html
assert 'id="save-issue-milestone"' in html
assert 'class="pill milestone-badge"' in html
assert '.work-milestone-filter, .issue-milestone-editor select, .issue-milestone-editor button { min-height:44px;' in html
assert "getMilestone: () => selectedWorkFilter === 'agenda' ? 'all' : selectedWorkMilestone" in html
assert 'buildMyWork.replaceIssueMilestone(' in html
@pytest.mark.anyio
async def test_mobile_issue_sheet_puts_reading_before_collapsed_planning_controls():
html = await dashboard()
body = html.index('id="issue-sheet-body"')
conversation = html.index('
Full conversation
', body)
planning = html.index('id="issue-planning"', conversation)
labels = html.index('id="issue-label-editor"', planning)
milestone = html.index('id="issue-milestone"', planning)
assert '
Plan & edit' in html[planning:labels]
assert '
' in html
assert body < conversation < planning < labels < milestone
def test_issue_planning_metadata_is_lazy_cached_and_retryable_after_failure():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const calls = [];
let fail = true;
const planning = createIssueSheet.createPlanningLoader({{
loadLabels: async item => {{ calls.push('labels:' + item.number); return [{{id:1,name:'P0'}}]; }},
loadMilestones: async item => {{
calls.push('milestones:' + item.number);
if (fail) throw new Error('offline');
return [{{id:9,title:'RC'}}];
}},
}});
const item = {{repository:'stackchain/api', number:17}};
(async () => {{
const before = calls.slice();
let failed = false;
try {{ await planning.open(item); }} catch (_error) {{ failed = true; }}
fail = false;
const first = await planning.open(item);
const second = await planning.open(item);
process.stdout.write(JSON.stringify({{before, failed, calls, same:first === second, first}}));
}})();
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"before": [],
"failed": True,
"calls": ["labels:17", "milestones:17", "labels:17", "milestones:17"],
"same": True,
"first": {"labels": [{"id": 1, "name": "P0"}], "milestones": [{"id": 9, "title": "RC"}]},
}
@pytest.mark.anyio
async def test_opening_issue_defers_planning_requests_until_disclosure_expands():
html = await dashboard()
open_handler = html[html.index('async function openIssueSheet'):html.index('function closeIssueSheet')]
assert 'loadIssueLabelEditor(' not in open_handler
assert 'loadIssueMilestoneEditor(' not in open_handler
assert "if (qs('#issue-planning').open && !offlineDetail) loadIssuePlanning();" in open_handler
assert "qs('#issue-planning').addEventListener('toggle'" in html
assert 'planningLoader.open(selectedIssue)' in html
assert 'id="retry-issue-planning"' in html
def test_my_work_reviews_filter_and_summary_are_actionable():
items = [
{"title": "Issue", "kind": "issue", "is_review": False, "is_assigned": True},
{"title": "Assigned PR", "kind": "pull", "is_review": False, "is_assigned": True},
{"title": "Review PR", "kind": "pull", "is_review": True, "is_assigned": False},
]
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const items = {json.dumps(items)};
process.stdout.write(JSON.stringify({{
reviews: buildMyWork.filterMyWork(items, 'review'),
summary: buildMyWork.summarizeMyWork(items),
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
output = json.loads(result.stdout)
assert [item["title"] for item in output["reviews"]] == ["Review PR"]
assert output["summary"] == "1 review · 2 assigned"
def test_attention_filter_unions_updates_and_reviews_without_double_counting():
items = [
{"title": "Unread issue", "kind": "issue", "has_update": True, "is_review": False},
{"title": "Requested review", "kind": "pull", "has_update": False, "is_review": True},
{"title": "Updated review", "kind": "pull", "has_update": True, "is_review": True},
{"title": "Ordinary assignment", "kind": "issue", "has_update": False, "is_review": False},
]
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const items = {json.dumps(items)};
process.stdout.write(JSON.stringify({{
attention: buildMyWork.filterMyWork(items, 'attention').map(item => item.title),
count: buildMyWork.countMyWork(items).attention,
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"attention": ["Unread issue", "Requested review", "Updated review"],
"count": 3,
}
def test_later_queue_defers_work_locally_and_scopes_it_to_confirmed_login():
script = f"""
const createLaterWork = require({json.dumps(str(LATER_WORK))});
const values = new Map();
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
let login = 'timmy';
const item = {{kind:'pull',is_review:true,repository:'stackchain/api',number:17,title:'Review me'}};
const store = createLaterWork({{storage,getLogin:() => login,now:() => new Date('2026-08-08T12:00:00Z')}});
const deferred = store.defer(item, new Date('2026-08-08T16:00:00Z'));
const timmy = store.partition([item]);
login = 'alexander';
const alexander = store.partition([item]);
process.stdout.write(JSON.stringify({{
deferred,
timmy:{{active:timmy.active.length,later:timmy.later}},
alexander:{{active:alexander.active.length,later:alexander.later.length}},
keys:Array.from(values.keys()),
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"deferred": "deferred",
"timmy": {
"active": 0,
"later": [{
"kind": "pull", "is_review": True, "repository": "stackchain/api",
"number": 17, "title": "Review me", "deferred_until": "2026-08-08T16:00:00.000Z",
}],
},
"alexander": {"active": 1, "later": 0},
"keys": ["stackchain.later-work.v1.timmy"],
}
def test_later_queue_reports_invalid_identity_and_failed_storage_without_false_success():
script = f"""
const createLaterWork = require({json.dumps(str(LATER_WORK))});
let login = '';
const item = {{kind:'issue',repository:'stackchain/api',number:17}};
const unavailable = createLaterWork({{
storage: {{getItem:() => null,setItem:() => {{ throw new Error('quota'); }},removeItem:() => {{}}}},
getLogin:() => login, now:() => new Date('2026-08-08T12:00:00Z'),
}});
const noIdentity = unavailable.defer(item, new Date('2026-08-08T16:00:00Z'));
login = 'timmy';
const invalid = unavailable.defer(item, new Date('2026-08-08T11:00:00Z'));
const failedWrite = unavailable.defer(item, new Date('2026-08-08T16:00:00Z'));
process.stdout.write(JSON.stringify({{noIdentity, invalid, failedWrite}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"noIdentity": "unavailable",
"invalid": "invalid",
"failedWrite": "unavailable",
}
def test_later_queue_defers_a_batch_atomically_with_one_wake_time_and_change_per_item():
script = f"""
const createLaterWork = require({json.dumps(str(LATER_WORK))});
const values = new Map();
const changes = [];
let writes = 0;
let fail = false;
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => {{ writes += 1; if (fail) throw new Error('quota'); values.set(key,value); }},
removeItem:key => values.delete(key),
}};
const first = {{kind:'update',repository:'stackchain/api',number:17,notification_id:91}};
const second = {{kind:'update',repository:'stackchain/web',number:8,notification_id:92}};
const store = createLaterWork({{
storage, getLogin:() => 'timmy', now:() => new Date('2026-08-08T12:00:00Z'),
setTimer:() => 1, clearTimer:() => {{}},
onChange:(...change) => changes.push(change),
}});
const deferred = store.deferMany([first, second], new Date('2026-08-09T09:00:00Z'));
const saved = store.partition([first, second]);
const beforeFailure = JSON.stringify(store.read());
fail = true;
const unavailable = store.deferMany([
{{kind:'update',repository:'stackchain/api',number:19,notification_id:93}},
{{kind:'update',repository:'stackchain/api',number:20,notification_id:94}},
], new Date('2026-08-10T09:00:00Z'));
process.stdout.write(JSON.stringify({{
deferred, unavailable, writes, changes,
later:saved.later.map(item => [item.notification_id,item.deferred_until]),
unchanged:beforeFailure === JSON.stringify(store.read()),
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"deferred": "deferred",
"unavailable": "unavailable",
"writes": 2,
"changes": [
["defer", "update:stackchain/api:17:91", "2026-08-09T09:00:00.000Z"],
["defer", "update:stackchain/web:8:92", "2026-08-09T09:00:00.000Z"],
],
"later": [
[91, "2026-08-09T09:00:00.000Z"],
[92, "2026-08-09T09:00:00.000Z"],
],
"unchanged": True,
}
def test_later_queue_prunes_missing_work_and_wakes_expired_items_without_reload():
script = f"""
const createLaterWork = require({json.dumps(str(LATER_WORK))});
const values = new Map();
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
let clock = new Date('2026-08-08T12:00:00Z');
let scheduled = null;
let wakes = 0;
const store = createLaterWork({{
storage, getLogin:() => 'timmy', now:() => clock,
setTimer:(callback, delay) => {{ scheduled = {{callback,delay}}; return 7; }},
clearTimer:() => {{}}, onWake:() => {{ wakes += 1; }},
}});
const kept = {{kind:'issue',repository:'stackchain/api',number:17,title:'Kept'}};
const gone = {{kind:'issue',repository:'stackchain/api',number:18,title:'Gone'}};
store.defer(kept, new Date('2026-08-08T13:00:00Z'));
store.defer(gone, new Date('2026-08-09T13:00:00Z'));
const before = store.partition([kept]);
const persisted = JSON.parse(values.get('stackchain.later-work.v1.timmy'));
clock = new Date('2026-08-08T13:00:01Z');
scheduled.callback();
const after = store.partition([kept]);
process.stdout.write(JSON.stringify({{
before:{{active:before.active.length,later:before.later.length}},
persisted:Object.keys(persisted), delay:scheduled.delay, wakes,
after:{{active:after.active.map(item => item.title),later:after.later.length}},
storageEmpty:!values.has('stackchain.later-work.v1.timmy'),
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"before": {"active": 0, "later": 1},
"persisted": ["issue:stackchain/api:17:"],
"delay": 3600000,
"wakes": 1,
"after": {"active": ["Kept"], "later": 0},
"storageEmpty": True,
}
def test_later_queue_preserves_rank_and_retains_unloaded_paginated_work():
script = f"""
const createLaterWork = require({json.dumps(str(LATER_WORK))});
const values = new Map();
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
const store = createLaterWork({{
storage,getLogin:() => 'timmy',now:() => new Date('2026-08-08T12:00:00Z'),
setTimer:() => 1, clearTimer:() => {{}},
}});
const first = {{kind:'issue',repository:'stackchain/api',number:1,title:'Higher priority'}};
const second = {{kind:'pull',repository:'stackchain/api',number:2,title:'Lower priority'}};
store.defer(second, new Date('2026-08-09T12:00:00Z'));
store.defer(first, new Date('2026-08-09T12:00:00Z'));
store.partition([first], {{pruneMissing:false}});
const visible = store.partition([first, second], {{pruneMissing:false}});
process.stdout.write(JSON.stringify({{
titles:visible.later.map(item => item.title),
records:Object.keys(JSON.parse(values.get('stackchain.later-work.v1.timmy'))).length,
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"titles": ["Higher priority", "Lower priority"],
"records": 2,
}
def test_later_queue_presets_and_bring_back_now_preserve_work_identity():
script = f"""
const createLaterWork = require({json.dumps(str(LATER_WORK))});
const values = new Map();
const storage = {{
getItem:key => values.get(key) || null,
setItem:(key,value) => values.set(key,value),
removeItem:key => values.delete(key),
}};
const now = new Date('2026-08-08T12:00:00Z');
const store = createLaterWork({{storage,getLogin:() => 'timmy',now:() => now}});
const item = {{kind:'update',repository:'stackchain/api',number:17,notification_id:91}};
const today = store.presetUntil('today');
const tomorrow = store.presetUntil('tomorrow');
store.defer(item, tomorrow);
const removed = store.restore(item);
process.stdout.write(JSON.stringify({{
today:today.toISOString(), tomorrow:tomorrow.toISOString(), removed,
partition:store.partition([item]),
}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True,
env={**os.environ, "TZ": "UTC"},
)
assert json.loads(result.stdout) == {
"today": "2026-08-08T16:00:00.000Z",
"tomorrow": "2026-08-09T09:00:00.000Z",
"removed": True,
"partition": {"active": [{
"kind": "update", "repository": "stackchain/api", "number": 17,
"notification_id": 91,
}], "later": []},
}
def test_starting_deferred_work_moves_it_to_today_before_opening_exact_item():
script = f"""
const createLaterAndStart = require({json.dumps(str(LATER_AND_START))});
const calls = [];
const item = {{kind:'issue',repository:'stackchain/api',number:17,title:'Resume me'}};
const controller = createLaterAndStart({{
todayWork: {{
identity:saved => saved.repository + '#' + saved.number,
add:saved => {{ calls.push(['today-add', saved.number]); return 'added'; }},
remove:saved => {{ calls.push(['today-remove', saved.number]); return true; }},
}},
todaySync: {{
enqueue:(action, identity) => {{ calls.push(['today-sync', action, identity]); return true; }},
flush:() => calls.push(['today-flush']),
}},
laterWork: {{restore:saved => {{ calls.push(['later-restore', saved.number]); return true; }}}},
refresh:() => calls.push(['refresh']),
warm:() => calls.push(['warm']),
start:saved => {{ calls.push(['start', saved.number]); return Promise.resolve('opened'); }},
announce:message => calls.push(['announce', message]),
}});
(async () => {{
const result = await controller.start(item);
process.stdout.write(JSON.stringify({{result,calls}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(
["node", "-e", script], capture_output=True, text=True
)
assert result.returncode == 0, result.stderr
assert json.loads(result.stdout) == {
"result": "started",
"calls": [
["today-add", 17],
["today-sync", "add", "stackchain/api#17"],
["later-restore", 17],
["refresh"],
["today-flush"],
["warm"],
["start", 17],
["announce", "Moved to Today and opened."],
],
}
def test_starting_deferred_work_keeps_later_when_today_is_full():
script = f"""
const createLaterAndStart = require({json.dumps(str(LATER_AND_START))});
const calls = [];
const controller = createLaterAndStart({{
todayWork: {{identity:() => 'issue:x/y:7', add:() => 'full', remove:() => calls.push('remove')}},
todaySync: {{enqueue:() => calls.push('enqueue'), flush:() => calls.push('flush')}},
laterWork: {{restore:() => calls.push('restore')}},
refresh:() => calls.push('refresh'), warm:() => calls.push('warm'),
start:() => calls.push('start'), announce:message => calls.push(message),
}});
(async () => {{
const result = await controller.start({{kind:'issue',repository:'x/y',number:7}});
process.stdout.write(JSON.stringify({{result,calls}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {
"result": "full",
"calls": ["Today is limited to 5 items. Remove one, then try Start now again."],
}
def test_starting_deferred_work_rolls_back_today_when_sync_admission_fails():
script = f"""
const createLaterAndStart = require({json.dumps(str(LATER_AND_START))});
const calls = [];
const item = {{kind:'pull',repository:'x/y',number:8}};
const controller = createLaterAndStart({{
todayWork: {{
identity:() => 'pull:x/y:8', add:() => {{ calls.push('add'); return 'added'; }},
remove:() => {{ calls.push('remove'); return true; }},
}},
todaySync: {{enqueue:() => {{ calls.push('enqueue'); return false; }}, flush:() => calls.push('flush')}},
laterWork: {{restore:() => calls.push('restore')}},
refresh:() => calls.push('refresh'), warm:() => calls.push('warm'),
start:() => calls.push('start'), announce:message => calls.push(message),
}});
(async () => {{
const result = await controller.start(item);
process.stdout.write(JSON.stringify({{result,calls}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {
"result": "sync-unavailable",
"calls": [
"add", "enqueue", "remove",
"Today sync is unavailable. The item remains in Later; try again.",
],
}
def test_starting_deferred_work_rolls_back_today_when_later_cannot_be_removed():
script = f"""
const createLaterAndStart = require({json.dumps(str(LATER_AND_START))});
const calls = [];
const item = {{kind:'review',repository:'x/y',number:9}};
const controller = createLaterAndStart({{
todayWork: {{
identity:() => 'review:x/y:9', add:() => 'added',
remove:() => {{ calls.push('remove'); return true; }},
}},
todaySync: {{
enqueue:(action, id) => {{ calls.push(['enqueue', action, id]); return true; }},
flush:() => calls.push('flush'),
}},
laterWork: {{restore:() => false}}, refresh:() => calls.push('refresh'),
warm:() => calls.push('warm'), start:() => calls.push('start'),
announce:message => calls.push(message),
}});
(async () => {{
const result = await controller.start(item);
process.stdout.write(JSON.stringify({{result,calls}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {
"result": "later-unavailable",
"calls": [
["enqueue", "add", "review:x/y:9"], "remove",
["enqueue", "remove", "review:x/y:9"], "flush",
"Could not remove this item from Later. Nothing was started; try again.",
],
}
def test_starting_deferred_work_reuses_existing_today_item_without_duplicate_sync():
script = f"""
const createLaterAndStart = require({json.dumps(str(LATER_AND_START))});
const calls = [];
const item = {{kind:'issue',repository:'x/y',number:10}};
const controller = createLaterAndStart({{
todayWork: {{identity:() => 'issue:x/y:10', add:() => 'exists', remove:() => calls.push('remove')}},
todaySync: {{enqueue:() => calls.push('enqueue'), flush:() => calls.push('flush')}},
laterWork: {{restore:() => {{ calls.push('restore'); return true; }}}},
refresh:() => calls.push('refresh'), warm:() => calls.push('warm'),
start:saved => calls.push(['start', saved.number]), announce:message => calls.push(message),
}});
(async () => {{
const result = await controller.start(item);
process.stdout.write(JSON.stringify({{result,calls}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {
"result": "started",
"calls": [
"restore", "refresh", "warm", ["start", 10],
"Opened the existing Today item.",
],
}
def test_starting_deferred_work_is_single_flight_for_repeated_taps():
script = f"""
const createLaterAndStart = require({json.dumps(str(LATER_AND_START))});
let adds = 0;
let finishStart;
const item = {{kind:'issue',repository:'x/y',number:11}};
const controller = createLaterAndStart({{
todayWork: {{identity:() => 'issue:x/y:11', add:() => {{ adds += 1; return 'added'; }}, remove:() => true}},
todaySync: {{enqueue:() => true, flush:() => {{}}}}, laterWork: {{restore:() => true}},
refresh:() => {{}}, warm:() => {{}}, announce:() => {{}},
start:() => new Promise(resolve => {{ finishStart = resolve; }}),
}});
const first = controller.start(item);
const second = controller.start(item);
if (adds !== 1) throw new Error('duplicate admission');
finishStart('opened');
(async () => {{
const results = await Promise.all([first, second]);
process.stdout.write(JSON.stringify({{adds,results}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {"adds": 1, "results": ["started", "started"]}
def test_starting_blocked_deferred_work_reports_readiness_gate_without_claiming_open():
script = f"""
const createLaterAndStart = require({json.dumps(str(LATER_AND_START))});
const messages = [];
const controller = createLaterAndStart({{
todayWork: {{identity:() => 'issue:x/y:12', add:() => 'added', remove:() => true}},
todaySync: {{enqueue:() => true, flush:() => {{}}}}, laterWork: {{restore:() => true}},
refresh:() => {{}}, warm:() => {{}}, start:() => Promise.resolve('gated'),
announce:message => messages.push(message),
}});
(async () => {{
const result = await controller.start({{kind:'issue',repository:'x/y',number:12}});
process.stdout.write(JSON.stringify({{result,messages}}));
}})().catch(error => {{ console.error(error); process.exit(1); }});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {
"result": "gated",
"messages": ["Moved to Today. Choose how to handle its blocker before starting."],
}
def test_detail_defer_closes_normal_triage_but_keeps_session_open_for_reconcile():
script = f"""
const createDetailDefer = require({json.dumps(str(DETAIL_DEFER))});
const calls = [];
let sessionActive = false;
const controller = createDetailDefer({{
laterWork: {{
presetUntil:preset => new Date(preset === 'today' ? '2026-08-08T16:00:00Z' : '2026-08-09T09:00:00Z'),
defer:(item, until) => {{ calls.push(['defer', item.title, until.toISOString()]); return 'deferred'; }},
}},
session: {{ active:() => sessionActive }},
close:() => calls.push(['close']),
refresh:() => calls.push(['refresh']),
focus:() => calls.push(['focus']),
announce:message => calls.push(['announce', message]),
formatTime:value => value.toISOString(),
}});
const item = {{kind:'issue',repository:'stackchain/api',number:17,title:'Read first'}};
const outside = controller.defer(item, 'today');
sessionActive = true;
const session = controller.defer(item, 'tomorrow');
process.stdout.write(JSON.stringify({{outside,session,calls}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"outside": True,
"session": True,
"calls": [
["defer", "Read first", "2026-08-08T16:00:00.000Z"],
["close"],
["refresh"],
["announce", "Deferred until 2026-08-08T16:00:00.000Z. It stays unread and unchanged in Gitea."],
["focus"],
["defer", "Read first", "2026-08-09T09:00:00.000Z"],
["refresh"],
["announce", "Deferred until 2026-08-09T09:00:00.000Z. It stays unread and unchanged in Gitea."],
],
}
def test_detail_defer_reports_storage_failure_without_closing_or_refreshing():
script = f"""
const createDetailDefer = require({json.dumps(str(DETAIL_DEFER))});
const calls = [];
const controller = createDetailDefer({{
laterWork: {{
presetUntil:() => new Date('2026-08-08T16:00:00Z'),
defer:() => 'unavailable',
}},
session: {{active:() => false}},
close:() => calls.push('close'), refresh:() => calls.push('refresh'),
focus:() => calls.push('focus'), announce:message => calls.push(message),
}});
const saved = controller.defer({{kind:'issue',repository:'stackchain/api',number:17}}, 'today');
process.stdout.write(JSON.stringify({{saved,calls}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"saved": False,
"calls": ["Could not save Later on this device."],
}
def test_detail_defer_completes_checkpointed_today_item_after_saving_later():
script = f"""
const createDetailDefer = require({json.dumps(str(DETAIL_DEFER))});
const calls = [];
const item = {{kind:'issue',repository:'stackchain/api',number:17,title:'Read first'}};
const controller = createDetailDefer({{
laterWork: {{
presetUntil:() => new Date('2026-08-09T09:00:00Z'),
defer:(saved, until) => {{ calls.push(['defer', saved.title, until.toISOString()]); return 'deferred'; }},
restore:() => calls.push(['restore']),
}},
session: {{active:() => true, checkpointed:saved => saved === item}},
continueSession:saved => {{ calls.push(['continue', saved.title]); return true; }},
close:() => calls.push(['close']), refresh:() => calls.push(['refresh']),
focus:() => calls.push(['focus']), announce:message => calls.push(['announce', message]),
}});
const saved = controller.defer(item, 'tomorrow');
process.stdout.write(JSON.stringify({{saved,calls}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"saved": True,
"calls": [
["defer", "Read first", "2026-08-09T09:00:00.000Z"],
["continue", "Read first"],
],
}
def test_detail_defer_rolls_back_later_when_today_cannot_be_removed():
script = f"""
const createDetailDefer = require({json.dumps(str(DETAIL_DEFER))});
const calls = [];
const item = {{kind:'issue',repository:'stackchain/api',number:17}};
const controller = createDetailDefer({{
laterWork: {{
presetUntil:() => new Date('2026-08-09T09:00:00Z'),
defer:() => {{ calls.push('defer'); return 'deferred'; }},
restore:saved => {{ calls.push(['restore', saved.number]); return true; }},
}},
session: {{active:() => true, checkpointed:() => true}},
continueSession:() => {{ calls.push('continue'); return false; }},
close:() => calls.push('close'), refresh:() => calls.push('refresh'),
focus:() => calls.push('focus'), announce:message => calls.push(['announce', message]),
}});
const saved = controller.defer(item, 'tomorrow');
process.stdout.write(JSON.stringify({{saved,calls}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"saved": False,
"calls": [
"defer",
"continue",
["restore", 17],
"refresh",
["announce", "Could not remove this item from Today, so it was restored from Later."],
],
}
@pytest.mark.anyio
async def test_mobile_detail_sheets_offer_touch_safe_defer_without_a_gitea_mutation():
html = await dashboard()
assert '' in html
assert html.count('class="detail-defer"') == 4
assert html.count('data-detail-defer-preset="today"') == 4
assert html.count('data-detail-defer-preset="tomorrow"') == 4
assert 'const detailDefer = createDetailDefer({' in html
assert 'selectedUpdate || selectedReview || selectedIssue || selectedPull' in html
assert "detailDefer.defer(item, button.dataset.detailDeferPreset)" in html
assert ".detail-defer summary, .detail-defer button { min-height:44px;" in html
assert "fetch(" not in DETAIL_DEFER.read_text()
@pytest.mark.anyio
async def test_checkpointed_detail_defer_completes_today_and_advertises_next_item():
html = await dashboard()
assert "continueSession: item => completeTodayItem(item, {" in html
assert "successMessage: 'Deferred to Later. Next Today item opened.'" in html
assert "advance: () => runTodayTransition('complete')" in html
assert "button.textContent = active ? 'Later today & next' : 'Later today'" in html
assert "button.textContent = active ? 'Tomorrow & next' : 'Tomorrow'" in html
assert "button.textContent = active ? 'Choose date & time & next' : 'Choose date & time'" in html
@pytest.mark.anyio
async def test_mobile_my_work_wires_touch_safe_non_mutating_later_actions():
html = await dashboard()
assert '' in html
assert 'data-work-filter="later"' in html
assert 'data-work-count="later"' in html
assert 'const laterWork = createLaterWork({' in html
assert 'getLogin: () => planningOwnerLogin' in html
assert 'laterWork.partition(actionableMyWork,' in html
assert 'data-later-preset="today"' in html
assert 'data-later-preset="tomorrow"' in html
assert 'data-later-restore' in html
assert "Deferred until ' + escapeHtml(fmt(item.deferred_until))" in html
assert "const result = laterWork.defer(item, until)" in html
assert 'laterWork.restore(item)' in html
assert '.later-actions button { min-height:44px;' in html
assert "'Deferred until ' + fmt(until)" in html
@pytest.mark.anyio
async def test_mobile_later_cards_start_exact_item_in_a_resumable_today_session():
html = await dashboard()
service_worker = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
assert '' in html
assert 'data-later-start' in html
assert '>Start now' in html
assert 'aria-label="Deferred work actions"' in html
assert "const laterAndStart = createLaterAndStart({" in html
assert "laterAndStart.start(item)" in html
assert "qs('[data-work-filter=\"today\"]').click();" in html
assert "todayReadiness.run('start', workSession.items(), item)" in html
assert "selectedWorkFilter === 'later' ? laterActions" in html
assert ".later-actions button { min-height:44px; width:100%; }" in html
assert "BASE + 'static/later-and-start.js'" in service_worker
@pytest.mark.anyio
async def test_mobile_later_actions_open_one_keyboard_safe_exact_time_dialog():
html = await dashboard()
assert '' in html
assert html.count('" in html
assert ">Copy feedback" in html
assert "reviewOutbox && item.status === 'authorization'" in html
assert ">Authorize & send review" in html
assert "Review submitted and queued intent cleared." in html
assert "item.kind === 'authored-outbox' && !reviewOutbox" in html
@pytest.mark.anyio
async def test_review_sheet_loads_details_and_preserves_safe_gitea_handoff():
html = await dashboard()
assert 'data-review-index' in html
assert "reviewController.load(selectedReview)" in html
assert "review-files" in html
assert "review-history" in html
assert "open-review-gitea" in html
assert 'id="submit-review"' in html
assert 'id="review-submit-status"' in html and 'aria-live="assertive"' in html
assert "reviewController.submit(selectedReview" in html
assert "window.confirm" in html
assert "expected_head_sha: selectedReviewHead" in html
assert "await load()" in html
@pytest.mark.anyio
async def test_mobile_review_sheet_captures_and_safely_hands_off_feedback():
html = await dashboard()
review_script = REVIEW_SHEET.read_text()
assert 'class="review-note"' in review_script
assert 'id="review-decision"' in html
assert 'id="review-summary"' in html
assert 'id="copy-review-feedback"' in html
assert 'id="review-copy-fallback"' in html
assert 'id="review-handoff-link"' in html
assert '.review-handoff-link' in html and 'min-height:44px' in html
assert '.review-handoff-link[hidden]' in html and 'display:none' in html
assert '.review-note' in html and 'min-height:88px' in html
assert '.review-handoff' in html and 'position:sticky' in html
assert "createReviewController.createDraft" in html
assert "draft.setNote" in html
assert "draft?.setSummary" in html
assert "draft?.setDecision" in html
assert "createReviewController.formatFeedback" in html
assert "createReviewController.copyAndContinue" in html
assert "navigator.clipboard.writeText" in html
assert "window.open('about:blank', '_blank')" in html
assert "handoffWindow.opener = null" in html
@pytest.mark.anyio
async def test_mobile_review_sheet_edits_inline_drafts_and_submits_them_with_review():
html = await dashboard()
assert 'id="review-inline-composer"' in html
assert 'id="review-inline-body"' in html and 'maxlength="10000"' in html
assert 'id="save-inline-comment"' in html
assert 'id="delete-inline-comment"' in html
assert '.review-inline-target' in html and 'min-height:44px' in html
assert '.review-inline-composer' in html and 'position:sticky' in html
assert "document.querySelectorAll('.review-inline-target')" in html
assert "draft.setInlineComment" in html
assert "draft.removeInlineComment" in html
assert "comments: snapshot.comments" in html
def test_review_controller_submits_once_while_request_is_in_flight():
script = f"""
const createReviewController = require({json.dumps(str(REVIEW_SHEET))});
let resolveRequest;
const calls = [];
const controller = createReviewController({{ createOperationId: () => 'review-op-test', fetchJson: (url, options) => {{
calls.push({{url, options}});
return new Promise(resolve => {{ resolveRequest = resolve; }});
}} }});
const item = {{repository:'stackchain/api', number:7}};
const payload = {{decision:'approve', body:'Looks good.', expected_head_sha:'abc123'}};
const first = controller.submit(item, payload);
const second = controller.submit(item, payload);
resolveRequest({{id:91, state:'APPROVED'}});
Promise.all([first, second]).then(results => process.stdout.write(JSON.stringify({{
calls, results
}})));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
payload = json.loads(result.stdout)
assert len(payload["calls"]) == 1
assert payload["calls"][0] == {
"url": "api/v1/repos/stackchain/api/pulls/7/review",
"options": {
"method": "POST",
"headers": {
"Accept": "application/json",
"Content-Type": "application/json",
"Idempotency-Key": "review-op-test",
},
"body": json.dumps(
{
"decision": "approve",
"body": "Looks good.",
"expected_head_sha": "abc123",
},
separators=(",", ":"),
),
},
}
assert payload["results"] == [
{"id": 91, "state": "APPROVED"},
{"id": 91, "state": "APPROVED"},
]
def test_successful_review_can_clear_head_scoped_draft_and_progress():
script = f"""
const reviewSheet = require({json.dumps(str(REVIEW_SHEET))});
const values = new Map();
const removed = [];
const storage = {{
getItem: key => values.get(key) || null,
setItem: (key, value) => values.set(key, value),
removeItem: key => {{ removed.push(key); values.delete(key); }},
}};
const options = {{storage, repository:'stackchain/api', number:7, headSha:'abc123', files:[{{filename:'a.py'}}]}};
const draft = reviewSheet.createDraft(options);
const progress = reviewSheet.createProgress(options);
draft.setSummary('Looks good.');
progress.markReviewed('a.py');
draft.clear();
progress.clear();
process.stdout.write(JSON.stringify({{removed, draft:draft.snapshot(), progress:progress.snapshot()}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
payload = json.loads(result.stdout)
assert payload["removed"] == [
"stackchain.review-draft.v1:stackchain/api#7@abc123",
"stackchain.review-progress.v1:stackchain/api#7@abc123",
]
assert payload["draft"] == {
"notes": {}, "comments": [], "summary": "", "decision": "comment"
}
assert payload["progress"]["reviewedCount"] == 0
def test_approved_assigned_review_carries_exact_head_progress_into_merge():
script = f"""
const reviewSheet = require({json.dumps(str(REVIEW_SHEET))});
const values = new Map();
const storage = {{
getItem: key => values.get(key) || null,
setItem: (key, value) => values.set(key, value),
}};
const assigned = {{
repository:'stackchain/api', number:7,
work_reasons:['assigned_to_me', 'review_requested']
}};
const carried = reviewSheet.prepareMergeContinuation({{
storage, item:assigned, headSha:'abc123',
reviewed:['a.py', 'b.py'], decision:'approve'
}});
const unassigned = reviewSheet.prepareMergeContinuation({{
storage, item:{{...assigned, number:8, work_reasons:['review_requested']}},
headSha:'def456', reviewed:['c.py'], decision:'approve'
}});
const changesRequested = reviewSheet.prepareMergeContinuation({{
storage, item:{{...assigned, number:9}}, headSha:'ghi789',
reviewed:['d.py'], decision:'request_changes'
}});
process.stdout.write(JSON.stringify({{carried, unassigned, changesRequested, entries:[...values.entries()]}}));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"carried": True,
"unassigned": False,
"changesRequested": False,
"entries": [[
"stackchain.pull-review.v1:stackchain/api#7:abc123",
'["a.py","b.py"]',
]],
}
@pytest.mark.anyio
async def test_successful_assigned_approval_continues_to_merge_in_place():
html = await dashboard()
assert 'id="continue-review-to-merge"' in html
assert '#continue-review-to-merge[hidden]' in html and 'display:none' in html
assert '.review-merge-continuation' in html and 'position:sticky' in html
assert '#continue-review-to-merge { min-height:44px;' in html
assert "createReviewController.prepareMergeContinuation" in html
assert "reviewed: progressSnapshot.reviewed" in html
assert "decision: snapshot.decision" in html
assert "workRoute.open({ ...item, kind:'pull', is_review:false }, { replace:true })" in html
assert "qs('#continue-review-to-merge').hidden = false" in html
assert "qs('#continue-review-to-merge').focus()" in html
@pytest.mark.anyio
async def test_mobile_review_failure_offers_an_in_place_retry_for_the_same_item():
html = await dashboard()
assert 'id="retry-review-load"' in html
assert '.review-retry' in html and 'min-height:44px' in html
assert "qs('#retry-review-load').hidden = false" in html
assert "qs('#retry-review-load').hidden = true" in html
assert "openReviewSheet(selectedReview, reviewTrigger)" in html
assert "qs('#retry-review-load').focus()" in html
@pytest.mark.anyio
async def test_mobile_update_sheet_renders_and_pages_the_complete_conversation():
html = await dashboard()
update_sheet = html[html.index('id="update-sheet"'):html.index('id="pull-sheet"')]
assert 'Full conversation
' in update_sheet
assert 'id="update-comments"' in update_sheet
assert 'id="load-older-update-comments"' in update_sheet
assert 'id="update-conversation-status"' in update_sheet
assert '.conversation-more' in html and 'min-height:44px' in html
assert "'/conversation?limit=20' + pageQuery" in html
assert 'id="retry-update-conversation"' in update_sheet
assert "notificationReader.retryConversation()" in html
assert "loadConversation: fetchNotificationConversation" in html
assert "onConversation: renderUpdateConversation" in html
assert "notificationReader.loadOlder()" in html
assert "notificationReader.appendReply(result)" in html
def test_issue_comment_reuses_operation_key_after_reload_until_success():
script = f"""
const createIssueSheet = require({json.dumps(str(ISSUE_SHEET))});
const values = new Map();
const storage = {{getItem:k => values.get(k) || null, setItem:(k,v) => values.set(k,v), removeItem:k => values.delete(k)}};
const calls = [];
const item = {{repository:'stackchain/api', number:7}};
const first = createIssueSheet({{storage, createOperationId:() => 'issue-op-185', fetchJson:(_u,o) => {{calls.push(o.headers['Idempotency-Key']); return Promise.reject(new Error('timeout'));}}}});
first.comment(item, 'Ship it').catch(() => {{
const second = createIssueSheet({{storage, createOperationId:() => 'wrong-key', fetchJson:(_u,o) => {{calls.push(o.headers['Idempotency-Key']); return Promise.resolve({{id:82}});}}}});
second.comment(item, 'Ship it').then(() => process.stdout.write(JSON.stringify({{calls, keys:[...values.keys()]}})));
}});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {"calls": ["issue-op-185", "issue-op-185"], "keys": []}
def test_pull_comment_reuses_operation_key_after_reload_until_success():
script = f"""
const createPullSheet = require({json.dumps(str(PULL_SHEET))});
const values = new Map();
const storage = {{getItem:k => values.get(k) || null, setItem:(k,v) => values.set(k,v), removeItem:k => values.delete(k)}};
const calls = [];
const item = {{repository:'stackchain/api', number:7}};
const first = createPullSheet({{storage, createOperationId:() => 'pull-op-185', fetchJson:(_u,o) => {{calls.push(o.headers['Idempotency-Key']); return Promise.reject(new Error('timeout'));}}}});
first.comment(item, 'Ship it').catch(() => {{
createPullSheet({{storage, createOperationId:() => 'wrong-key', fetchJson:(_u,o) => {{calls.push(o.headers['Idempotency-Key']); return Promise.resolve({{id:91}});}}}}).comment(item, 'Ship it')
.then(() => process.stdout.write(JSON.stringify({{calls, keys:[...values.keys()]}})));
}});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {"calls": ["pull-op-185", "pull-op-185"], "keys": []}
def test_notification_reply_reuses_persisted_operation_key():
script = f"""
const build = require({json.dumps(str(MY_WORK))});
const values = new Map();
const storage = {{getItem:k => values.get(k) || null, setItem:(k,v) => values.set(k,v), removeItem:k => values.delete(k)}};
const calls = [];
const item = {{notification_id:42}};
const first = build.createNotificationReplier({{storage, onStatus:()=>{{}}, createOperationId:() => 'reply-op-185', post:(_id,_body,key) => {{calls.push(key); return Promise.reject(new Error('timeout'));}}}});
first.submit(item, 'Retry').then(() => {{
const second = build.createNotificationReplier({{storage, onStatus:()=>{{}}, createOperationId:() => 'wrong-key', post:(_id,_body,key) => {{calls.push(key); return Promise.resolve({{id:91}});}}}});
second.submit(item, 'Retry').then(() => process.stdout.write(JSON.stringify({{calls, keys:[...values.keys()]}})));
}});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {"calls": ["reply-op-185", "reply-op-185"], "keys": []}
def test_review_submission_reuses_persisted_operation_key():
script = f"""
const createReviewController = require({json.dumps(str(REVIEW_SHEET))});
const values = new Map();
const storage = {{getItem:k => values.get(k) || null, setItem:(k,v) => values.set(k,v), removeItem:k => values.delete(k)}};
const calls = [];
const item = {{repository:'stackchain/api', number:7}};
const payload = {{decision:'approve', body:'Good', expected_head_sha:'abc', comments:[]}};
const first = createReviewController({{storage, createOperationId:() => 'review-op-185', fetchJson:(_u,o) => {{calls.push(o.headers['Idempotency-Key']); return Promise.reject(new Error('timeout'));}}}});
first.submit(item, payload).catch(() => {{
createReviewController({{storage, createOperationId:() => 'wrong-key', fetchJson:(_u,o) => {{calls.push(o.headers['Idempotency-Key']); return Promise.resolve({{id:93}});}}}}).submit(item, payload)
.then(() => process.stdout.write(JSON.stringify({{calls, keys:[...values.keys()]}})));
}});
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {"calls": ["review-op-185", "review-op-185"], "keys": []}
def test_filed_queue_contains_authored_issues_and_deduplicates_self_assigned_filings():
payload = {
"user": {"login": "timmy"},
"issues": [
{"id": 1, "repository": "stackchain/api", "number": 1, "title": "Delegated", "assignees": ["alex"], "work_reasons": ["created_by_me"]},
{"id": 2, "repository": "stackchain/api", "number": 2, "title": "Mine", "assignees": ["timmy"], "work_reasons": ["created_by_me"]},
{"id": 3, "repository": "stackchain/api", "number": 3, "title": "Assigned only", "assignees": ["timmy"]},
],
"pull_requests": [],
}
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const items = buildMyWork({json.dumps(payload)});
process.stdout.write(JSON.stringify({{
filed: buildMyWork.filterMyWork(items, 'filed').map(item => item.number).sort((a,b) => a-b),
count: buildMyWork.countMyWork(items).filed,
identities: items.map(item => item.key).sort(),
}}));
"""
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
assert json.loads(result.stdout) == {
"filed": [1, 2], "count": 2,
"identities": ["stackchain/api#1", "stackchain/api#2", "stackchain/api#3"],
}