Skip to content

Badge outstanding notifications and log every one - #29

Merged
leoncheng57 merged 7 commits into
mainfrom
feat/notification-history
Aug 21, 2026
Merged

Badge outstanding notifications and log every one#29
leoncheng57 merged 7 commits into
mainfrom
feat/notification-history

Conversation

@leoncheng57

@leoncheng57 leoncheng57 commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Adds a red counter for notifications that still need a reply, and a durable history of every notification the BFF classified.

Counter semantics

The counter is outstanding work, not unread state. It counts unresolved permission and question requests and clears only on an explicit reply/reject, successful upstream reconciliation, or manual dismissal. idle, error, and abort are logged but never counted. A parked permission escalates its parent record rather than adding a second count.

The nav/header counter is scoped to the current ?directory= so unrelated projects do not keep it permanently red. The history list remains global so the page still explains notifications from every project.

Retention and reconciliation

HistoryStore persists .state/notification-history.json. Active records are never evicted. They may temporarily grow the file beyond the nominal limit; resolved records fill only the remaining capacity in a 500-record ring.

Records outlive the BFF, so the active set is reconciled against GET /permission and GET /question on event-stream reconnect and, throttled, on history reads. Lookup or path-validation failures are never treated as evidence of resolution. There is no age-based expiry and no bulk clear: resolved history is the evidence this feature exists to preserve.

Questions especially require reconciliation because this repo has never observed a dependable question.replied event. The SSE branch is opportunistic; the pending-question lookup is authoritative.

Honest delivery reporting

Every classified event is recorded, including suppressed and failed delivery. Auto-approved permissions are marked suppressed by auto permissions and are born resolved.

  • delivery.ntfy: sent, off, or failed with error text
  • delivery.desktop: whether server-backed desktop notifications were allowed, never proof a tab rendered one
  • Sound and speech are device-local and intentionally absent because the BFF cannot observe those settings

Merge resolution

Merged current origin/main through PR #31 and preserved the notification sound/speech, session sharing, catalog, mobile screenshot, and composer-key changes. The merge also exposed an Express/send runtime regression where absolute-path sendFile returned ENOENT for SPA routes; the fallback now uses the equivalent rooted sendFile form.

Verification

  • npm run typecheck — clean
  • npm test — 242 passed
  • npm run build — clean
  • Notification E2E (history API, scoped badge UI, sound/speech integration) — 9 passed
  • Full local E2E was attempted; its three Playwright web servers exited mid-run and caused an ECONNREFUSED cascade after 33 passes. This is an infrastructure failure rather than an assertion failure; GitHub CI is the authoritative full run.
/settings/notifications

Notifications were fire-and-forget: NotificationService classified an event,
pushed ntfy, and discarded it. Nothing could report what was outstanding or
what had already fired.

Add a persisted record behind both views. HistoryStore keeps the newest 500
records in .state/notification-history.json using the same atomic write as
PreferenceStore, with writes serialized so events landing in one tick cannot
interleave temp files.

The badge counts unresolved permission and question requests, not unread
items, so it clears by answering the agent rather than by opening the page.
idle/error/abort are logged but never counted, and a parked alert escalates
its parent permission instead of adding a second count.

Records outlive the process, so a reply that lands while the BFF is down
would otherwise strand a badge nobody can clear. Reconcile the active set
against GET /permission and GET /question on stream reconnect and, throttled,
on history reads. That is also the only dependable path for questions: this
repo has never observed a question.replied event, and the Playwright mock
does not emit one. Actives older than 24h retire, and Dismiss clears a single
stuck row.

Suppressed and failed deliveries are recorded too, since explaining a missing
ping is the log's main job. delivery.browser records the preference only —
the BFF cannot observe whether a tab rendered anything, so it never claims
delivery.

Live updates reuse the existing app-level EventSource via a debounced
callback rather than opening a second stream per tab.
github-actions Bot added a commit that referenced this pull request Aug 21, 2026
github-actions Bot added a commit that referenced this pull request Aug 21, 2026
github-actions Bot added a commit that referenced this pull request Aug 21, 2026
@leoncheng57
leoncheng57 merged commit 919d973 into main Aug 21, 2026
3 checks passed
github-actions Bot added a commit that referenced this pull request Aug 21, 2026
@leoncheng57
leoncheng57 deleted the feat/notification-history branch August 21, 2026 22:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant