- Create mobile/src/api/blob-upload.ts with uploadNoteAttachment() and uploadNoteImage() - All mobile uploads route through shared blobClient from lib/platform.ts - No duplicate SAS logic or raw fetch paths - Update MOBILE_DELEGATION_ROADMAP to mark Block F complete
13 KiB
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.
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, 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_notesroot:pnpm install(workspace:backend,web,mobile). - Private registry: If
.npmrcreferences${GITEA_NPM_HOST}, set env so installs resolve@bytelyst/*. - Baseline:
cd mobile && pnpm run typecheck(andpnpm test) pass on your machine. - Branch:
git checkout -b feat/mobile-<block-name>(one branch per major block is fine).
Note: This repo’s 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 — bootstraps auth, initPlatform(), hydrates stores |
| Entry | mobile/src/app/index.tsx → Redirect to /auth |
| Auth UI | mobile/src/app/auth.tsx — email/password → useAuthStore → /(tabs) |
| Auth + API | mobile/src/api/auth.ts (createAuthClient, MMKV storagePrefix: PRODUCT_ID), mobile/src/api/client.ts (createApiClient + getToken) |
| Platform clients | mobile/src/lib/platform.ts — telemetry init + trackEvent, feature flags, kill switch, blobClient, diagnostics singleton |
| Stores + API modules | auth-store, notes-store, workspace-store, inbox-store; mobile/src/api/notes.ts, workspaces.ts, note-agent-actions.ts |
| Offline queue (module) | mobile/src/lib/offline-queue.ts exports noteOfflineQueue — UI/store wiring is still partial (see capture screen copy) |
| Tabs + detail | (tabs)/ index, search, capture, inbox; mobile/src/app/note/[id].tsx |
| Tests | Vitest on stores (*.test.ts); no @testing-library/react-native in mobile/package.json today |
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
/authon every cold start —indexshould redirect to/(tabs)whenuseAuthStore/getAuthClient()is authenticated (afterbootstrapresolves). - Add register (and optional forgot-password) paths aligned with backend + web, or document why mobile is sign-in only.
- Ensure token refresh behavior matches
@bytelyst/auth-clientexpectations (no silent 401 loops ongetApiClient()).
A.2 Files to read first
mobile/src/app/index.tsx,mobile/src/app/auth.tsx,mobile/src/app/_layout.tsxmobile/src/store/auth-store.ts,mobile/src/api/auth.ts,mobile/src/api/config.ts- Web reference:
web/src/lib/auth.ts, auth pages underweb/src/app/(auth)/
A.3 Optional (platform SDK parity)
If the team standardizes on @bytelyst/react-native-platform-sdk: 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
- Cold start: authed → tabs; unauthed → auth
cd mobile && pnpm run typecheck && pnpm test- Update Current State / any new checklist rows in
AGENT_TASK_ROADMAP.md - 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.tsxor a smallKillSwitchGatewrapper component
B.3 Done criteria
- Manual test with kill switch on/off (or mocked)
- Update
AGENT_TASK_ROADMAP.md - 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.jsonif missing mobile/src/app/_layout.tsx+ new thin components undermobile/src/components/as needed- Reference:
web/src/app/(app)/layout.tsx(or wherever BroadcastBanner / SurveyBanner live)
C.3 Done criteria
- No duplicate/wrong client API (align with current package exports)
- Update
AGENT_TASK_ROADMAP.md - 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.tsxor profile section on a tab mobile/package.jsonif addingfeedback-client
D.3 Done criteria
- Graceful offline / error handling
- Update
AGENT_TASK_ROADMAP.md - 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.tsmobile/src/store/notes-store.ts(and any create/update paths)mobile/src/app/(tabs)/capture.tsx
E.3 Done criteria
- Airplane-mode manual test: queue → reconnect → drain
- Update
AGENT_TASK_ROADMAP.md - 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—blobClientsingleton (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
- Single upload abstraction via
blob-upload.ts; no duplicate fetch/SAS paths - Update
AGENT_TASK_ROADMAP.md - 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 |
| 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
- Runtime telemetry/client metadata uses
expo-constants+ platform metadata helpers - Accessibility labels added on tabs and primary action buttons
- Typecheck/tests pass and changes reflected in
AGENT_TASK_ROADMAP.md - Commit:
e4683ad—fix(mobile): complete block G metadata and accessibility
Block H — RNTL + smoke tests
H.1 Infrastructure
- Add
@testing-library/react-native+@types/react-test-renderertomobiledevDependencies - Extend
mobile/vitest.config.tswith resolve aliases for RN / Expo / RNTL mocks - Create
mobile/__mocks__/with mocks forreact-native,expo-router,expo-constants,expo-status-bar,react-native-mmkv,@testing-library/react-native
H.2 Minimum tests
- Auth screen — import smoke test (valid component, named export, no redirect when unauthenticated)
- Home tab screen — import smoke test (valid component, named export)
H.3 Done criteria
cd mobile && pnpm run typecheck && pnpm test— 32 tests pass (27 store + 5 component)- Update
AGENT_TASK_ROADMAP.md - 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-rendererto produce non-null output for mocked RN host components in Node. Current smoke tests verify import +isValidElementinstead. A future iteration could addjsdomenvironment +@testing-library/reactas 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 - Commit:
feat(mobile): adopt @bytelyst/sync(large; may split)
3 — After every block
cd mobile && pnpm run typecheckcd mobile && pnpm test- Sync
AGENT_TASK_ROADMAP.md - Commit with a clear message; push to remote
- 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 (A–H) identified and fixed:
feedback-client.ts— null coercion ongetAuthToken(commit9d3ac06)offline-queue.ts— missingContent-Type: application/jsonheader on flushsettings.tsx— hardcodedplatform: 'ios'→ dynamicAPP_PLATFORMnotes-store.ts/inbox-store.ts—isLoadingstuck true on API failure (commitc96c785)_layout.tsx— stalelastFlushedbanner when queue already empty (commita412494)- All screens — missing
accessibilityLabelon interactive elements (commit83f4953) - Backend —
trackEventcall signature mismatch + route testdeletemock (commit6acd1a7)
Remaining: Block F (blob uploads) and Block I (sync engine) are deferred/optional.
End of delegation roadmap. Point your agent at: docs/MOBILE_DELEGATION_ROADMAP.md.