Skip to content

Notifications & Messages โ€‹

How in-app message notifications and 1:1 message handling work today, and where the code lives. This is the one guide for this area (the old NOTIFICATIONS_QUICK_REF.md was folded in here; both previously described a superseded implementation, and git history holds those write-ups).

Connection banners (messages AND accepted waves) โ€‹

Banners are an app-shell concern, not a Dashboard feature: App.jsx mounts two hooks and composes BOTH banner types through one stack, so they appear on every route.

  • Message hook: apps/web/src/hooks/useMessageNotifications.js. Subscribes to the user's active connections and their message streams, and surfaces one transient "New message" banner per connection for genuinely new messages from the other party. Initial Firestore snapshots are history, not news, and never trigger a banner.
  • Acceptance hook: apps/web/src/hooks/useAcceptanceBanners.js. Surfaces a banner when someone accepts your wave.
  • Stack: apps/web/src/components/ConnectionBannerStack.jsx is what App.jsx actually mounts. It merges both banner lists (acceptances ordered first), stays type-agnostic, and caps the visible count.
  • Banner component: apps/web/src/components/ConnectionNotification.jsx renders each banner inside the stack. It is also the repo's reference implementation for the clickable-card accessibility pattern (plain div wrapper, real inner <button>, inner controls stop propagation); see the UI conventions in AGENTS.md.
  • Privacy: the banner carries only WHO (the lantern name) and never a decrypted message body. Message bodies are E2EE and stay in the chat.
  • Dismissal is session-scoped. Dismissing a banner removes it from React state for this session only. There is no localStorage persistence of dismissed IDs (an earlier iteration had one; it was removed).
  • Open-chat suppression: the banner for the currently open chat (?chat= route param) is suppressed, and opening a chat clears any pending banner for it.
  • Tests: apps/web/src/hooks/__tests__/useMessageNotifications.test.js.

Message deletion โ€‹

Users can delete their own messages from the chat:

  • UI: apps/web/src/components/Chat.jsx. A trash button (lucide Trash2) appears on hover for the user's own messages, with an in-progress guard (deletingMessageId) and a confirmation step.
  • Service: deleteMessage(connectionId, messageId) in apps/web/src/lib/messageService.js deletes connections/{connectionId}/messages/{messageId}. The real-time listener updates both participants' chats; there is no separate sync step.
  • Rules: deletion authorization lives in firestore.rules under the connections/{connectionId}/messages/{messageId} match. If you change deletion behavior, run the rules suite (npm run test:rules).

Manual test checklist โ€‹

  • Send a message from a second account: banner appears on whatever route the recipient is on, showing the sender's lantern name only.
  • Accept a wave from a second account: the acceptance banner appears for the waver, ordered above any message banners in the stack.
  • Open that chat: the banner clears and no new banner appears for it while open.
  • Dismiss a banner, then reload: a still-unread conversation may banner again (dismissal is per-session by design).
  • Hover your own message: delete appears; confirm deletes it for both sides in real time. Other users' messages offer no delete.

Ideas and follow-ups โ€‹

File notification/messaging feature ideas (read receipts, typing indicators, notification preferences, and similar) as GitHub issues rather than growing this doc; issues are the backlog home (AGENTS.md rule 5).

Built with VitePress