feat: create and start captured issues (#387)
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 09:08:06 +00:00
1 parent cbdddcddbe
commit edbe2d76dd
14 files changed
+203 -19

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
import json
import subprocess
from pathlib import Path
CREATE_AND_START = Path(__file__).parents[1] / "frontend" / "create-and-start.js"
HTML = Path(__file__).parents[1] / "frontend" / "index.html"
DASHBOARD = Path(__file__).parents[1] / "frontend" / "dashboard.js"
CSS = Path(__file__).parents[1] / "frontend" / "dashboard.css"
WORKER = Path(__file__).parents[1] / "frontend" / "service-worker.js"
def run_node(script):
return json.loads(subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout)
def test_confirmed_issue_is_planned_synced_warmed_and_started_once():
script = f"""
const fs = require('fs');
if (!fs.existsSync({json.dumps(str(CREATE_AND_START))})) {{
process.stdout.write(JSON.stringify({{available:false}}));
}} else {{
const createCreateAndStart = require({json.dumps(str(CREATE_AND_START))});
const calls = [];
const issue = {{kind:'issue', repository:'stackchain/dashboard', number:387}};
const flow = createCreateAndStart({{
todayWork: {{limit:5, read:()=>[], identity:item=>'issue:'+item.repository+':'+item.number+':', add:()=>{{calls.push('add');return 'added';}}}},
todaySync: {{enqueue:(action,id)=>{{calls.push(action+':'+id);return true;}}, flush:()=>calls.push('flush')}},
refresh:()=>calls.push('refresh'), warm:()=>calls.push('warm'), start:item=>calls.push('start:'+item.number),
announce:message=>calls.push('announce:'+message),
}});
const result = flow.complete(issue);
flow.complete(issue);
process.stdout.write(JSON.stringify({{available:true,result,calls}}));
}}
"""
assert run_node(script) == {
"available": True,
"result": "started",
"calls": [
"add",
"add:issue:stackchain/dashboard:387:",
"refresh",
"flush",
"warm",
"start:387",
"announce:Created, added to Today, and ready to work.",
],
}
def test_mobile_capture_wires_distinct_create_and_start_intent_into_the_offline_shell():
html = HTML.read_text()
dashboard = DASHBOARD.read_text()
css = CSS.read_text()
worker = WORKER.read_text()
assert '<button id="create-and-start-issue" type="submit">Create &amp; start</button>' in html
assert '<script src="static/create-and-start.js"></script>' in html
assert "const createAndStart = createCreateAndStart({" in dashboard
assert "event.submitter?.id === 'create-and-start-issue'" in dashboard
assert "createAndStart.complete(created)" in dashboard
assert ".create-issue-actions" in css and "grid-template-columns" in css
assert "BASE + 'static/create-and-start.js'" in worker
def test_create_and_start_stops_before_session_when_today_sync_cannot_be_queued():
script = f"""
const createCreateAndStart = require({json.dumps(str(CREATE_AND_START))});
const calls = [];
const issue = {{kind:'issue', repository:'stackchain/dashboard', number:388}};
const flow = createCreateAndStart({{
todayWork: {{limit:5, read:()=>[], identity:()=> 'issue:stackchain/dashboard:388:', add:()=> 'added'}},
todaySync: {{enqueue:()=>false, flush:()=>calls.push('flush')}},
refresh:()=>calls.push('refresh'), warm:()=>calls.push('warm'), start:()=>calls.push('start'),
announce:message=>calls.push(message),
}});
process.stdout.write(JSON.stringify({{result:flow.complete(issue), calls}}));
"""
assert run_node(script) == {
"result": "sync-unavailable",
"calls": ["Created and saved to Today on this device, but account sync is unavailable."],
}
+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-v57" in source
assert "stackchain-dashboard-shell-v58" 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-v57" in worker
assert "stackchain-dashboard-shell-v58" 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-v57" in worker
assert "stackchain-dashboard-shell-v58" in worker
+23
View File
@@ -1376,6 +1376,29 @@ async def test_dashboard_wires_work_session_to_existing_sheet_flows_and_completi
assert opener in html
def test_work_session_can_start_at_a_newly_created_item():
script = f"""
const buildMyWork = require({json.dumps(str(MY_WORK))});
const items = [1, 2, 3].map(number => ({{kind:'issue', repository:'stackchain/dashboard', number}}));
const opened = [];
const session = buildMyWork.createWorkSession({{
getItems: () => items,
getFilter: () => 'all',
getMilestone: () => '',
onOpen: item => opened.push(item.number),
onProgress: () => {{}},
onFinish: () => {{}},
}});
const started = session.start(items[2]);
process.stdout.write(JSON.stringify({{started, opened}}));
"""
output = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
).stdout
assert json.loads(output) == {"started": True, "opened": [3]}
def test_my_work_filter_counts_distinguish_prs_from_review_requests():
items = [
{"kind": "issue", "is_review": False},
+1 -1
View File
@@ -101,5 +101,5 @@ async def test_mobile_dashboard_wires_focused_plan_today_sheet():
def test_plan_today_controller_is_available_in_the_offline_shell():
source = SERVICE_WORKER.read_text()
assert "stackchain-dashboard-shell-v57" in source
assert "stackchain-dashboard-shell-v58" in source
assert "BASE + 'static/plan-today.js'" in source
+7 -6
View File
@@ -108,7 +108,7 @@ async function dispatchNotificationClick(route) {{
def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
source = WORKER.read_text()
assert "stackchain-dashboard-shell-v57" in source
assert "stackchain-dashboard-shell-v58" in source
assert "BASE + 'static/create-issue-sheet.js'" in source
assert "BASE + 'static/dashboard.js'" in source
@@ -116,14 +116,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-v57" in source
assert "stackchain-dashboard-shell-v58" 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-v57" in source
assert "stackchain-dashboard-shell-v58" in source
assert "BASE + 'static/dashboard.css'" in source
assert "BASE + 'static/dashboard.js'" in source
assert "BASE + 'static/install-app.js'" in source
@@ -132,21 +132,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-v57" in source
assert "stackchain-dashboard-shell-v58" 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-v57" in source
assert "stackchain-dashboard-shell-v58" 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-v57" in source
assert "stackchain-dashboard-shell-v58" in source
assert "BASE + 'static/update-ownership.js'" in source
@@ -352,6 +352,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
"/dashboard/static/conversation.js",
"/dashboard/static/issue-sheet.js",
"/dashboard/static/create-issue-sheet.js",
"/dashboard/static/create-and-start.js",
"/dashboard/static/pull-sheet.js",
"/dashboard/static/review-sheet.js",
"/dashboard/static/work-route.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-v57" in source
assert "stackchain-dashboard-shell-v58" in source
assert "BASE + 'static/today-sync.js'" in source