feat: finish Today from the lock screen (Closes #1032)
CI / lint (pull_request) Successful in 3m8s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 2m15s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-17 17:02:54 +00:00
1 parent ca520d2eb6
commit 11395b655a
6 files changed
+211 -52

No files matched your search

+26 -5
View File
@@ -138,11 +138,11 @@ async function dispatchMessage(data, ports = []) {{
listeners.message({{ data, ports, waitUntil: promise => {{ pending = promise; }} }});
if (pending) await pending;
}}
async function dispatchNotificationClick(route, action = '', notificationId = null, tag = null, url = null) {{
async function dispatchNotificationClick(route, action = '', notificationId = null, tag = null, url = null, actionToken = '') {{
let pending;
listeners.notificationclick({{
action,
notification: {{tag: tag || ('stackchain-update-' + notificationId), data: {{route, notificationId, ...(url ? {{url}} : {{}})}}, close: () => {{ state.notificationClosed = true; }}}},
notification: {{tag: tag || ('stackchain-update-' + notificationId), data: {{route, notificationId, ...(url ? {{url}} : {{}}), ...(actionToken ? {{actionToken}} : {{}})}}, close: () => {{ state.notificationClosed = true; }}}},
waitUntil: promise => {{ pending = promise; }},
}});
if (pending) await pending;
@@ -507,7 +507,7 @@ def test_authenticated_resume_rearms_a_previously_purged_worker_before_flushing(
def test_active_today_message_replaces_one_privacy_safe_lock_screen_notification():
result = run_worker_scenario(
"""
await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:true,identity:'secret/repo#42'});
await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:true,identity:'secret/repo#42',actionToken:'opaque-token-1234567890'});
process.stdout.write(JSON.stringify(state));
"""
)
@@ -522,9 +522,9 @@ def test_active_today_message_replaces_one_privacy_safe_lock_screen_notification
"silent": True,
"actions": [
{"action": "pause-today", "title": "Pause"},
{"action": "open-today", "title": "Open Today"},
{"action": "finish-today", "title": "Finish current"},
],
"data": {"route": "#/my-work/today"},
"data": {"route": "#/my-work/today", "actionToken": "opaque-token-1234567890"},
},
}
]
@@ -589,6 +589,27 @@ def test_today_lock_screen_action_opens_a_validated_one_shot_route_when_closed()
]
def test_finish_today_lock_screen_action_forwards_only_its_opaque_token():
result = run_worker_scenario(
"""
state.clientMessages=[];
state.clientList=[{
url:'https://forge.example/dashboard/#/my-work/today',
postMessage:message=>state.clientMessages.push(message),
focus:async()=>state.focused.push('today'),
}];
await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890');
process.stdout.write(JSON.stringify(state));
"""
)
assert result["clientMessages"] == [{
"type": "stackchain-today-timer-action",
"action": "complete",
"actionToken": "opaque-token-1234567890",
}]
def test_background_mutation_abort_also_cancels_stalled_csrf_lookup():
result = run_worker_scenario(
"""
+49 -6
View File
@@ -25,8 +25,10 @@ const NotificationRef = {get permission(){return permission;},requestPermission:
const actions = [];
const locationRef = {href:'https://forge.example/dashboard/#/my-work/today'};
const historyRef = {replaceState:(_a,_b,url)=>{locationRef.href=new URL(url, locationRef.href).href;}};
let tokenCounter = 0;
const tokens = ['opaque-token-1234567890', 'new-opaque-token-0987654321'];
(async()=>{
const lockScreen = createTodayLockScreen({storage,getLogin:()=> 'Timmy',serviceWorker,NotificationRef,control,status,locationRef,historyRef,onAction:action=>actions.push(action)});
const lockScreen = createTodayLockScreen({storage,getLogin:()=> 'Timmy',serviceWorker,NotificationRef,control,status,locationRef,historyRef,randomToken:()=> tokens[tokenCounter++],fingerprint:async (_token, identity)=>identity.startsWith('issue:') ? 'opaque-issue-fingerprint' : 'opaque-pull-fingerprint',onAction:(action, identity)=>actions.push([action, identity])});
%SCENARIO%
})().catch(error=>{console.error(error);process.exit(1)});
""".replace("%SCENARIO%", scenario)
@@ -43,11 +45,15 @@ def test_operator_opt_in_syncs_one_privacy_safe_session_notification():
assert result["checked"] is True
assert result["status"] == "Lock-screen Today controls are on."
assert result["stored"] == [["stackchain.today-lock-screen.v1.timmy", "1"]]
assert result["stored"] == [
["stackchain.today-lock-screen.v1.timmy", "1"],
["stackchain.today-lock-screen-action.v1.timmy", '{"token":"opaque-token-1234567890","fingerprint":"opaque-issue-fingerprint"}'],
]
assert result["messages"][-1] == {
"type": "stackchain-today-lock-screen",
"active": True,
"running": True,
"actionToken": "opaque-token-1234567890",
}
assert "secret" not in json.dumps(result)
@@ -72,8 +78,8 @@ def test_valid_notification_action_is_consumed_once_and_removed_from_url():
result = run_scenario(r"""
values.set('stackchain.today-lock-screen.v1.timmy','1');
locationRef.href='https://forge.example/dashboard/?today_timer_action=pause#/my-work/today';
const consumed = lockScreen.consumeLaunchAction();
const second = lockScreen.consumeLaunchAction();
const consumed = await lockScreen.consumeLaunchAction();
const second = await lockScreen.consumeLaunchAction();
listeners['sw-message']({data:{type:'stackchain-today-timer-action',action:'resume'}});
process.stdout.write(JSON.stringify({consumed,second,actions,href:locationRef.href}));
""")
@@ -81,7 +87,41 @@ def test_valid_notification_action_is_consumed_once_and_removed_from_url():
assert result == {
"consumed": True,
"second": False,
"actions": ["pause", "resume"],
"actions": [["pause", None], ["resume", None]],
"href": "https://forge.example/dashboard/#/my-work/today",
}
def test_finish_action_is_bound_to_the_exact_active_identity_and_consumed_once():
result = run_scenario(r"""
await lockScreen.enable();
await lockScreen.sync({identity:'issue:private/repo:42:',running:true}, true);
const first = await lockScreen.consumeAction('complete', 'opaque-token-1234567890');
const replay = await lockScreen.consumeAction('complete', 'opaque-token-1234567890');
await lockScreen.sync({identity:'pull:private/repo:9:',running:true}, true);
const stale = await lockScreen.consumeAction('complete', 'opaque-token-1234567890');
process.stdout.write(JSON.stringify({first,replay,stale,actions,messages,stored:[...values.entries()]}));
""")
assert result["first"] is True
assert result["replay"] is False
assert result["stale"] is False
assert result["actions"] == [["complete", "issue:private/repo:42:"]]
assert all("private/repo" not in json.dumps(message) for message in result["messages"])
def test_cold_launch_finish_action_is_removed_from_history_before_completion():
result = run_scenario(r"""
await lockScreen.enable();
await lockScreen.sync({identity:'issue:private/repo:42:',running:true}, true);
locationRef.href='https://forge.example/dashboard/?today_timer_action=complete&today_action_token=opaque-token-1234567890#/my-work/today';
const consumed = await lockScreen.consumeLaunchAction();
process.stdout.write(JSON.stringify({consumed,actions,href:locationRef.href}));
""")
assert result == {
"consumed": True,
"actions": [["complete", "issue:private/repo:42:"]],
"href": "https://forge.example/dashboard/#/my-work/today",
}
@@ -111,7 +151,10 @@ def test_lock_screen_flow_is_wired_into_the_packaged_today_journey():
assert '<script src="static/today-lock-screen.js"></script>' in index
assert index.index('static/today-lock-screen.js') < index.index('static/dashboard.js')
assert 'createTodayLockScreen({' in dashboard
assert "action === 'pause' ? timer.pause() : timer.resume()" in dashboard
assert "action === 'complete'" in dashboard
assert "completeTodayItem(item)" in dashboard
assert "todayLockScreen.consumeLaunchAction()" in dashboard
assert dashboard.index("const completeTodayItem = createTodayCompletion({") < dashboard.index("todayLockScreen.consumeLaunchAction()")
assert "todayLockScreen.sync(snapshot, workSession.checkpointed())" in dashboard
assert "todayLockScreen.consumeLaunchAction()" in dashboard
assert '"static/today-lock-screen.js"' in bundle