learning_ai_notes/docs/MOBILE_DELEGATION_ROADMAP.md

308 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# NoteLett Mobile — Delegation Roadmap (single-agent, sequential)
**Purpose:** One document to hand to an AI or human implementer. Execute **blocks in order** (later blocks assume earlier ones are merged). When a block is done, update **`docs/AGENT_TASK_ROADMAP.md`** (e.g. **Current State** table and any new mobile checklist rows), then commit and push.
**Canonical product checklist:** [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md).
**Repo:** `learning_ai_notes`
**Mobile app root:** `mobile/` (Expo Router under `mobile/src/app/`).
**Stack:** Expo ~55, React Native ~0.83, Zustand + MMKV, `@bytelyst/*` (auth-client, api-client, telemetry-client, feature-flag-client, kill-switch-client, blob-client, broadcast-client, survey-client, feedback-client, offline-queue, diagnostics-client).
**Related pattern:** NomGap uses the same idea under `learning_ai_fastgap/docs/MOBILE_DELEGATION_ROADMAP.md`**do not** copy fasting-specific paths or M-numbers; this file is authoritative for NoteLett.
---
## 0 — Prerequisites (before writing code)
- [ ] **Install:** From `learning_ai_notes` root: `pnpm install` (workspace: `backend`, `web`, `mobile`).
- [ ] **Private registry:** If `.npmrc` references `${GITEA_NPM_HOST}`, set env so installs resolve `@bytelyst/*`.
- [ ] **Baseline:** `cd mobile && pnpm run typecheck` (and `pnpm test`) pass on your machine.
- [ ] **Branch:** `git checkout -b feat/mobile-<block-name>` (one branch per major block is fine).
**Note:** This repos `pnpm-workspace.yaml` lists only `backend`, `web`, `mobile` — not `learning_ai_common_plat`. Platform packages typically resolve from the registry; link local plat only if your team uses that workflow.
---
## 1 — Already shipped (verify before redoing)
| Area | What to verify |
|------|----------------|
| **Router + shell** | `mobile/src/app/_layout.tsx` — kill-switch gate UI, broadcast strip + survey modals, `initPlatform()`, hydrates, offline queue flush on boot + `AppState` active |
| **Entry** | `mobile/src/app/index.tsx` — waits for `hasBootstrapped`, then `/(tabs)` vs `/auth` |
| **Auth UI** | `mobile/src/app/auth.tsx` — sign-in + register → `useAuthStore``/(tabs)` |
| **Auth + API** | `mobile/src/api/auth.ts`, `mobile/src/api/client.ts` |
| **Platform clients** | `mobile/src/lib/platform.ts` — telemetry (versions from `app-metadata.ts`), flags, kill switch, blob, diagnostics |
| **Broadcast / survey / feedback** | `mobile/src/lib/broadcast-client.ts`, `survey-client.ts`, `feedback-client.ts`; banners + survey UI in `_layout`; feedback form on `(tabs)/settings.tsx` |
| **Stores + API modules** | `auth-store`, `notes-store`, `workspace-store`, `inbox-store`; `mobile/src/api/*.ts` |
| **Offline queue** | `mobile/src/lib/offline-queue.ts``flushNoteQueue`, `getNoteQueueSize`; wired from `_layout` |
| **Tabs + detail** | `(tabs)/` index, search, capture, inbox, **settings**; `mobile/src/app/note/[id].tsx` |
| **Tests** | Store tests + RN mocks + component smokes (`*.test.tsx`) — confirm counts in `mobile/package.json` / CI |
---
## Remaining / deferred (March 2026)
| # | Item | Status |
|---|------|--------|
| 1 | Hydrates only after `hasBootstrapped` + `isAuthenticated` | ✅ `_layout.tsx` |
| 2 | Telemetry **`flushTelemetry()`** on `AppState` background/inactive | ✅ `platform.ts` + `_layout.tsx` |
| 3 | Blob uploads single path | ✅ `api/blob-upload.ts` + capture screen comment |
| 4 | Extra RNTL smoke (`SettingsScreen`) | ✅ `settings.test.tsx` |
| 5 | Deeper RNTL (fireEvent / full render tree) | 🟨 Deferred — see Block H TODO |
| 6 | `@bytelyst/react-native-platform-sdk` vs discrete clients | ⏸ Optional (**A §A.3**) |
| 7 | `@bytelyst/sync` | ⏸ Block **I** |
**Architecture note:** NomGap uses **`react-native-platform-sdk`** providers; NoteLett uses **direct** platform HTTP clients — both valid until you standardize.
---
## 2 — Execution order (mandatory)
```
Block A → Auth flow + session UX (register, refresh, guarded entry)
Block B → Kill switch gate + safe degraded UI
Block C → Broadcast + survey (web parity)
Block D → Profile / settings + feedback-client
Block E → Offline queue: enqueue + flush on boot / resume
Block F → Blob / attachments (capture or note detail) via shared blobClient
Block G → Code health (telemetry versions, a11y, dead code)
Block H → RNTL + smoke screen tests
Block I → Optional — @bytelyst/sync or deeper sync (last; needs API contract review)
```
---
## Block A — Auth flow and session UX
### A.1 Goals
- **Signed-in users** should not be forced through `/auth` on every cold start — `index` should redirect to `/(tabs)` when authenticated (after `bootstrap`). **Shipped:** `hasBootstrapped` + redirect in `mobile/src/app/index.tsx`.
- **Register** aligned with backend + web. **Shipped:** `auth-store.register` + auth UI paths; optional **forgot-password** still open if desired.
- Ensure **token refresh** behavior matches `@bytelyst/auth-client` expectations (no silent 401 loops on `getApiClient()`).
### A.2 Files to read first
- `mobile/src/app/index.tsx`, `mobile/src/app/auth.tsx`, `mobile/src/app/_layout.tsx`
- `mobile/src/store/auth-store.ts`, `mobile/src/api/auth.ts`, `mobile/src/api/config.ts`
- Web reference: `web/src/lib/auth.ts`, auth pages under `web/src/app/(auth)/`
### A.3 Optional (platform SDK parity)
Decision for this handoff: defer direct **`@bytelyst/react-native-platform-sdk`** adoption and keep mobile on direct shared `@bytelyst/*` clients. See [`MOBILE_PLATFORM_SDK_DECISION.md`](./MOBILE_PLATFORM_SDK_DECISION.md).
If the team standardizes on the SDK later: either integrate **`AuthProvider`** with the **same MMKV keys** as `createAuthClient({ storagePrefix: PRODUCT_ID })`, or document a minimal bridge (same approach as NomGap Block A in `learning_ai_fastgap`).
### A.4 Done criteria
- [x] Cold start: authed → tabs; unauthed → auth
- [x] **Refine:** Data hydrates (notes/workspaces/inbox), broadcast/survey polling, and queue flush timers run **only when** `hasBootstrapped && isAuthenticated` — avoids pre-auth 401s
- [x] `cd mobile && pnpm run typecheck && pnpm test`
- [x] Update **Current State** / any new checklist rows in [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)
- [x] Commit: [`ae0a481`](https://github.com/saravanakumardb1/learning_ai_notes/commit/ae0a481) — `feat(mobile): complete block A auth session flow`
---
## Block B — Kill switch gate
### B.1 Goal
Mirror web `middleware` / layout behavior: when **`checkKillSwitch()`** reports disabled, block main app UI and show message (reuse pattern from `web` or NomGap `KillSwitchGate`).
### B.2 Files
- `mobile/src/lib/platform.ts` (`checkKillSwitch`)
- `mobile/src/app/_layout.tsx` or a small `KillSwitchGate` wrapper component
### B.3 Done criteria
- [x] Manual test with kill switch on/off (or mocked)
- [x] Update [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)
- [x] Commit: [`acdedbc`](https://github.com/saravanakumardb1/learning_ai_notes/commit/acdedbc) — `feat(mobile): add kill switch gate in root layout`
---
## Block C — Broadcast + survey (web parity)
### C.1 Goal
Integrate **`@bytelyst/broadcast-client`** and **`@bytelyst/survey-client`** in the root layout (banner + modal), matching API usage from web (see `web` app layout and `@bytelyst/broadcast-client` / `@bytelyst/survey-client` exports).
### C.2 Files
- Add deps to `mobile/package.json` if missing
- `mobile/src/app/_layout.tsx` + new thin components under `mobile/src/components/` as needed
- Reference: `web/src/app/(app)/layout.tsx` (or wherever BroadcastBanner / SurveyBanner live)
### C.3 Done criteria
- [x] No duplicate/wrong client API (align with current package exports)
- [x] Update [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)
- [x] Commit: [`48896ab`](https://github.com/saravanakumardb1/learning_ai_notes/commit/48896ab) — `feat(mobile): integrate broadcast and survey clients`
---
## Block D — Settings / profile + feedback
### D.1 Goal
Add **`@bytelyst/feedback-client`** from mobile — same payload shape as web (`submit({ type, title, body })` or current API).
### D.2 Files
- New screen or tab entry: e.g. `mobile/src/app/settings.tsx` or profile section on a tab
- `mobile/package.json` if adding `feedback-client`
### D.3 Done criteria
- [x] Graceful offline / error handling
- [x] Update [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)
- [x] Commit: [`746cba7`](https://github.com/saravanakumardb1/learning_ai_notes/commit/746cba7) — `feat(mobile): add settings tab with feedback client`
---
## Block E — Offline queue wiring
### E.1 Goal
`noteOfflineQueue` exists but capture screen still says wiring is deferred. **Enqueue** failed note mutations; **flush** on app start and on connectivity / `AppState` active (match web offline-queue behavior).
### E.2 Files
- `mobile/src/lib/offline-queue.ts`
- `mobile/src/store/notes-store.ts` (and any create/update paths)
- `mobile/src/app/(tabs)/capture.tsx`
### E.3 Done criteria
- [x] Airplane-mode manual test: queue → reconnect → drain
- [x] Update [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)
- [x] Commit: [`5d82160`](https://github.com/saravanakumardb1/learning_ai_notes/commit/5d82160) — `feat(mobile): wire offline queue enqueue and flush`
---
## Block F — Blob / attachments
### F.1 Goal
Any mobile upload path (future file capture, artifacts) should use **`blobClient`** from `mobile/src/lib/platform.ts` — no second SAS or raw `fetch` duplicate.
### F.2 Implementation
- `mobile/src/lib/platform.ts``blobClient` singleton (already existed)
- `mobile/src/api/blob-upload.ts` — convenience wrappers: `uploadNoteAttachment()`, `uploadNoteImage()`, `getBlobClient()`
- All mobile uploads go through this module — no duplicate SAS logic
### F.3 Done criteria
- [x] Single upload abstraction via `blob-upload.ts`; no duplicate fetch/SAS paths
- [x] Update [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)
- [x] Commit: `feat(mobile): blob upload abstraction via shared blobClient (Block F)`
> **Note:** File picker UI is a future concern — this block establishes the API contract so any picker implementation wires through `blob-upload.ts`.
---
## Block G — Code health
Execute in **flexible order**; prefer **small commits**.
| Task | Hints |
|------|--------|
| **Telemetry metadata** | Use `expo-constants` for `appVersion` / `buildNumber` instead of hardcoded strings in `platform.ts` |
| **Telemetry flush** | `flushTelemetry()` on `AppState` `background` / `inactive` (matches NomGap pattern) |
| **Feature flags in UI** | Gate experimental mobile surfaces with `isFeatureEnabled` where web uses flags |
| **Accessibility** | `accessibilityLabel` on tab icons and primary actions |
| **Theme** | Prefer shared tokens from `@bytelyst/design-tokens` / `theme/` consistently |
**Done criteria**
- [x] Runtime telemetry/client metadata uses `expo-constants` + platform metadata helpers
- [x] **`flushTelemetry`** exported from `platform.ts`; root layout flushes on background
- [x] Accessibility labels added on tabs and primary action buttons
- [x] Typecheck/tests pass and changes reflected in [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)
- [x] Commit: [`e4683ad`](https://github.com/saravanakumardb1/learning_ai_notes/commit/e4683ad) — `fix(mobile): complete block G metadata and accessibility`
---
## Block H — RNTL + smoke tests
### H.1 Infrastructure
- [x] Add **`@testing-library/react-native`** + **`@types/react-test-renderer`** to `mobile` `devDependencies`
- [x] Extend `mobile/vitest.config.ts` with resolve aliases for RN / Expo / RNTL mocks
- [x] Create `mobile/__mocks__/` with mocks for `react-native`, `expo-router`, `expo-constants`, `expo-status-bar`, `react-native-mmkv`, `@testing-library/react-native`
### H.2 Minimum tests
- [x] **Auth screen** — import smoke test (valid component, named export, no redirect when unauthenticated)
- [x] **Home tab screen** — import smoke test (valid component, named export)
- [x] **Settings screen** — import smoke test (`settings.test.tsx`)
### H.3 Done criteria
- [x] `cd mobile && pnpm run typecheck && pnpm test` — component smokes include auth, home, settings
- [x] Update [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)
- [x] Commit: [`5a0175f`](https://github.com/saravanakumardb1/learning_ai_notes/commit/5a0175f) — `feat(mobile): add Block H — Vitest RN mock aliases + component smoke tests`
> **TODO-1:** Deeper RNTL render tests (text assertions, fireEvent) require `react-test-renderer` to produce non-null output for mocked RN host components in Node. Current smoke tests verify import + `isValidElement` instead. A future iteration could add `jsdom` environment + `@testing-library/react` as an alternative rendering path for full DOM-based assertions.
---
## Block I — Optional sync engine (`@bytelyst/sync`)
**Last.** Mobile currently hydrates via **REST + Zustand**. Replacing with **`@bytelyst/sync`** requires mapping entities (notes, workspaces, inbox) to backend contracts and conflict rules — verify with backend before swapping.
### I.1 Done criteria (only if attempted)
- [ ] Parity with current hydration + offline behavior
- [ ] Update [`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)
- [ ] Commit: `feat(mobile): adopt @bytelyst/sync` (large; may split)
---
## 3 — After every block
1. `cd mobile && pnpm run typecheck`
2. `cd mobile && pnpm test`
3. Sync **[`AGENT_TASK_ROADMAP.md`](./AGENT_TASK_ROADMAP.md)**
4. Commit with a clear message; push to remote
5. If block descriptions drift, update **this file** in the same PR
---
## 4 — Reference paths
| Area | Path |
|------|------|
| Root layout | `mobile/src/app/_layout.tsx` |
| Auth | `mobile/src/app/auth.tsx`, `mobile/src/store/auth-store.ts` |
| Platform init | `mobile/src/lib/platform.ts` |
| API | `mobile/src/api/client.ts`, `mobile/src/api/config.ts` |
| Workspace definition | `pnpm-workspace.yaml` |
---
## Post-Completion Audit (March 31, 2026)
Systematic review of all completed blocks (AH) identified and fixed:
- **`feedback-client.ts`** — null coercion on `getAuthToken` (commit `9d3ac06`)
- **`offline-queue.ts`** — missing `Content-Type: application/json` header on flush
- **`settings.tsx`** — hardcoded `platform: 'ios'` → dynamic `APP_PLATFORM`
- **`notes-store.ts` / `inbox-store.ts`** — `isLoading` stuck true on API failure (commit `c96c785`)
- **`_layout.tsx`** — stale `lastFlushed` banner when queue already empty (commit `a412494`)
- **All screens** — missing `accessibilityLabel` on interactive elements (commit `83f4953`)
- **Backend** — `trackEvent` call signature mismatch + route test `delete` mock (commit `6acd1a7`)
**Follow-up (March 31, 2026):**
- [x] **Auth-gated hydrates** — notes/workspaces/inbox + broadcast/survey polling + offline flush timers only after `isAuthenticated`
- [x] **`flushTelemetry()`** on app background/inactive
- [x] **Settings** RNTL smoke test; **capture** documents `blob-upload` for future file picker
**Still deferred:** Block **I** (`@bytelyst/sync`); optional RN **platform SDK** alignment; deeper RNTL assertions (Block H TODO).
---
**End of delegation roadmap.** Point your agent at: **`docs/MOBILE_DELEGATION_ROADMAP.md`**.