Badge outstanding notifications and log every one - #29
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
permissionandquestionrequests and clears only on an explicit reply/reject, successful upstream reconciliation, or manual dismissal.idle,error, andabortare 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
HistoryStorepersists.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 /permissionandGET /questionon 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.repliedevent. 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 permissionsand are born resolved.delivery.ntfy:sent,off, orfailedwith error textdelivery.desktop: whether server-backed desktop notifications were allowed, never proof a tab rendered oneMerge resolution
Merged current
origin/mainthrough 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-pathsendFilereturned ENOENT for SPA routes; the fallback now uses the equivalent rootedsendFileform.Verification
npm run typecheck— cleannpm test— 242 passednpm run build— cleanECONNREFUSEDcascade after 33 passes. This is an infrastructure failure rather than an assertion failure; GitHub CI is the authoritative full run.