Previously the SSE stream only ran while the Messages component was
mounted. Navigate to Dashboard or Claims and new messages just piled
up server-side until you came back. Now the stream runs for the whole
session, drives an unread badge in the nav, and (with permission)
fires a system notification when a message lands while you're in
another tab.
inboxService (lib/inbox-service.svelte.ts):
• Singleton Svelte 5 $state class. session.setUnlocked() starts it,
session.lock() stops it. Holds the SSE stream + the 30s heartbeat
poll for the entire session lifetime.
• Reactive state read by anyone: status (off/connecting/live/
reconnecting), unreadCount (since last visit to /messages), and
lastError (surfaced in the Messages footer).
• onMessage(fn) lets components subscribe to repaint when ingest
succeeds — Messages page uses this instead of owning its own
stream.
• #fireSystemNotification fires Notification API on inbound when
Notification.permission === "granted" AND document.visibilityState
!== "visible". Silent while you're actively looking at the tab.
Uses tag="kez-chat-inbox" so multiple notifications collapse.
Messages.svelte:
• Stripped its own stream/poll. Now just subscribes to inboxService.
onMount also calls markAllRead() — landing on /messages = you've
seen the new stuff.
• Footer status indicator reads from inboxService instead of local
state.
App.svelte nav:
• Messages link grows a red unread-count badge (1, 2, …, 9+) when
inboxService.unreadCount > 0 and the user isn't already on the
Messages route.
Dashboard:
• New "Notifications" section between Quick unlock and Backup with
the standard 3-state UX: granted (green confirm), denied (amber
"fix in site settings"), default (button to request).
• Helpers in inbox-service.ts wrap the Notification API so non-
supporting browsers (older Safari, Firefox in some configs) get
graceful "not supported" copy.
Caveat (for v0.3): notifications only fire while the tab is open in
SOME state (background-but-not-closed). Closing the tab kills the
SSE stream so nothing arrives at the page to notify about. True
background push (Web Push API + VAPID + server-side push) is a
separate piece of work.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>