Repository navigation
Description
aggregator: a REJECTED restore still cancels its target in card-state — the card resurfaces even though the consumer refused the restore
Summary
pm-stack/aggregator/src/card-state.ts derives each dashboard card's effective state at render time. Its documented contract (module header, lines 13–21) is that consumer rejections are non-effective: "A consumer REJECTION lifts the suppression again (the item resurfaces, with the rejection badge explaining why)." Every verb family honours that rule — snooze (:73), dismiss (:77), done (:79), answer (:86), ack (:92) all check state !== "rejected" — except restore. cancelledActionIds (:52–58) cancels the action named in a restore's supersedes for any restore whose state is merely not "superseded", so a restore the consumer rejected still cancels its target at render time.
Evidence (verified this run, main 7eca828)
Probe: the verbatim aggregator src/ tree (card-state.ts sha256 76f9f438…, identical on both sides) driven under Bun on the laptop, using the same mkAction fixture shape as test/dashboard-overhaul.test.ts:
control applied-dismiss + new restore: suppressed=null cancelled=da-dismiss eff=da-restore
control applied-dismiss alone: suppressed=dismissed cancelled= eff=da-dismiss
applied-dismiss + REJECTED restore: suppressed=null cancelled=da-dismiss eff=da-restore <-- BUG
future snooze + REJECTED restore: suppressed=null cancelled=da-snooze eff=da-restore <-- BUG
applied ack + REJECTED restore of ack: acked=false cancelled=da-ack <-- BUG
So with an applied dismiss in place and a restore that the consumer rejected (with a note, per actions.ts:43-44), the dashboard renders the card as restored — it leaves the hidden strip and returns to its main list — even though the restore never took effect. The same happens for a live snooze (card resurfaces before its until) and for an applied acknowledge (card loses its "seen, not resolved" quiet state).
Root cause: card-state.ts:54 —
if (a.verb === "restore" && a.state !== "superseded" && a.supersedes) out.add(a.supersedes);state === "rejected" must also exclude a restore from cancelling, exactly as the other verbs are excluded. (A pending restore must keep cancelling — that is the verified control above and the committed test at dashboard-overhaul.test.ts:123.)
Why it matters
The dashboard is Drew's triage surface: a card that resurfaces reads as "this needs you again". A rejected restore (consumer could not apply it, e.g. the underlying object changed) silently resurrecting dismissed/snoozed items re-adds noise the user explicitly removed, and the rendered state disagrees with the store's applied truth — the exact class of divergence #1442/#1443 built this derivation layer to eliminate. No error is shown; the rejection badge logic never gets a chance to explain anything because the target action is filtered out of effectiveActions entirely.
Acceptance criteria
-
cardState([appliedDismiss, rejectedRestore(supersedes=dismiss)])returnssuppressed.kind === "dismissed"; the dismiss remains ineffectiveActions. - Same for snooze (future
until→ stillsnoozed) and acknowledge (applied ack + rejected restore →acked === true). - Pending (
new) and applied restores still cancel their target (existing test atdashboard-overhaul.test.ts:123keeps passing). - New regression tests in
pm-stack/aggregator/test/dashboard-overhaul.test.tscover the rejected-restore cases, mirroring the existing rejected-dismiss test at :120. - Full aggregator test suite passes in CI.
Scope notes
- Fix is expected to be confined to
cancelledActionIdsinpm-stack/aggregator/src/card-state.ts(plus tests); if triage finds the store's own supersede transition has the same gap, note it on this issue rather than expanding scope silently. - Distinct from eval(dashboard): actions/comments stores — JSON files vs SQLite #1452 (actions/comments store evaluation) and aggregator: Cost Explorer inline-script computations from PR #1390 (recommendations, Pareto, projection) ship with no committed tests #1405 (Cost Explorer test coverage) — different module, different defect.
Activity
developing-today commentedon Oct 27, 2026
CODE-547
dezren39 commentedon Oct 27, 2026
🤖 Agent note · filed
6 remaining items
dezren39 commentedon Oct 28, 2026
🤖 Agent note · parked
cancelledActionIdsin pm-stack/aggregator/src/card-state.ts now excludes rejected restores; 4 regression tests added (dismiss/snooze/ack rejected-restore + applied-restore control). Laptop verification:bun test test/dashboard-overhaul.test.ts24 pass / 0 fail. CI queued at park time (0 failed).queue/1503-1474-rejected-restore, laptop worktree ~/git/fleet/1503-1474-rejected-restore.dezren39 commentedon Oct 28, 2026
🤖 Agent note · picked up (resume)
queue/1503-1474-rejected-restore.fix(aggregator): a rejected restore no longer cancels its target (#1474…
dezren39 commentedon Oct 28, 2026
🤖 Agent note · done
card-state.ts+ regression tests, +57/−4). Merged as squash commitdb6f7d002bc6ede3f6ca61aa706920b7b4a36930; issue auto-closed by the merge. Tests: laptopbun test test/dashboard-overhaul.test.ts24 pass / 0 fail (prior shift, recorded on the PR).