MalloryEstate Briefing
M·025Designed
← All briefings

Notifications & Nudges

Notifications & Updates Intelligence

Three separable fixes from an on-device review: honest session counts, device-local push suppression/collapse, and a resolve action for Updates.

Dated 2026-06-16Programme Notifications & Nudges

Situation

An on-device review of the live notification + Updates experience surfaced three issues: the EOD wrap stated a confident-but-wrong "177 conversations" (actually a raw, inflated event count); proactive pushes fire for every surfaced item and stack repetitively even when the item is already visible on-screen; and Updates items can never be cleared once handled. Three separable tracks, each its own PR, build order T1 → T2 → T3.

T1 fixes a live trust bug (Worker-only, smallest); T2 kills daily push noise via per-device foreground suppression and collapsing repeat batches; T3 adds a manual, purely subtractive resolve action plus tighter, resolve-aware retention.

Key decisions

Count sessions, not events

"Conversations" becomes countSessions over today's feed message timestamps (new session after a >30min gap) plus discrete task_sessions — never a raw interactionCount of logged events.

Busyness from edge-countable signals, not chat volume

Mentions handled, digs run, and session counts feed the teaser as labelled facts; calendar meeting-hours is flagged as the strongest signal but deferred — parsing agenda prose is explicitly rejected as fragile.

Device-local push suppression, not global focus-tracking

Each device decides for itself whether to show a push based on its own foreground state (web SW clients.matchAll, Android ProcessLifecycleOwner) — so a focused desktop never silences the phone and vice versa.

Collapsing batch notifications

Batch pushes get a fixed tag/collapse_key so a new "awaiting you" replaces the prior one instead of stacking, and titles lead with the most urgent item/people instead of a bare count.

Resolve is purely subtractive

A manual per-item resolve marks an Updates item strikethrough-and-greyed in place; it never creates a tracked task, keeping Updates a triage surface rather than a to-do list.

Retention tightens and becomes resolve-aware

Resolved items are kept 24h then dropped; unresolved items are now kept only 3 days (a change from the prior indefinite retention) — implemented as a single pruneUpdates predicate in the EOD decay tick.

Architecture

Two Worker-side session-counting helpers fix the accuracy bug; per-device foreground checks and a collapse key fix the noise; a resolved_at column and prune rewrite fix retention.

countSessions / countSessionsToday (eod.ts, briefing.ts)
Pure gap-heuristic session counter over feed timestamps plus a task_sessions count, replacing the raw interactionCount data line.
Service-worker push handler (web)
clients.matchAll({type:'window'}) check — skips showNotification when a focused/visible client already exists.
MalloryMessagingService (Android)
Foreground check via ProcessLifecycleOwner suppresses the system notification when the app is already in foreground.
batchDigest (src/lib/nudge.ts)
Title composition changed from a bare "N things awaiting you" count to a signal-led lead (most urgent item or people involved).
POST /updates/resolve
New route setting resolved_at on an Updates row (migration-added column).
pruneUpdates (EOD decay tick)
Replaces the flat 30-day decay with resolved-24h / unresolved-3-day cutoffs.

Risks & guardrails

Trust erosion from wrong counts

The "177 conversations" bug is a confident-but-wrong statement, which is worse than an admitted unknown — this is the reason T1 ships first despite being the smallest change.

Device-local suppression could under- or over-suppress

Guarded by scoping the foreground check to each device independently rather than a shared/global state, so no single device's focus silences another's notification.

Retention tightening loses data

Unresolved items dropping from indefinite to 3 days is called out explicitly as a behaviour change, not just a bugfix — worth flagging before it ships.

Collapse could hide a genuinely new urgent item

Mitigated by the informative-title change (leads with the most urgent item/person) so a collapsed notification still surfaces what's new, not just a stale count.

Delivery

T1 — EOD/briefing accuracy

Worker-only: session-counting helpers replace the raw event count; ships first as the smallest fix to a live trust bug.

T2 — Notification intelligence

Worker batchDigest title change + web service-worker foreground check + Android foreground-suppression service; kills daily push noise.

T3 — Updates resolve + retention

Migration adds resolved_at; POST /updates/resolve route; web resolve UI; EOD decay tick reworked to resolve-aware retention.

Out of scope

Auto-resolving a Gmail/Slack item once Alex has replied (needs Hub reply-detection; manual-resolve first)Per-message deep-links (Slack team+channel+ts, Gmail thread) — separate deferred itemA structured Hub-pushed {meetings, meetingHours} busyness stat (flagged as a small future follow-up)
spec · docs/superpowers/specs/2026-06-16-notifications-updates-intelligence-design.md
Mallory · Estate briefing