feat: converge Today sync across active devices (#361)
CI / lint (pull_request) Successful in 42s
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 02:01:55 +00:00
1 parent 7ec3980713
commit 3c743a2b71
9 files changed
+217 -11

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-v46" in worker
assert "stackchain-dashboard-shell-v47" 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-v46" in worker
assert "stackchain-dashboard-shell-v47" in worker
+10 -3
View File
@@ -108,23 +108,30 @@ async function dispatchNotificationClick(route) {{
def test_navigation_deadline_ships_in_a_new_shell_cache():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v46" in source
assert "stackchain-dashboard-shell-v47" in source
assert "BASE + 'static/dashboard.css'" in source
assert "BASE + 'static/dashboard.js'" in source
assert "BASE + 'static/install-app.js'" in source
def test_today_convergence_ships_in_a_new_shell_cache():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v47" in source
assert "BASE + 'static/today-sync.js'" in source
def test_mobile_search_viewport_ships_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v46" in source
assert "stackchain-dashboard-shell-v47" 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-v46" in source
assert "stackchain-dashboard-shell-v47" in source
assert "BASE + 'static/update-ownership.js'" in source
+136
View File
@@ -171,3 +171,139 @@ sync.enqueue('add', '6');
"ids": ["1", "2", "3", "4", "5"],
"status": "full",
}
def test_tabs_ignore_an_older_response_after_a_newer_revision_is_broadcast():
script = f"""
const createTodaySync = require({json.dumps(str(TODAY_SYNC))});
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 listeners = [];
const createChannel = () => ({{
addEventListener: (_name, listener) => listeners.push(listener),
postMessage: data => listeners.forEach(listener => listener({{data}})),
}});
let resolveOld;
let resolveNew;
const oldHistory = [];
const newHistory = [];
const oldTab = createTodaySync({{
storage, getLogin:()=> 'timmy', createChannel,
fetchJson:()=>new Promise(resolve=>{{resolveOld=resolve}}),
onRemoteIds:ids=>oldHistory.push(ids), onStatus:()=>{{}},
}});
const newTab = createTodaySync({{
storage, getLogin:()=> 'timmy', createChannel,
fetchJson:()=>new Promise(resolve=>{{resolveNew=resolve}}),
onRemoteIds:ids=>newHistory.push(ids), onStatus:()=>{{}},
}});
(async()=>{{
const oldFlush = oldTab.flush();
const newFlush = newTab.flush();
await Promise.resolve();
resolveNew({{revision:2,ids:['new']}});
await newFlush;
resolveOld({{revision:1,ids:['old']}});
await oldFlush;
process.stdout.write(JSON.stringify({{oldHistory,newHistory}}));
}})();
"""
result = json.loads(
subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True).stdout
)
assert result["oldHistory"][-1] == ["new"]
assert result["newHistory"][-1] == ["new"]
def test_returning_online_replays_pending_operations():
script = f"""
const createTodaySync = require({json.dumps(str(TODAY_SYNC))});
const values = new Map();
const handlers = {{}};
const requests = [];
let revision = 1;
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:()=> 'offline-add',
fetchJson:async (_url, options={{}})=>{{
requests.push(options.method || 'GET');
if (options.method) return {{revision:++revision,ids:['issue:r:1:']}};
return {{revision,ids:[]}};
}},
onRemoteIds:()=>{{}}, onStatus:()=>{{}},
}});
sync.enqueue('add', 'issue:r:1:');
sync.startLifecycle({{
window: {{addEventListener:(name, handler)=>{{handlers[name]=handler}}}},
document: {{addEventListener:()=>{{}}, hidden:false}},
}});
(async()=>{{
await handlers.online();
process.stdout.write(JSON.stringify({{requests,pending:sync.pending()}}));
}})();
"""
result = json.loads(
subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True).stdout
)
assert result == {"requests": ["GET", "PATCH"], "pending": []}
def test_foregrounding_a_stale_tab_refreshes_the_saved_plan():
script = f"""
const createTodaySync = require({json.dumps(str(TODAY_SYNC))});
const handlers = {{}};
const adopted = [];
const documentObject = {{
hidden:true,
addEventListener:(name, handler)=>{{handlers[name]=handler}},
}};
const sync = createTodaySync({{
storage: {{getItem:()=>null,setItem:()=>{{}},removeItem:()=>{{}}}},
getLogin:()=> 'timmy',
fetchJson:async ()=>({{revision:7,ids:['issue:r:7:']}}),
onRemoteIds:ids=>adopted.push(ids), onStatus:()=>{{}},
}});
sync.startLifecycle({{
window: {{addEventListener:()=>{{}}}}, document:documentObject,
}});
(async()=>{{
await handlers.visibilitychange();
documentObject.hidden = false;
await handlers.visibilitychange();
process.stdout.write(JSON.stringify(adopted));
}})();
"""
result = json.loads(
subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True).stdout
)
assert result == [["issue:r:7:"]]
def test_old_account_broadcasts_are_ignored_after_identity_changes():
script = f"""
const createTodaySync = require({json.dumps(str(TODAY_SYNC))});
let login = 'timmy';
let listener;
const adopted = [];
const sync = createTodaySync({{
storage: {{getItem:()=>null,setItem:()=>{{}},removeItem:()=>{{}}}},
getLogin:()=>login,
createChannel:()=>({{addEventListener:(_name, handler)=>{{listener=handler}},postMessage:()=>{{}},close:()=>{{}}}}),
fetchJson:async ()=>({{revision:1,ids:['timmy-plan']}}),
onRemoteIds:ids=>adopted.push(ids), onStatus:()=>{{}},
}});
(async()=>{{
await sync.flush();
login = 'alexander';
listener({{data:{{revision:2,ids:['timmy-secret']}}}});
process.stdout.write(JSON.stringify(adopted));
}})();
"""
result = json.loads(
subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True).stdout
)
assert result == [["timmy-plan"]]
+7
View File
@@ -173,6 +173,13 @@ async def test_dashboard_runs_the_curated_today_queue_as_a_mobile_work_flow():
assert "Another device filled Today · showing its saved plan." in html
@pytest.mark.anyio
async def test_dashboard_activates_today_lifecycle_convergence():
html = await dashboard()
assert "todaySync.startLifecycle({ window, document });" in html
@pytest.mark.anyio
async def test_retained_authenticated_context_keeps_local_planning_separate_from_fresh_delivery():
html = await dashboard()