Compare commits

..

No commits in common. "c5e6e17fc15b008c21563d0d0cd3016828049ad0" and "cbe4017bdc4c1509390210745999539e4cc1e737" have entirely different histories.

7 changed files with 8 additions and 89 deletions

View File

@ -6058,27 +6058,10 @@
if (!preserveRoute) workRoute.queue(filter);
return true;
}
async function openWorkQueueRoute(filter, action = null) {
function openWorkQueueRoute(filter) {
if (!selectWorkQueue(filter, { preserveRoute:true })) return;
qs('#my-work').scrollIntoView({block:'start'});
qs('#my-work').focus();
if (action !== 'protect-today') return;
await completeAgendaIssues();
if (workPagination.issue?.has_more) {
qs('#my-work-action-status').textContent =
'Protect Today needs all assigned deadlines. Retry when connected.';
return;
}
const proposal = protectToday.propose({
agenda:agendaMyWork(activeMyWork), today:todayMyWork,
identity:item => todayWork.identity(item), limit:todayWork.limit,
});
if (!proposal.protected.length) {
qs('#protect-today-status').textContent = 'No overdue or due-today work needs protection.';
return;
}
pendingProtectToday = proposal;
openPlanToday(qs('#protect-today'));
}
qs('#work-milestone-filter').addEventListener('change', event => {
selectedWorkMilestone = event.target.value;

View File

@ -314,14 +314,12 @@ self.addEventListener('push', event => {
try { payload = event.data?.json?.() || {}; }
catch (_error) { return; }
const route = String(payload.route || '');
const protectRoute = String(payload.protect_route || '');
const tag = String(payload.tag || '');
const notificationId = Number(payload.notification_id);
const updateCount = Number(payload.update_count);
const deadlineCount = Number(payload.deadline_count);
if (
route === '#/my-work/agenda'
&& protectRoute === '#/my-work/agenda/protect-today'
&& /^stackchain-deadline-digest-\d{4}-\d{2}-\d{2}$/.test(tag)
&& Number.isSafeInteger(deadlineCount)
&& deadlineCount > 0
@ -332,11 +330,7 @@ self.addEventListener('push', event => {
{
body: 'Open Agenda to review or replan ' + (deadlineCount === 1 ? 'it.' : 'them.'),
tag,
actions: [
{ action: 'protect-today', title: 'Protect Today' },
{ action: 'open-agenda', title: 'Open Agenda' },
],
data: {route, protectRoute},
data: {route},
}
));
return;
@ -386,22 +380,6 @@ async function openWorkRoute(route) {
self.addEventListener('notificationclick', event => {
const route = String(event.notification.data?.route || '');
if (event.action === 'protect-today') {
const protectRoute = String(event.notification.data?.protectRoute || route);
if (
protectRoute !== '#/my-work/agenda/protect-today'
|| !['#/my-work/agenda', protectRoute].includes(route)
) return;
event.notification.close();
event.waitUntil(openWorkRoute(protectRoute));
return;
}
if (event.action === 'open-agenda') {
if (route !== '#/my-work/agenda') return;
event.notification.close();
event.waitUntil(openWorkRoute(route));
return;
}
if (event.action === 'tomorrow') {
const notificationId = Number(event.notification.data?.notificationId);
if (

View File

@ -20,9 +20,6 @@
if (queueFilters.includes(queue) && parts.length === 3) {
return { kind: 'queue', filter: queue };
}
if (queue === 'agenda' && parts[3] === 'protect-today' && parts.length === 4) {
return { kind: 'queue', filter: 'agenda', action: 'protect-today' };
}
if (parts[2] === 'update' && parts.length === 4) {
const notificationId = positiveInteger(parts[3]);
return notificationId ? { kind: 'update', notification_id: notificationId } : null;
@ -117,7 +114,7 @@
if (active && active !== fragment) onClose();
if (active === fragment) return;
active = fragment;
onQueue(route.filter, route.action || null);
onQueue(route.filter);
return;
}
const item = items.find(candidate => sameRoute(candidate, route));

View File

@ -352,7 +352,6 @@ async def _dispatch_deadline_reminders_unlocked(
"title": f"{due_count} deadline{'s' if due_count != 1 else ''} need{'s' if due_count == 1 else ''} attention",
"body": f"Open Agenda to review or replan {'it' if due_count == 1 else 'them'}.",
"route": "#/my-work/agenda",
"protect_route": "#/my-work/agenda/protect-today",
"tag": f"stackchain-deadline-digest-{local_day}",
"deadline_count": due_count,
}, separators=(",", ":"))

View File

@ -47,7 +47,6 @@ async def test_deadline_reminder_sends_one_private_local_day_digest_and_deduplic
"title": "1 deadline needs attention",
"body": "Open Agenda to review or replan it.",
"route": "#/my-work/agenda",
"protect_route": "#/my-work/agenda/protect-today",
"tag": "stackchain-deadline-digest-2026-08-13",
"deadline_count": 1,
}]

View File

@ -40,20 +40,6 @@ COMMENT_ACTIONS = Path(__file__).parents[1] / "frontend" / "comment-actions.js"
PICK_WORK = Path(__file__).parents[1] / "frontend" / "pick-work.js"
WORK_ROUTE = Path(__file__).parents[1] / "frontend" / "work-route.js"
UPDATE_OWNERSHIP = Path(__file__).parents[1] / "frontend" / "update-ownership.js"
def test_protect_today_route_is_an_explicit_agenda_action():
script = f"""
const routes = require({json.dumps(str(WORK_ROUTE))});
process.stdout.write(JSON.stringify(routes.parse('#/my-work/agenda/protect-today')));
"""
result = subprocess.run(
["node", "-e", script], check=True, capture_output=True, text=True
)
assert json.loads(result.stdout) == {
"kind": "queue", "filter": "agenda", "action": "protect-today"
}
CARD_PLANNING = Path(__file__).parents[1] / "frontend" / "card-planning.js"
TODAY_WORK = Path(__file__).parents[1] / "frontend" / "today-work.js"
WORK_SELECTION = Path(__file__).parents[1] / "frontend" / "work-selection.js"

View File

@ -459,15 +459,14 @@ def test_update_digest_push_opens_unread_inbox_without_item_actions_or_private_c
assert "must-not-render" not in json.dumps(result["notifications"])
def test_deadline_digest_push_offers_protect_today_and_agenda_without_rendering_private_copy():
def test_deadline_digest_push_opens_agenda_without_rendering_private_copy():
result = run_worker_scenario(
"""
await dispatchPush({
title:'must-not-render', body:'private details must-not-render',
tag:'stackchain-deadline-digest-2026-08-13', route:'#/my-work/agenda',
protect_route:'#/my-work/agenda/protect-today', deadline_count:3,
tag:'stackchain-deadline-digest-2026-08-13', route:'#/my-work/agenda', deadline_count:3,
});
await dispatchNotificationClick('#/my-work/agenda/protect-today', 'protect-today', null, 'stackchain-deadline-digest-2026-08-13');
await dispatchNotificationClick('#/my-work/agenda');
process.stdout.write(JSON.stringify(state));
"""
)
@ -477,37 +476,15 @@ def test_deadline_digest_push_offers_protect_today_and_agenda_without_rendering_
"options": {
"body": "Open Agenda to review or replan them.",
"tag": "stackchain-deadline-digest-2026-08-13",
"actions": [
{"action": "protect-today", "title": "Protect Today"},
{"action": "open-agenda", "title": "Open Agenda"},
],
"data": {
"route": "#/my-work/agenda",
"protectRoute": "#/my-work/agenda/protect-today",
},
"data": {"route": "#/my-work/agenda"},
},
}]
assert result["opened"] == [
"https://forge.example/dashboard/#/my-work/agenda/protect-today"
"https://forge.example/dashboard/#/my-work/agenda"
]
assert "must-not-render" not in json.dumps(result["notifications"])
def test_deadline_digest_open_agenda_action_preserves_browsing_flow():
result = run_worker_scenario(
"""
await dispatchPush({
tag:'stackchain-deadline-digest-2026-08-13', route:'#/my-work/agenda',
protect_route:'#/my-work/agenda/protect-today', deadline_count:1,
});
await dispatchNotificationClick('#/my-work/agenda', 'open-agenda', null, 'stackchain-deadline-digest-2026-08-13');
process.stdout.write(JSON.stringify(state));
"""
)
assert result["opened"] == ["https://forge.example/dashboard/#/my-work/agenda"]
def test_push_mark_read_action_confirms_authenticated_mutation_without_opening_app():
result = run_worker_scenario(
"""