feat: preview work before planning (#405)
CI / lint (pull_request) Successful in 43s
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 14:22:37 +00:00
1 parent 461f90c8ff
commit 7a62f7ead7
13 files changed
+235 -17

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-v64" in source
assert "stackchain-dashboard-shell-v65" 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-v64" in worker
assert "stackchain-dashboard-shell-v65" 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-v64" in worker
assert "stackchain-dashboard-shell-v65" in worker
+57 -1
View File
@@ -8,6 +8,7 @@ from tests.dashboard_bundle import dashboard
PLAN_TODAY = Path(__file__).parents[1] / "frontend" / "plan-today.js"
PLAN_TODAY_PREVIEW = Path(__file__).parents[1] / "frontend" / "plan-today-preview.js"
SERVICE_WORKER = Path(__file__).parents[1] / "frontend" / "service-worker.js"
@@ -78,6 +79,53 @@ process.stdout.write(JSON.stringify({{result, calls, snapshot:planner.snapshot()
}
def test_plan_today_preview_preserves_draft_scroll_and_adds_item_once_on_return():
script = f"""
const createPlanToday = require({json.dumps(str(PLAN_TODAY))});
const createPlanTodayPreview = require({json.dumps(str(PLAN_TODAY_PREVIEW))});
const item = number => ({{kind:'issue', repository:'stackchain/dashboard', number, title:'Issue ' + number}});
const events = [];
let scroll = 318;
const planner = createPlanToday({{
identity: value => 'issue:stackchain/dashboard:' + value.number + ':',
save: () => true,
}});
planner.open([item(1)], [item(1), item(2)]);
const preview = createPlanTodayPreview({{
planner,
identity: value => 'issue:stackchain/dashboard:' + value.number + ':',
getScroll: () => scroll,
setScroll: value => {{ scroll = value; }},
onOpen: value => events.push(['open', value.number]),
onClose: (value, trigger) => events.push(['close', value.number, trigger]),
}});
preview.open(item(2), 'preview-2');
scroll = 0;
const first = preview.close({{add:true}});
preview.open(item(2), 'preview-2');
scroll = 0;
const second = preview.close({{add:true}});
process.stdout.write(JSON.stringify({{first, second, scroll, events, planner:planner.snapshot()}}));
"""
assert run_node(script) == {
"first": "added",
"second": "already-added",
"scroll": 318,
"events": [
["open", 2],
["close", 2, "preview-2"],
["open", 2],
["close", 2, "preview-2"],
],
"planner": {
"open": True,
"ids": ["issue:stackchain/dashboard:1:", "issue:stackchain/dashboard:2:"],
"count": 2,
"limit": 5,
},
}
@pytest.mark.anyio
async def test_mobile_dashboard_wires_focused_plan_today_sheet():
html = await dashboard()
@@ -96,6 +144,13 @@ async def test_mobile_dashboard_wires_focused_plan_today_sheet():
assert "padding-bottom:calc(12px + env(safe-area-inset-bottom))" in html
assert ".plan-today-item { grid-template-columns:1fr; }" in html
assert ".plan-today-item-actions { display:grid; grid-template-columns:repeat(3,1fr);" in html
assert ".plan-preview-actions { position:fixed; z-index:76;" in html
assert '<script src="static/plan-today-preview.js"></script>' in html
assert 'data-plan-preview="' in html
assert 'id="add-plan-preview"' in html
assert "taskOverlayHistory.open('plan-today-preview')" in html
assert "planTodayPreview.close({ add:true })" in html
assert "Preview unavailable offline" in html
@pytest.mark.anyio
@@ -113,5 +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-v64" in source
assert "stackchain-dashboard-shell-v65" in source
assert "BASE + 'static/plan-today.js'" in source
assert "BASE + 'static/plan-today-preview.js'" in source
+8 -7
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-v64" in source
assert "stackchain-dashboard-shell-v65" 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-v64" in source
assert "stackchain-dashboard-shell-v65" 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-v64" in source
assert "stackchain-dashboard-shell-v65" 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-v64" in source
assert "stackchain-dashboard-shell-v65" 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-v64" in source
assert "stackchain-dashboard-shell-v65" 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-v64" in source
assert "stackchain-dashboard-shell-v65" 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-v64" in source
assert "stackchain-dashboard-shell-v65" in source
assert "BASE + 'static/update-ownership.js'" in source
@@ -365,6 +365,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
"/dashboard/static/card-planning.js",
"/dashboard/static/today-work.js",
"/dashboard/static/plan-today.js",
"/dashboard/static/plan-today-preview.js",
"/dashboard/static/today-sync.js",
"/dashboard/static/update-ownership.js",
"/dashboard/static/later-work.js",
+38
View File
@@ -162,6 +162,44 @@ process.stdout.write(JSON.stringify({{
}
def test_plan_today_preview_is_a_nested_browser_history_layer():
script = f"""
const createTaskOverlayHistory = require({json.dumps(str(OVERLAY_HISTORY))});
const listeners = {{}};
const changes = [];
const stack = [{{ page:'dashboard' }}];
let cursor = 0;
const history = {{
get state() {{ return stack[cursor]; }},
pushState(state) {{ stack.splice(cursor + 1); stack.push(state); cursor += 1; }},
back() {{ cursor -= 1; listeners.popstate({{state:stack[cursor]}}); }},
}};
const controller = createTaskOverlayHistory({{
history,
eventTarget: {{ addEventListener(name, callback) {{ listeners[name] = callback; }} }},
onChange(kind, previous) {{ changes.push([kind, previous]); }},
}});
controller.start();
controller.open('plan-today');
const previewed = controller.open('plan-today-preview');
controller.close();
process.stdout.write(JSON.stringify({{previewed, cursor, current:controller.current(), changes}}));
"""
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
assert result.returncode == 0, result.stderr
assert json.loads(result.stdout) == {
"previewed": True,
"cursor": 1,
"current": "plan-today",
"changes": [
["plan-today", None],
["plan-today-preview", "plan-today"],
["plan-today", "plan-today-preview"],
],
}
def test_dashboard_routes_mobile_task_overlays_through_browser_history():
html = dashboard_bundle_text()
+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-v64" in source
assert "stackchain-dashboard-shell-v65" in source
assert "BASE + 'static/today-sync.js'" in source