feat: open saved Today details offline (#345)
This commit is contained in:
1 parent
e78ceb1bf7
commit
a2ba624dc2
10 files changed
+214
-21
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user