'
) 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.",
}
@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 'hidden aria-describedby="update-sheet-status"' in html
assert 'const updateOwnership = createUpdateOwnership({' in html
assert 'updateOwnership.open(detail, selectedUpdate)' in html
assert "qs('#update-ownership-action').addEventListener('click'" 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
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:'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/pull/stackchain/dashboard/42",
"#/my-work/review/stackchain/dashboard/42",
"#/my-work/update/913",
],
"parsed": [
{"kind": "issue", "repository": "stackchain/api", "number": 17},
{"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_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_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
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_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_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_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_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 'issueCapture.loadMilestones(repository)' in html
assert "milestoneId: Number(qs('#create-issue-milestone').value) || null" in html
assert "dueDate: qs('#create-issue-due-date').value" 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: () => 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_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_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."],
}
@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_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(lastMyWork,' 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_actions_open_one_keyboard_safe_exact_time_dialog():
html = await dashboard()
assert '' in html
assert html.count('