# Notification Expansion Design

**Date**: 2026-02-26
**Status**: Approved
**Scope**: Expand push notification triggers + in-app NPC message banners

## Context

BaoLife's notification infrastructure is already built:
- Backend: APNS HTTP/2 integration, JWT auth, 4/hr throttle, stub mode for dev
- iOS: Push permission, device token registration, notification categories, deep link routing

The gap: only `v2Prompt` events trigger push notifications. NPC messages, achievements, milestones, and other events are silent when the app is backgrounded. In-app, conversation messages arriving while the user is on a different screen have no visual indicator.

## Design

### Part 1: Backend — Expand Push Notification Triggers

**New helper** in `PlayerSession.ts` or notification service:
```typescript
notifyIfBackgrounded(session, title, body, category)
```
Wraps the existing pattern: check `ws.readyState !== OPEN` + `deviceToken` exists + throttle budget. One-liner at each dispatch point.

**Trigger points to add:**

| Trigger | File | Notification Text | Category |
|---------|------|-------------------|----------|
| NPC-initiated messages | `npc_initiative.ts:~261` | `"{npcName}: {preview}"` | `relationship` |
| Achievement unlocks | `integration.ts:~36` | `"Achievement Unlocked: {name}"` | `milestone` |
| Life events (messageQueue) | `PlayerSession.ts:~148-169` | `"{event title/summary}"` | `life_event` |
| Milestone events (marriage, birth, graduation) | `integration.ts` (various) | `"Life Milestone: {description}"` | `milestone` |
| Career events (promotion, fired, new job) | `integration.ts:~96` | `"Career Update: {description}"` | `life_event` |

**Unchanged**: APNS service, 4/hr throttle, JWT auth, deep links, stub mode, notification categories.

### Part 2: iOS — In-App NPC Message Banners

**Extend ToastManager** with a new `.message` style:
- Chat bubble icon, NPC name bold, message preview truncated to ~2 lines
- Auto-dismiss after ~4 seconds (same as other toasts)
- Uses existing queue system (one toast at a time)

**Add tap action support to ToastManager**:
- Currently toasts only have a dismiss X button
- Add optional `onTap` closure to toast data model
- `.message` toasts: tap opens ChatView directly for that conversation

**Trigger logic** in `WebSocketService.swift`:
- When `conversationEvent` arrives, check if user is currently viewing that conversation
- If not, fire `ToastManager.shared.show(.message, ...)` with NPC name + preview
- Carry conversation ID + NPC info so tap can route directly

**Navigation on tap**:
- Toast tap sets the active conversation and presents ChatView directly (sheet or navigation)
- No intermediate screens — straight into the conversation

## Files Touched

### Backend
- `server/src/game/PlayerSession.ts` — add `notifyIfBackgrounded` helper, expand notification calls at messageQueue dispatch
- `server/src/events/conversations/npc_initiative.ts` — add notification trigger after NPC message send
- `server/src/services/retention/integration.ts` — add notification triggers for achievements, milestones, career events

### iOS
- `ios/.../Shared/Managers/ToastManager.swift` — add `.message` style, add tap action callback to toast model
- `ios/.../Core/Services/WebSocketService.swift` — trigger `.message` toast on conversation arrival when off-screen
- `ios/.../lichunWebsocketApp.swift` or `ContentView.swift` — wire toast tap to present ChatView directly

## Out of Scope
- Notification preferences UI (user choosing what to receive)
- Badge count management (increment/reset)
- Rich notification content extensions (images in push notifications)
- Notification inbox/feed
- Android notifications (separate task)
