# Notification Feature Phases — Status Report

**Generated:** 2026-05-10  
**Status Summary:** Phase A shipped + Phase B largely shipped with fixes pending; Phase C & D fully planned

---

## Phase A — Wire the Gaps

**Status:** ✅ SHIPPED (with follow-up fixes in progress)

### What Was Scoped

Per `.agent-status/notifications-phase-a-status.md`:
- Device token registration wired into app startup (called after successful auth, re-registers on cold start)
- Foreground notification handler configured via `setNotificationHandler` 
- Notification tap routing table — routes by `data.type` to Stock Detail, Portfolio, Notification Center
- Badge sync from unread count (iOS: `setBadgeCountAsync`)
- Backend broadcast endpoint for admin-initiated product updates
- Alert payload contract includes routing `data` dict (type, ticker, alert_id)
- Backend schema migration for Phase A additions (over existing NAN-195/NAN-85/NAN-310 tables)

### What Shipped

**Mobile commits** (per git log):
- `df8a76a9` — gate push token registration to post-onboarding (prevent permission prompt during questionnaire)
- `e65fd054` — Phase B follow-up: focus refetch, unread indicator, APNS routing
- Multiple fixes addressing code review feedback (async anchors, test mocks, cross-session safety)

**Backend branches visible:**
- `feat/notification-phase-a-backend` (remote branch)
- `feat/notif-aps-badge` (remote branch)
- `feat/notif-apns-dual-env` (remote branch)

**Core infrastructure already shipped (from NAN-195, NAN-85, NAN-310):**
- `NotificationService` singleton with APNS (HTTP/2 + JWT) and FCM (HTTP v1) providers
- Device token CRUD with upsert on `device_id`, auto-cleanup on expired tokens
- Notification preferences (22-key JSONB with atomic merge RPC)
- Notification history table with paginated list, read/unread, mark-all-read
- Price, earnings, volume alert checkers (60s price/volume, daily earnings)

### What Is Pending

**Phase A completion items** (per recent commits):
- `d1d8b5f9` — "remove realtime — APNS handles foreground updates" — indicates shift from Phase B's Supabase Realtime approach back to relying on APNS foreground handling
- Unread count freshness validation
- Badge sync validation across foreground/background/kill states

**Notes:** The realtime subscription design from Phase B plan was rolled back in favor of APNS native handling. This simplifies the architecture but means no live in-app toast when notifications arrive while app is foregrounded.

---

## Phase B — Realtime In-App Feed

**Status:** ⚠️ PARTIALLY SHIPPED / REVERTED

### What Was Scoped

Per `.agent-status/notifications-phase-b-plan.md`:
- Supabase Realtime subscription on `notifications` table filtered by `user_id`
- In-app toast/banner component for foreground events
- Reactive badge count (replace polling with realtime)
- Optimistic prepend to NotificationCenterScreen list
- Infinite scroll on NotificationCenterScreen

### What Shipped

**Supabase Realtime preparation:**
- Migration `20260506020100_enable_realtime_on_notifications.sql` — EXISTS in backend repo (enables Realtime on `notifications` table)
- RLS policies already in place from NAN-85

**Mobile changes:**
- React Query hooks for unread count and notification list
- Infinite scroll support on NotificationCenterScreen (per `useNotificationCenterViewModel` contract)
- Badge count observer pattern via COUNT query observer (`68011901` commit)

### What Is Still Pending

**Realtime subscription reverted** — per commit `d1d8b5f9` ("remove realtime — APNS handles foreground updates"):
- The Supabase Realtime channel subscription was removed from the mobile codebase
- Toast component for foreground notifications **NOT implemented**
- In-app realtime events now rely on APNS silent pushes + background refetch instead

**Current state (post-revert):**
- Notifications appear only when user navigates to Notification Center OR when APNS fires
- No live toast banner while using the app
- Badge count updates via React Query cache observer on refetch, not realtime subscription

**Open decision:** The rollback to APNS-only indicates realtime may have had issues (performance, quota, or connection management). Verify if Phase B should be redesigned or if current state is intentional.

---

## Phase C — Backend Notification Trigger Types

**Status:** 📋 PLANNED (NOT YET SHIPPED)

### What It Will Cover

Per `.agent-status/notifications-phase-c-plan.md`:

**Central `NotificationDispatcher` service** — single choke point:
- Check user preference for notification type
- Store in `notifications` table (in-app history)
- Send push via `NotificationService`

**Refactor existing alert checkers** to route through dispatcher:
- Price alerts, earnings alerts, volume alerts now stored in notification history (currently push-only)

**New notification types** (7 types):
1. **Portfolio summary** — daily cron at 20:05 UTC (market close), shows top 3 movers
2. **Earnings alert** — 1 day before earnings  
3. **Volume alert** — threshold exceeded  
4. **Competition update** — event-driven (creation, completion, leaderboard changes)
5. **Learning reminder** — daily reminders for incomplete quests, streak alerts
6. **Watchlist auto-alerts** — ±3% and ±10% moves on all watchlisted stocks (dedup per day per ticker)
7. **Product update** — admin-initiated via broadcast endpoint

**Estimated effort:** 5 days

**Depends on:** Phase A merged (dispatcher integration point)

### Tasks Breakdown

1. New service `notification_dispatcher.py` (~0.5 day)
2. Refactor alert checker to use dispatcher (~0.5 day)
3. Refactor broadcast endpoint to use dispatcher (~0.5 day)
4. Portfolio summary sender cron (~1 day)
5. Learning reminder sender cron (~1 day)
6. Competition update hooks (~0.5 day)
7. Watchlist auto-alerts with dedup (~1 day)

---

## Phase D — Polish

**Status:** 📋 PLANNED (NOT YET SHIPPED)

### What It Will Cover

Per `.agent-status/notifications-phase-d-plan.md`:

**Backend hardening:**
1. **Quiet hours** — per-user time window (22:00–08:00) to suppress pushes (in-app notifications still appear)
   - Timezone-aware via user preference
   - Mobile UI: time picker + timezone override
   
2. **Rate limiting** — max 5 pushes per hour per notification type per user
   - Lightweight counter in `notifications` table
   - Configurable limits per type (price_alert: 5, earnings: 2, learning: 2, etc.)
   - In-app notifications still created; pushes suppressed

3. **Notification grouping**
   - iOS: thread-id in APNS payload (group by ticker or type)
   - Android: tag field in FCM (collapse/replace behavior for portfolio summary, learning reminders)

4. **Badge sync correctness** across app states (foreground, background, killed)
   - Set badge in push payload from unread count
   - Clear badge on app open via background listener

5. **Retention policy** — auto-delete read notifications older than 90 days
   - Daily cleanup cron at 03:00 UTC
   - Unread notifications never auto-deleted

**Estimated effort:** 2 days

**Depends on:** Phase A + C merged (Phase B nice-to-have but not blocking)

### Tasks Breakdown

1. Quiet hours backend (~0.5 day)
2. Quiet hours mobile UI (~0.5 day)
3. Rate limiting (~0.5 day)
4. Notification grouping (~0.5 day)
5. Badge sync correctness (~0.25 day)
6. Retention policy cron (~0.25 day)

---

## Open Items / Risks / Dependencies

### Critical Blockers
- **Phase A completion:** Verify that APNS-only foreground handling is sufficient (realtime rollback rationale unclear). Confirm phase B toast component is intentionally deferred.
- **Realtime decision:** Phase B's Supabase Realtime was enabled in backend (`20260506020100_enable_realtime_on_notifications.sql`) but removed from mobile code (`d1d8b5f9`). Need clarification: is this a temporary rollback pending fixes or a permanent architectural shift?

### High Priority (before Phase C shipping)
- Watchlist auto-alert deduplication schema (dedup log to prevent same-day repeats)
- Competition service integration point (check if `services/main/app/services/` has competition logic; if not, Phase C task 6 is a stub)
- SmartScore change detection threshold (NAN-85 has toggle but no spec for what constitutes a "change")
- Major news pipeline hook (if news ingestion exists, identify trigger point)

### Phase C Unknowns
- Portfolio insights and achievements toggles exist but trigger logic is TBD (likely AI-generated summaries or badge system hooks)
- Learning quest system status (Phase C assumes quest data exists in database)
- Watchlist performance: checking 20 stocks × 10,000 users per 60s tick = 200,000 ops/tick. Design for batching by ticker.

### Phase D Considerations
- Badge count computation (`unread_count + 1` per push) adds DB query per notification. For bulk sends (portfolio summary), consider cheaper alternatives (set badge to 99+, correct on app open).
- Time picker component selection (check if `@react-native-community/datetimepicker` already in deps; if not, consider text-input dropdowns to avoid new dependency).
- Rate limit scope: per-type per-hour limits proposed, but NAN-57 mentions "max 20/day per user" globally — confirm if both limits are desired.

### Source Documents
- `.agent-status/notifications-phase-a-status.md` — Phase A spec + what landed
- `.agent-status/notifications-phase-b-plan.md` — Phase B spec
- `.agent-status/notifications-phase-c-plan.md` — Phase C spec with builder patterns
- `.agent-status/notifications-phase-d-plan.md` — Phase D polish tasks
- `.agent-status/notifications-design.md` — architectural research + decisions
- Backend migration: `supabase/migrations/20260506020100_enable_realtime_on_notifications.sql`
- Mobile git branch: `fix/notifications-phase-b-followup` (follow-up fixes on Phase B behavior)
