Notifications & Nudges
Demand Board — Measurement
The Situation Board was aimed at the wrong question. Re-points it at demands on Alex's time, and instruments the estate before rebuilding anything.
Situation
The board was not under-commissioned. It was aimed at the wrong question. Its own founding audit found 142 ball-in-court items, of which 130 auto-resolved the moment Alex replied, 12 aged, and none were ever nudged. Confirmed in use: *"by the time the updates flagged the issue, I had already replied or planned to reply."* The court zone lags a system Alex already sits inside all day.
Everything Mallory built pointed at the single axis Slack wins — *has this been replied to* — and nothing at the axes Slack cannot see. triage still defaults to 'off' and was never switched on; POST /ingest/calendar is built, tested and has no caller; the board has never read loop_items, Alex's own to-dos, because v1 fenced the Loops tray out of scope. And there was no usage instrumentation anywhere, so every retire/keep decision to date was argued from architecture rather than evidence.
Key decisions
The unit is a demand
A demand on Alex's time — anything wanting a slice of his day, reply or not — ranked by cost of ignoring rather than age of non-reply.
Derived *and* authored
Manual capture is in. The v1 *"not a to-do app"* principle was aimed at manual ticking and banned manual telling as collateral — which is precisely what left the board a partial view.
Mallory takes the interrupt budget
Few, calendar-aware pushes instead of ambient silence, with a watch glance underneath. Precondition, agreed: Alex turns Slack's notifications down. Without it this is a second, slower notifier and should not be built.
Measurement first, and gated
Instrument before redesigning. A two-week miss log runs before the demand store is built — if the misses come back empty, the rest should not be built at all.
Append-only, one table
A count yields a number; the log yields the sequence — surfaced 08:12, deferred 08:13, deferred 12:40, expired. That cannot be reconstructed from counters afterwards. One table, not two, so the push→open→act chain is answerable.
No dashboard
The numbers land in the weekly review. A dashboard would be a surface for numbers Alex will not read, inside an app he does not open — the exact failure this cycle exists to correct.
Architecture
One append-only D1 table written from three places — the PWA beacon, the push helper, and the board's own actions — plus a nightly question on the VM and a weekly rollup in the vault. Nothing new is read by the app itself.
at, surface, action, subject_type, subject_id, detail. Never updated, only appended.recordUsageEvent, countByAction for the rollup, and a 365-day pruneUsageEvents on the existing cron tick.POST /events — the beacon endpoint. Batch-capped at 50; an unknown action is skipped, not rejected, so a stale client never fails the batch.nudgeId on PushMessage; notify mirrors a push_sent row and stamps ?n= onto the deep link so a tap is attributable.misses_drafted guard that fails on an empty section.Risks & guardrails
A net that never fired
Demoting breach pushes removes the only safety net for the ~12 items that did age. Accepted with eyes open — a net that has never fired across 142 items is not a net — but it is the decision most likely to need reversing.
An empty miss log looks like a broken one
The estate's known failure mode: eod-reflect recorded ok on Goose's startup banner for three nights because the guard tested for a heading the template always emits. The miss log must answer None. literally, and the guard has a negative case proving it bites.
Measurement degrading the thing measured
The beacon is fire-and-forget and swallows every error; the push_sent mirror is wrapped so it can never block a push. Instrumentation must never be the reason something fails.
Documented state ≠ actual state
Three times now the docs have described a state the code never reached — triage: 'off', the calendar endpoint with no caller, and nudges still being read by GET /updates despite being "demoted". Both plan deviations came from re-reading the code rather than the spec.
The Slack precondition slipping
If the real-time notifications stay on, Mallory always loses on speed and the interrupt budget is noise. The weekly review carries a self-reported Slack volume specifically so the precondition is visible rather than assumed.
Delivery
The log
Migration 025, db.usage.ts behind unit tests, POST /events, and the retention prune folded into the cron tick that already runs pruneUpdates.
The beacon
postEvent in the API client; open on mount, view on afterNavigate, and push_tapped read from the ?n= stamp on load.
Push attribution
stampNudgeUrl behind tests, then notify mirrors the send and ships the stamped link. Attribution by URL, not a service-worker fetch whose Access cookie may not ride along.
The miss log (VM)
The question appended to the reflection prompt, plus misses_drafted and two new cases in the existing bash test — one passing on None., one failing on blank.
The weekly rollup (vault)
Counts queried from D1 and messages, misses counted from the week's daily notes, Slack volume typed by hand.
The two-week stop
A hard gate, not a pause. Nothing downstream is planned or built until the miss log has produced evidence — including the demands table itself.
Out of scope
docs/superpowers/specs/2026-08-01-demand-board-measurement-design.mdplan ·
docs/superpowers/plans/2026-08-02-demand-board-measurement.md