fix: retire completed Today work across devices (#359)
CI / lint (pull_request) Successful in 40s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-09 01:16:26 +00:00
1 parent 4610afd489
commit d940e0b37b
10 files changed
+117 -10

No files matched your search

+1 -1
View File
@@ -137,4 +137,4 @@ def test_markdown_work_bodies_are_mobile_safe_block_containers():
assert ".markdown-content { min-width:0; max-width:100%; overflow-wrap:anywhere;" in css
assert ".markdown-content pre { max-width:100%; overflow-x:auto;" in css
assert ".markdown-content a { min-height:44px;" in css
assert "stackchain-dashboard-shell-v45" in worker
assert "stackchain-dashboard-shell-v46" in worker
+1 -1
View File
@@ -35,4 +35,4 @@ def test_offline_shell_contains_every_local_dashboard_runtime_asset():
shell_assets = set(re.findall(r"BASE \+ '([^']+)'", worker.split("async function sessionCsrf", 1)[0]))
assert local_assets <= shell_assets, f"Offline shell is missing: {sorted(local_assets - shell_assets)}"
assert "stackchain-dashboard-shell-v45" in worker
assert "stackchain-dashboard-shell-v46" in worker
+3 -3
View File
@@ -108,7 +108,7 @@ async function dispatchNotificationClick(route) {{
def test_navigation_deadline_ships_in_a_new_shell_cache():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v45" in source
assert "stackchain-dashboard-shell-v46" in source
assert "BASE + 'static/dashboard.css'" in source
assert "BASE + 'static/dashboard.js'" in source
assert "BASE + 'static/install-app.js'" in source
@@ -117,14 +117,14 @@ def test_navigation_deadline_ships_in_a_new_shell_cache():
def test_mobile_search_viewport_ships_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v45" in source
assert "stackchain-dashboard-shell-v46" in source
assert "BASE + 'static/mobile-search-viewport.js'" in source
def test_update_ownership_flow_ships_atomically_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v45" in source
assert "stackchain-dashboard-shell-v46" in source
assert "BASE + 'static/update-ownership.js'" in source
+31
View File
@@ -87,6 +87,37 @@ sync.enqueue('move', 'issue:r:2:', 'up');
}
def test_duplicate_pending_retirements_collapse_to_one_effective_remove():
script = f"""
const createTodaySync = require({json.dumps(str(TODAY_SYNC))});
const values = new Map();
let sequence = 0;
const statuses = [];
const sync = createTodaySync({{
storage: {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}},
getLogin: () => 'timmy', createOperationId: () => 'remove-' + (++sequence),
fetchJson: async () => ({{revision:0,ids:[]}}), onRemoteIds:()=>{{}}, onStatus:value=>statuses.push(value),
}});
const first = sync.enqueue('remove', 'issue:r:1:');
const duplicate = sync.enqueue('remove', 'issue:r:1:');
process.stdout.write(JSON.stringify({{first, duplicate, pending:sync.pending(), statuses}}));
"""
result = json.loads(
subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True).stdout
)
assert result == {
"first": True,
"duplicate": True,
"pending": [{
"operation_id": "remove-1",
"action": "remove",
"item_id": "issue:r:1:",
"direction": None,
}],
"statuses": ["pending", "pending"],
}
def test_existing_device_queue_is_migrated_only_once():
script = f"""
const createTodaySync = require({json.dumps(str(TODAY_SYNC))});
+54
View File
@@ -78,6 +78,55 @@ process.stdout.write(JSON.stringify([
]
def test_authoritative_reconciliation_reports_retired_ids_before_local_prune():
script = f"""
const createTodayWork = require({json.dumps(str(TODAY_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 queue = createTodayWork({{storage, getLogin: () => 'timmy'}});
const item = number => ({{kind:'issue', repository:'r', number}});
queue.add(item(1));
queue.add(item(2));
const reports = [];
const visible = queue.reconcile([item(2)], {{
pruneMissing: true,
onPrune: retiredIds => reports.push({{retiredIds, idsDuringReport: queue.read()}}),
}});
queue.reconcile([item(2)], {{
pruneMissing: true,
onPrune: retiredIds => reports.push({{retiredIds, idsDuringReport: queue.read()}}),
}});
process.stdout.write(JSON.stringify({{reports, visible:visible.map(entry => entry.number), stored:queue.read()}}));
"""
assert json.loads(run_node(script)) == {
"reports": [{
"retiredIds": ["issue:r:1:"],
"idsDuringReport": ["issue:r:1:", "issue:r:2:"],
}],
"visible": [2],
"stored": ["issue:r:2:"],
}
def test_failed_retirement_queue_keeps_local_identity_for_retry():
script = f"""
const createTodayWork = require({json.dumps(str(TODAY_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 queue = createTodayWork({{storage, getLogin: () => 'timmy'}});
const item = number => ({{kind:'issue', repository:'r', number}});
queue.add(item(1));
const visible = queue.reconcile([], {{pruneMissing:true, onPrune:() => false}});
process.stdout.write(JSON.stringify({{visible, stored:queue.read()}}));
"""
assert json.loads(run_node(script)) == {
"visible": [],
"stored": ["issue:r:1:"],
}
def test_today_queue_adopts_bounded_server_order():
script = f"""
const createTodayWork = require({json.dumps(str(TODAY_WORK))});
@@ -115,6 +164,11 @@ async def test_dashboard_runs_the_curated_today_queue_as_a_mobile_work_flow():
assert "todaySync.enqueue('add'" in html
assert "todaySync.enqueue('remove'" in html
assert "todaySync.enqueue('move'" in html
assert "const authoritativeTodayReconciliation = liveMode && hasContextSnapshot &&" in html
assert "!lastContextSnapshot?.error &&" in html
assert "onPrune: retiredIds =>" in html
assert "retiredIds.map(id => todaySync.enqueue('remove', id)).every(Boolean)" in html
assert "if (queued) todaySync.flush();" in html
assert "todaySync.flush();" in html
assert "Another device filled Today · showing its saved plan." in html