feat: open saved Today details offline (#345)
CI / lint (pull_request) Successful in 36s
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-08 22:03:28 +00:00
1 parent e78ceb1bf7
commit a2ba624dc2
10 files changed
+214 -21

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-v38" in worker
assert "stackchain-dashboard-shell-v39" 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-v38" in worker
assert "stackchain-dashboard-shell-v39" in worker
+1 -1
View File
@@ -829,7 +829,7 @@ async def test_opening_issue_defers_planning_requests_until_disclosure_expands()
assert 'loadIssueLabelEditor(' not in open_handler
assert 'loadIssueMilestoneEditor(' not in open_handler
assert "if (qs('#issue-planning').open) loadIssuePlanning();" 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
+51
View File
@@ -87,6 +87,36 @@ process.stdout.write(JSON.stringify({enabled:store.enabled(), loaded:store.load(
assert result == {"enabled": False, "loaded": None}
def test_opted_in_detail_cache_is_allowlisted_account_bound_and_bounded():
result = run_scenario("""
const store = createOfflineWorkStore({storage, now:() => new Date('2026-08-07T12:00:00Z'), maxDetails:2});
store.setEnabled(true);
const issue = {kind:'issue', repository:'stackchain/dashboard', number:8};
store.saveDetail('timmy', issue, {
title:'Ship offline work', body:'Safe description', state:'open', labels:['P0'], assignees:['timmy'],
url:'https://forge.example/issues/8', token:'secret', files:[{patch:'private diff'}],
conversation:{comments:[{id:1, author:'alexander', body:'Newest message', created_at:'2026-08-07T11:00:00Z',
token:'comment-secret'}], page:1, older_page:null, total:1},
});
store.saveDetail('timmy', {kind:'pull', repository:'stackchain/dashboard', number:9}, {title:'PR 9', body:'Body 9'});
store.saveDetail('timmy', {kind:'issue', repository:'stackchain/dashboard', number:10}, {title:'Issue 10', body:'Body 10'});
const raw = [...values.values()].join(' ');
process.stdout.write(JSON.stringify({
evicted:store.loadDetail('timmy', issue),
loaded:store.loadDetail('timmy', {kind:'pull', repository:'stackchain/dashboard', number:9}),
wrongUser:store.loadDetail('alexander', {kind:'pull', repository:'stackchain/dashboard', number:9}),
raw,
}));
""")
assert result["evicted"] is None
assert result["wrongUser"] is None
assert result["loaded"]["title"] == "PR 9"
assert result["loaded"]["saved_at"] == "2026-08-07T12:00:00.000Z"
assert "secret" not in result["raw"]
assert "private diff" not in result["raw"]
@pytest.mark.anyio
async def test_dashboard_offers_private_offline_work_controls_and_read_only_hydration():
html = await dashboard()
@@ -114,3 +144,24 @@ async def test_initial_http_outage_hydrates_saved_work_and_recovers_on_live_snap
assert "Live details and actions will return automatically." in html
assert "function renderLiveSnapshot(snapshot, changedSections" in html
assert "setOfflineWorkMode(false);\n offlineStatus.hidden = true;" in html
@pytest.mark.anyio
async def test_saved_today_details_open_offline_without_enabling_server_state_actions():
html = await dashboard()
assert "offlineWorkStore.loadDetail(offlineLogin, item)" in html
assert "Details not saved—reconnect to open this item." in html
assert "openIssueSheet(item, trigger, savedDetail)" in html
assert "openPullSheet(item, trigger, savedDetail)" in html
assert "offlineWorkStore.saveDetail(confirmedOwnerLogin, item, detail)" in html
assert "todayWork.contains(item)" in html
assert "Offline copy · saved " in html
assert "setOfflineDetailControls('issue')" in html
assert "setOfflineDetailControls('pull')" in html
assert "qs('#issue-planning').inert = true;" in html
assert "qs('#issue-handoff').inert = true;" in html
assert "qs('#pull-review').inert = true;" in html
assert "qs('#issue-planning').inert = false;" in html
assert "qs('#pull-review').inert = false;" in html
assert "confirmedOwnerLogin = String(saved.user?.login || '').trim();" in html
+9
View File
@@ -32,3 +32,12 @@ def test_readme_documents_liveness_and_gitea_readiness_checks():
assert "`/readyz`" in text
assert "does not contact Gitea" in text
assert "HTTP 503" in text
def test_readme_documents_bounded_offline_today_details_and_safe_actions():
text = " ".join(README.read_text().split())
assert "opened Today issue or pull request" in text
assert "newest 20 comments" in text
assert "comments can enter the account-bound durable outbox" in text
assert "planning, assignment, review, merge, and close controls remain disabled" in text
+3 -3
View File
@@ -97,7 +97,7 @@ async function dispatchNotificationClick(route) {{
def test_share_target_sign_in_fix_ships_in_a_new_shell_cache():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v38" in source
assert "stackchain-dashboard-shell-v39" in source
assert "BASE + 'static/dashboard.css'" in source
assert "BASE + 'static/dashboard.js'" in source
assert "BASE + 'static/install-app.js'" in source
@@ -106,14 +106,14 @@ def test_share_target_sign_in_fix_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-v38" in source
assert "stackchain-dashboard-shell-v39" 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-v38" in source
assert "stackchain-dashboard-shell-v39" in source
assert "BASE + 'static/update-ownership.js'" in source