# Phase B — Realtime In-App Feed

*Estimated: 3 days · Depends on: Phase A merged*

---

## Goal

Notifications appear instantly in the app without requiring manual refresh. When a new notification is created (by alert checker, broadcast, or any future trigger), the mobile client receives it in real time and updates the UI — badge count, notification list, and an in-app toast if the user is active.

---

## Scope

- Supabase Realtime subscription on `notifications` table
- In-app toast/banner component for foreground events
- Reactive badge count (replace polling with realtime)
- Optimistic prepend to NotificationCenterScreen list
- Does NOT add new notification types (that's Phase C)

---

## Architecture

### Realtime Channel

Subscribe to Supabase `postgres_changes` on the `notifications` table, filtered by `user_id`:

```typescript
supabase
  .channel(`notifications:${userId}`)
  .on('postgres_changes', {
    event: 'INSERT',
    schema: 'public',
    table: 'notifications',
    filter: `user_id=eq.${userId}`,
  }, (payload) => {
    // 1. Increment unread badge count
    // 2. Prepend to notification list (if on that screen)
    // 3. Show in-app toast
  })
  .subscribe();
```

**Prerequisite:** The `notifications` table must have Realtime enabled in Supabase dashboard (or via migration: `ALTER PUBLICATION supabase_realtime ADD TABLE notifications`). RLS policies already exist from NAN-85 — Realtime respects them.

### Data Flow

```
Backend creates notification
  → INSERT into notifications table
  → Supabase Realtime broadcasts to subscribed clients
  → Mobile receives payload
  → Updates React Query cache (unread count + list)
  → Shows toast if app is foregrounded
```

---

## Affected Components

### Mobile — New Files

| File | Purpose |
|---|---|
| `src/features/notification/service/realtime/NotificationRealtimeService.ts` | Supabase channel subscription lifecycle (connect, disconnect, reconnect) |
| `src/features/notification/presentation/components/NotificationToast.tsx` | In-app toast banner UI |
| `src/features/notification/presentation/hooks/useNotificationRealtime.ts` | Hook that bridges realtime events → React Query cache updates + toast trigger |

### Mobile — Modified Files

| File | Change |
|---|---|
| `src/features/notification/module.ts` | Export `NotificationRealtimeService` |
| `src/features/notification/index.ts` | Export `useNotificationRealtime` for cross-feature use |
| `src/core/di/container.ts` | Wire `NotificationRealtimeService` with Supabase client |
| `src/core/providers/AppProviders.tsx` | Mount `useNotificationRealtime` hook (or a provider) |
| `src/features/notification/presentation/hooks/useNotificationCenterViewModel.ts` | Accept realtime inserts as cache updates instead of refetching |
| `src/features/notification/presentation/hooks/useUnreadNotificationCountData.ts` | Reactive count from realtime instead of polling |
| `src/features/notification/presentation/screens/NotificationCenterScreen.tsx` | Infinite scroll + pull-to-refresh |

### Backend — Migration

| File | Change |
|---|---|
| `supabase/migrations/20260507000000_notifications_realtime.sql` | Enable Realtime on `notifications` table |

No backend application code changes needed — Supabase Realtime is a database-level feature.

---

## Concrete Tasks (commit-sized)

### Task 1: Enable Realtime on notifications table (backend)

- Add migration: `ALTER PUBLICATION supabase_realtime ADD TABLE notifications;`
- Verify RLS policies allow `SELECT` for authenticated user on their own rows
- Test: insert a row via Supabase dashboard, confirm Realtime event fires in JS client

### Task 2: NotificationRealtimeService (mobile service layer)

- New file in `src/features/notification/service/realtime/`
- Manages Supabase channel lifecycle:
  - `connect(userId: string)` — subscribe to `postgres_changes` INSERT events
  - `disconnect()` — unsubscribe and remove channel
  - `onNotification(callback)` — register listener for new notifications
- Uses existing Supabase client from `src/infrastructure/` (or `@supabase/supabase-js` directly)
- Reconnect on channel error/timeout
- Wire into DI container via `module.ts`

### Task 3: useNotificationRealtime hook (mobile presentation layer)

- Connects to `NotificationRealtimeService` on mount, disconnects on unmount
- On new notification:
  1. Invalidate React Query `unreadCount` query (or optimistically increment)
  2. Prepend to `notifications` list query cache (if data is loaded)
  3. Trigger toast via a shared state atom (Zustand store or React context)
- Must handle auth state changes — reconnect with new `userId` on account switch

### Task 4: NotificationToast component (mobile UI)

- Slide-down toast banner appearing at top of screen
- Auto-dismiss after 4 seconds, swipe-up to dismiss early
- Shows: icon + title + body (same as NotificationCard from existing NotificationCenterScreen)
- Tapping the toast navigates to the relevant screen (same routing table as Phase A tap handler)
- Positioned above safe area inset
- Uses existing theme tokens: `colors.surfaceSecondary`, `fontFamily.medium`, `typography.body`
- Animated with `react-native-reanimated` (already in deps) — FadeInUp / FadeOutUp

### Task 5: Infinite scroll on NotificationCenterScreen

- Replace current single-page fetch with `useInfiniteQuery`
- Fetch next page when user scrolls within 200px of bottom
- Pull-to-refresh resets to page 1 and refetches
- Loading indicator at bottom during page fetch
- Existing `NotificationListResponse` from backend already has `has_next` + `page` fields

### Task 6: Reactive badge count

- Remove polling-based `useUnreadNotificationCountData` refetch interval
- Badge count now updates reactively via realtime subscription
- On realtime INSERT → increment count by 1
- On mark-read / mark-all-read → decrement (already handled by mutation invalidation)
- Sync to iOS badge number via `Notifications.setBadgeCountAsync(count)` on each change

### Task 7: Tests

- Unit test `NotificationRealtimeService`: mock Supabase channel, verify connect/disconnect/callback
- Unit test `useNotificationRealtime`: mock service, verify cache invalidation on event
- Snapshot test `NotificationToast`: verify renders with title/body/icon
- Integration test infinite scroll: mock paginated API response, verify next page loads on scroll

---

## UI Spec — NotificationToast

```
┌──────────────────────────────────────────────────┐
│  📊  Portfolio Summary              just now      │
│  Your top movers today: META -1.70%, GOOG +1.09% │
└──────────────────────────────────────────────────┘
```

- Background: `colors.surfaceSecondary` with `overlay.white10` border
- Border radius: 12
- Padding: `spacing.md`
- Icon: same mapping as NotificationCard
- Title: `fontFamily.medium`, `typography.body`, `colors.textPrimary`
- Body: `fontFamily.regular`, `typography.caption`, `colors.textSecondary`, max 2 lines
- Timestamp: `typography.caption`, `colors.textTertiary`
- Shadow: `elevation.md` equivalent

---

## Open Questions / Dependencies

1. **Supabase Realtime quotas:** Check current plan's Realtime connection limit. Each authenticated user holds one persistent connection. For current user base this should be fine, but monitor.

2. **Supabase client availability:** The mobile app currently uses `HttpClient` for all API calls, NOT the Supabase JS client directly. Phase B needs the Supabase JS client for Realtime. Options:
   - Add `@supabase/supabase-js` as a dependency (it may already be installed for auth)
   - Use raw WebSocket — more work, less benefit

3. **Toast vs. system notification:** When a push arrives AND the app is foregrounded, both the system banner (from `setNotificationHandler` in Phase A) and the in-app toast would fire. Decision: disable `shouldShowAlert` in `setNotificationHandler` when app is foregrounded, show only the in-app toast. This avoids duplicate banners.

4. **Notification list ordering:** Realtime INSERT prepends to the list. If the user is mid-scroll on page 3, a prepend to page 1 would shift items. Decision: prepend only if user is at the top of the list; otherwise show a "New notifications" pill that scrolls to top on tap.

5. **Depends on Phase A being merged** — realtime subscription needs the backend notification table and mobile notification infrastructure to be in place.
