feat: finish Today items without changing Gitea (#421)
CI / lint (pull_request) Successful in 43s
CI / build-release (pull_request) Successful in 4s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-09 19:37:40 +00:00
1 parent 95baa20eeb
commit 854d040e88
12 files changed
+143 -15

No files matched your search

+1 -1
View File
@@ -347,5 +347,5 @@ async def test_dashboard_syncs_every_later_change_and_exposes_account_status():
def test_later_sync_ships_atomically_in_the_offline_shell():
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
assert "stackchain-dashboard-shell-v67" in source
assert "stackchain-dashboard-shell-v68" in source
assert "BASE + 'static/later-sync.js'" in source
+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-v67" in worker
assert "stackchain-dashboard-shell-v68" 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-v67" in worker
assert "stackchain-dashboard-shell-v68" in worker
+1 -1
View File
@@ -168,6 +168,6 @@ async def test_plan_today_wires_cancel_back_and_success_through_overlay_history(
def test_plan_today_controller_is_available_in_the_offline_shell():
source = SERVICE_WORKER.read_text()
assert "stackchain-dashboard-shell-v67" in source
assert "stackchain-dashboard-shell-v68" in source
assert "BASE + 'static/plan-today.js'" in source
assert "BASE + 'static/plan-today-preview.js'" in source
+9 -8
View File
@@ -121,7 +121,7 @@ async function dispatchNotificationClick(route) {{
def test_resumable_today_session_ships_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v67" in source
assert "stackchain-dashboard-shell-v68" in source
assert "BASE + 'static/my-work.js'" in source
assert "BASE + 'static/dashboard.js'" in source
assert "BASE + 'static/dashboard.css'" in source
@@ -130,7 +130,7 @@ def test_resumable_today_session_ships_in_a_new_offline_shell():
def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v67" in source
assert "stackchain-dashboard-shell-v68" in source
assert "BASE + 'static/create-issue-sheet.js'" in source
assert "BASE + 'static/dashboard.js'" in source
@@ -138,14 +138,14 @@ def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
def test_exact_later_picker_ships_atomically_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v67" in source
assert "stackchain-dashboard-shell-v68" in source
assert "BASE + 'static/later-picker.js'" in source
def test_navigation_deadline_ships_in_a_new_shell_cache():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v67" in source
assert "stackchain-dashboard-shell-v68" in source
assert "BASE + 'static/dashboard.css'" in source
assert "BASE + 'static/dashboard.js'" in source
assert "BASE + 'static/install-app.js'" in source
@@ -154,21 +154,21 @@ def test_navigation_deadline_ships_in_a_new_shell_cache():
def test_today_convergence_ships_in_a_new_shell_cache():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v67" in source
assert "stackchain-dashboard-shell-v68" 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-v67" in source
assert "stackchain-dashboard-shell-v68" 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-v67" in source
assert "stackchain-dashboard-shell-v68" in source
assert "BASE + 'static/update-ownership.js'" in source
@@ -334,7 +334,7 @@ def test_one_session_bound_csrf_proof_is_reused_for_a_background_drain():
def test_queue_today_ships_atomically_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v67" in source
assert "stackchain-dashboard-shell-v68" in source
assert "BASE + 'static/queue-today.js'" in source
@@ -371,6 +371,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
"/dashboard/static/my-work.js",
"/dashboard/static/card-planning.js",
"/dashboard/static/today-work.js",
"/dashboard/static/today-completion.js",
"/dashboard/static/plan-today.js",
"/dashboard/static/plan-today-preview.js",
"/dashboard/static/today-sync.js",
+1 -1
View File
@@ -86,7 +86,7 @@ sync.enqueue('add', 'issue:r:1:');
def test_inflight_today_drain_ships_in_a_new_offline_shell():
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
assert "stackchain-dashboard-shell-v67" in source
assert "stackchain-dashboard-shell-v68" in source
assert "BASE + 'static/today-sync.js'" in source
+81
View File
@@ -8,6 +8,8 @@ from tests.dashboard_bundle import dashboard
TODAY_WORK = Path(__file__).parents[1] / "frontend" / "today-work.js"
TODAY_COMPLETION = Path(__file__).parents[1] / "frontend" / "today-completion.js"
SERVICE_WORKER = Path(__file__).parents[1] / "frontend" / "service-worker.js"
def run_node(script):
@@ -87,6 +89,69 @@ process.stdout.write(JSON.stringify([
]
def test_done_for_today_removes_current_plan_item_syncs_and_advances_without_gitea_mutation():
script = f"""
const createTodayCompletion = require({json.dumps(str(TODAY_COMPLETION))});
const calls = [];
const current = {{kind:'issue', repository:'stackchain/dashboard', number:421}};
const complete = createTodayCompletion({{
todayWork: {{
identity: item => `issue:${{item.repository}}:${{item.number}}:`,
remove: item => {{ calls.push(`remove:${{item.number}}`); return true; }},
}},
todaySync: {{
enqueue: (action, identity) => {{ calls.push(`sync:${{action}}:${{identity}}`); return true; }},
flush: () => calls.push('flush'),
}},
refresh: () => calls.push('refresh'),
warm: () => calls.push('warm'),
workSession: {{ complete: () => calls.push('advance') }},
announce: message => calls.push(`announce:${{message}}`),
}});
const completed = complete(current);
process.stdout.write(JSON.stringify({{completed, calls}}));
"""
assert json.loads(run_node(script)) == {
"completed": True,
"calls": [
"remove:421",
"sync:remove:issue:stackchain/dashboard:421:",
"flush",
"refresh",
"warm",
"advance",
"announce:Done for Today. The Gitea item is unchanged.",
],
}
def test_done_for_today_does_not_advance_when_local_plan_persistence_fails():
script = f"""
const createTodayCompletion = require({json.dumps(str(TODAY_COMPLETION))});
const calls = [];
const complete = createTodayCompletion({{
todayWork: {{identity: () => 'issue:r:1:', remove: () => false}},
todaySync: {{enqueue: () => calls.push('sync'), flush: () => calls.push('flush')}},
refresh: () => calls.push('refresh'),
warm: () => calls.push('warm'),
workSession: {{complete: () => calls.push('advance')}},
announce: message => calls.push(message),
}});
const completed = complete({{kind:'issue', repository:'r', number:1}});
process.stdout.write(JSON.stringify({{completed, calls}}));
"""
assert json.loads(run_node(script)) == {
"completed": False,
"calls": ["Could not update Today on this device. Try again."],
}
def test_done_for_today_logic_is_available_in_the_offline_app_shell():
assert "BASE + 'static/today-completion.js'" in SERVICE_WORKER.read_text()
def test_authoritative_reconciliation_reports_retired_ids_before_local_prune():
script = f"""
const createTodayWork = require({json.dumps(str(TODAY_WORK))});
@@ -183,6 +248,22 @@ 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_today_session_sheets_offer_a_touch_safe_plan_only_completion_action():
html = await dashboard()
assert '<script src="static/today-completion.js"></script>' in html
assert html.count('data-work-session-complete=') == 4
assert html.count('Done for Today &amp; next') == 4
assert "button.hidden = !workSession.checkpointed();" in html
assert "const completeTodayItem = createTodayCompletion({" in html
assert "refresh: () => refreshMyWorkView({ reconcileSession:false })," in html
assert "document.querySelectorAll('[data-work-session-complete]')" in html
assert "completeTodayItem(selectedSessionItem(button.dataset.workSessionComplete))" in html
assert ".work-session-nav button { min-height:44px; width:100%; }" in html
assert "grid-template-columns:repeat(3,minmax(0,1fr))" in html
@pytest.mark.anyio
async def test_dashboard_activates_today_lifecycle_convergence():
html = await dashboard()