Source: apps/oshun/web/src/design-system/components/Toast/,
apps/oshun/web/src/components/NotificationsCenterPanel.tsx,
apps/oshun/web/src/components/MessageCenter.tsx, push subscription via
PwaBootstrap.tsx/sw.js.
Three notification surfaces: ephemeral toasts (right-now feedback), in-app notifications center (history + unread), and push notifications (off-app re-engagement).
Toasts#
ToastProvider wraps the tree at the root layout level.
- Variants — info, success, warning, error, action (with button)
- Position — bottom-right desktop, bottom-center mobile (above bottom-nav)
- Stacking — up to N visible; older toasts collapse or expire
- Auto-dismiss — info/success 4 s, warning 6 s, error 10 s, action manual
- Manual dismiss — close button; keyboard
Escdismisses focused toast - Action toast — button performs declared action; auto-dismiss after success
- Screen reader —
role="status"for info/success;role="alert"for error - Reduced motion —
prefers-reduced-motiondisables slide animation - Focus return — focus returns to triggering element when toast dismissed
- Don't queue infinitely — if N+ toasts queued, oldest drops
In-app notifications center#
NotificationsCenterPanel.tsx — opened from header bell. Customer-message rows
are projected from the customer-message-center store through
/v1/notifications; mark-read and dismiss writes flow back through
/v1/notifications-write to that same store.
- Bell in header — unread count badge (cap at "99+"); customer-message count is sourced from the shared inbox snapshot
- Open — bell click; Esc closes
- Tabs / filters — all / mentions / system / domain-specific
- List item — title, preview, timestamp (relative), read/unread state for customer-message notifications
- Click item — marks read through the notifications-write route and navigates to source for customer-message notifications
- Mark all read — visible action; bulk operation
- Empty state — calm copy, no doom font
- Long list — pagination or virtualization; scroll restored
- Realtime — new notifications appear without reload
(
OshunQueryProvidersubscription) - Offline — last-fetched list visible; "you're offline" hint; no broken loaders
Message center#
MessageCenter.tsx — /messages plus a header preview.
-
/messages— full inbox/outbox view (see customer/02-home-discovery/messages.md) - Header preview — recent N messages; click to open full view
- Compose — destination, subject, body; send via BFF; optimistic update
- Delete / archive — soft-delete with undo toast
Push notifications#
sw.js registers push and notificationclick listeners; subscription managed
in PwaBootstrap.
- Permission request — only after a clear user signal (e.g., toggle in profile/notifications); never on first page load
- Subscription —
pushManager.subscribe; endpoint POSTed to BFF - Permission denied — UI shows "Push is blocked; enable in browser settings"
- Permission revoked — detect via
permissionState; show re-enable hint - Push payload received →
self.registration.showNotification(title, options) - Notification click → focuses existing tab if open, else opens canonical route
- Notification action buttons (snooze / dismiss / open) trigger correct flow
- Quiet hours respected if configured
- Unsubscribe — toggle off in profile; SW unsubscribes; BFF informed
Notification preferences#
Where users configure which channels send which notifications.
- Profile → Notifications — granular per-channel toggles
- Categories — system / domain-specific / mentions / digest
- Per-domain — Tara ritual reminders, Arete habit nudges, Nyx sky-event alerts, Veritas story drops, Nisaba study reminders, Metis class deadlines
- Quiet hours — start/end time; timezone-aware
- Save — confirmation toast; settings persist across devices
Realtime delivery#
The notifications center subscribes to a realtime channel (Psyche substrate). Behavior:
- Connected — new notifications stream in
- Disconnected — reconnect attempted with backoff
- Reconnect — fetch missed notifications via REST snapshot
States#
- No notifications — empty state shown
- Some unread — badge on bell; bold styling on unread items
- All read — no badge; subdued styling
- Offline — last-known state shown; banner hint
- Push blocked — preferences page shows hint to re-enable
Cross-references#
- 01-app-shell.md — bell lives in header
- 03-pwa-behavior.md — push subscription + SW
- Architecture:
V1/ARCHITECTURE.md - Feature spec:
V1/features.md
Open questions / known gaps#
- Confirm whether mentions across domains live in notifications, messages, or both
- Document quiet-hours edge cases (DST, traveler timezone)
- Email/SMS fall-through channels — do they exist in V1?