Appearance
Mobile app strategy
Status: Active architecture decision. v1.1, 2026-05-26.
Supersedes: the working plan written 2026-04-30 at
~/.claude/plans/okay-this-is-a-starry-diffie.md(local, not in git). This doc is the canonical version; the local plan file stays as a historical artifact but is no longer the source of truth.Reconciled: 2026-05-15 against everything shipped between April 30 and May 15 (AWS migration close, portal-entry-handoff design contract, Florence AI text + Phase 1.5 voice plan, ENG-187 member portal Phase A, ADR 0006 Mongo user simplification, ADR 0008 four-layer testing strategy, member-portal CSFLE encryption posture), then revised the same day per explicit founder direction (see Founder direction (2026-05-15)).
Reconciled again: 2026-05-26 against shipped reality across 68 commits + 7 minor releases (v0.52.0 → v0.56.0). Three load-bearing reconciliations: (a) Phase 0 monorepo refactor is DONE (ENG-349 M0, shipped 2026-05-17 —
apps/web,apps/mobileplaceholder,packages/sharedlive on main); (b) Florence AI shipped on prod at/florencewith ElevenLabs Conversational AI (ENG-356), a vendor pivot from the Deepgram + Cartesia path inflorence-ai/voice.md— picked to move fast and because ElevenLabs has an agents mode that fits the WOW experience; (c) MongoDB Atlas HIPAA BAA signed (ENG-270, was on the Phase 3 blocker list). Detail in Founder direction (2026-05-26).v1 scope pivoted same day (2026-05-26 late): mobile v1 is now the native Florence flow — not the marketing-tier funnel that was Phase 1 in the morning draft. Mobile gets native control over the interactivity loop (audio session, presence states, haptics, lock-screen controls) that mobile web cannot deliver. Demo posture for first ship (test data, no real PHI). PostHog HIPAA Cloud is the analytics layer, lean event model derived from the Florence conversation arc. The marketing-tier funnel (home → plans → filter → waitlist) is reframed as a deferred Phase 1.5. Detail in Founder direction (2026-05-26).
Tracking: no Linear issue yet. Mobile app work is gated on the blockers in "What unblocks this plan" below. When Phase 1 (mobile v1) is approved, a Linear initiative is filed at that point.
The one paragraph
A native mobile app for AskFlorence is anticipated by multiple existing surfaces (portal entry handoff design contract, Florence AI voice plan, member portal handoff design). The stack decision, made April 30 and validated by a 2026-05-15 research pass, is React Native via Expo (managed workflow), native experience end to end. No webview wrapping, ever (founder direction). Reuse between web and mobile happens at the LOGIC layer (packages/shared: calculator pipeline, eligibility math, types, schemas), never at the UI layer. The monorepo substrate this doc called for has shipped — apps/web, apps/mobile (placeholder), packages/shared are live on main as of 2026-05-17 (ENG-349 M0). Florence AI also shipped on web at /florence on ElevenLabs Conversational AI (ENG-356). The first mobile release (v1) is the native Florence flow — same conversational arc the web /florence delivers (greeting → ZIP + household + income → find_plans → narrowing via check_drugs and check_providers → email-capture moment → best-plan reveal), built natively so the interactivity loop (audio session, listening/thinking/speaking presence states, haptics) is premium and tight in a way mobile web cannot match. Demo posture for first ship (test data, no real PHI yet). PostHog HIPAA Cloud is the analytics + error-tracking + session-replay layer (decision-ask #7, answered 2026-05-26 — supersedes ADR 0009 OpenPanel + GlitchTip pending the ADR 0010 follow-up). The traditional marketing-tier funnel (home → plans → filter → waitlist) is reframed as a deferred Phase 1.5, optional after Florence v1 stabilizes. Native enrollment + member-portal features come after web member-enrollment ships. The deliverable order is now Florence-native v1 first, marketing-funnel screens (if added) Phase 1.5, native enrollment Phase 3, agent companion app last. None of this is being built yet — the monorepo slot at apps/mobile/ is reserved and intentionally empty per its README. What changes today is the v1 framing flips from marketing-funnel-first to Florence-first, with PostHog HIPAA as the analytics layer.
Reconciliation log: what changed since April 30
The April 30 plan was written with the assumption that AWS migration was "days from close" and that the portal entry contract, Florence AI architecture, and member portal scope were all in design. Most of that has now shipped. This doc absorbs the change.
| Shipped since 2026-04-30 | Where to read it | Impact on this plan |
|---|---|---|
| AWS migration closed (issue #47 closed 2026-05-11) | CLAUDE.md Architecture section (repo root, not in docs/) | The "starts after AWS migration closes" gate has cleared. Phase 0 can be approved any time the other gates also clear. |
| Portal entry handoff design contract | portal-entry-handoff.md | The contract documents two future mobile patterns: Pattern 1 (deeplink into a web view) and Pattern 2 (fully native against the same API). Founder direction 2026-05-15 selects Pattern 2 and rejects Pattern 1 outright - no webview, ever. What this doc inherits from the contract is the server-side invariant: the canonical POST /api/enroll/applications entry endpoint with a self-contained payload, identical for every surface. entryChannel enum reserves "ios_native" and "android_native" (portal-entry-handoff.md:73-76). The presentation layer is native. |
| Florence AI runtime model | florence-ai/index.md, florence-ai/voice.md | Florence runs on Claude Agent SDK (Haiku 4.5 / Sonnet 4.6 / Opus 4.7), not "Bedrock" generically as the April 30 plan said. The voice section already spells out on-device ASR/TTS for the native app (voice.md:134-143), with iOS SFSpeechRecognizer + AVSpeechSynthesizer as the privacy-preferred path. |
Member portal Phase A on app.askflorence.health | briefs/member-portal-plan.md | Portal subdomain split is live. CSP blocks all third-party trackers on portal. Umami self-hosted is the only allowed analytics. Magic-link auth + member-applications collection are designed. The mobile app inherits this PHI boundary. |
| ADR 0006 Mongo user simplification | adr/0006-mongo-user-simplification.md | The 4-user functional model (app_read, app_write, app_audit_writer, app_admin_schema) replaces the speculative per-feature narrow users referenced in the April 30 plan. Mobile work doesn't add Mongo users; tenant isolation moves to JWT-in-middleware. |
| ADR 0008 E2E testing strategy + L1 to L4 model | adr/0008-e2e-testing-strategy.md, development/testing-strategy.md | The April 30 plan had only generic mention of "smoke tests." Mobile testing strategy now has a concrete L1-L4 ladder to fit into. See "Near-term next steps" section 2. |
| Member portal CSFLE encryption posture | security-compliance/sensitive-data-handling-member-portal.md | SSN, immigration document numbers, full DOB, bank routing + account are encrypted at rest via CSFLE field-level encryption. Card PAN is tokenized via payment vendor and never stored in member_applications. Mobile inherits these rules; the April 30 plan's compliance table understated this. |
| Florence-ai roadmap "Native app timeline" open question | florence-ai/roadmap.md (line 195) | This doc answers the question. Mobile v1 is marketing-tier (calculator/plans/waitlist) and ships independently of the member portal and Phase 5. Florence + on-device voice (voice.md:134-143) arrive in a later mobile phase, after native enrollment, not in v1. |
| Founder direction (no webview, re-scoped v1, CI/CD parity) | This doc, 2026-05-15 conversation | Three calls absorbed: (1) native experience end to end, no webview wrapping under any circumstances; reuse is logic-level only; (2) v1 = the existing web marketing flow (home, plans showcase, filtering, waitlist) to capture users; native enrollment is a later release after web member-enrollment ships; (3) mobile gets a CI/CD + automated regression posture analogous to the web L1-L4 model, adjusted for mobile, to preserve shipping pace. Detail in Founder direction (2026-05-15). |
| ENG-349 M0 monorepo refactor SHIPPED 2026-05-17 | apps/web/, apps/mobile/ (placeholder per apps/mobile/README.md), packages/shared/ (@askflorence/shared) on main | The "Phase 0 monorepo refactor, do this NOW" recommendation in this doc is now fulfilled, not pending. apps/mobile/ is the reserved slot waiting for the Expo app; the README there is explicit: "Reserved for the mobile v1 Expo app (ENG-349 Phase 1, NOT this PR). Intentionally empty in M0." Phase 0 sections below stay as historical record + acceptance criteria reference. |
| Florence AI shipped on web with ElevenLabs Conversational AI (ENG-356, v0.52.0 2026-05-19 + iteration through v0.56.0) | apps/web/src/app/florence/page.tsx, apps/web/src/app/api/florence/{agent-session,byo-llm,flag}/route.ts, apps/web/src/lib/florence/agent/{provision,quota}.ts, apps/web/src/components/florence/FlorenceRoot.tsx | Vendor pivot from the Deepgram + Cartesia path documented in florence-ai/voice.md: the production implementation uses ElevenLabs Conversational AI over WebRTC. Founder rationale: move fast + ElevenLabs ships an agents mode that fits the rendered WOW experience. Hard-gated on FLORENCE_WOW_DEMO_ENABLED (404 when off), token-mediated (server-side ELEVENLABS_API_KEY, short-lived WebRTC conversation token to client), ENG-361 preflight quota guardrail (hard-fail 503 with public message when >=95% of subscription used). Mobile sections below now reference this surface, not the planned Deepgram + Cartesia adapter sinks. |
| MongoDB Atlas HIPAA BAA signed (ENG-270, 2026-05-15) | security-compliance/vendor-register | Was on the Phase 3 hard-blocker list in this doc. Cleared. |
| v1 scope pivot: Florence-first (founder direction, 2026-05-26 late) | This doc, Founder direction (2026-05-26) | v1 is now the native Florence flow, not the marketing-tier funnel that was Phase 1 in the same-day morning draft. Native gives us the premium interactivity loop the web Florence cannot match. Demo posture for first ship (test data). PostHog HIPAA Cloud is the analytics layer. Marketing-funnel screens become an optional Phase 1.5. |
| Analytics direction: PostHog HIPAA Cloud (decision-ask #7 answered 2026-05-26) | This doc, decision-ask #7 + a follow-up ADR 0010 | Replaces the ADR 0009 OpenPanel + GlitchTip path. PostHog HIPAA consolidates analytics + error tracking + session replay + feature flags + surveys + LLM observability in one workspace under one BAA. Setup is hours not days. ADR 0010 to be drafted in a follow-up PR; mobile v1 ships against PostHog from day one. |
Update against April 30 baseline (2026-05-15 research pass)
Three targeted questions revisited the April 30 stack decision against the 2025-26 landscape. Each is summarized with the verdict on whether the April 30 call still holds.
Apple's PWA stance under the EU DMA (2025-26)
Question: Has Apple's PWA stance shifted such that "PWA-first" becomes credible for the member app?
Findings:
- Apple does not accept PWAs in the App Store. App Store policy requires native binaries compiled through Xcode; PWAs are excluded by Guideline 4.2. No indication Apple plans to change this.
- In the EU only, Home Screen web apps work via WebKit under DMA pressure. Apple briefly attempted to remove standalone PWA support in iOS 17.4 (early 2024), reversed after public backlash, and Home Screen web apps now continue using WebKit in the EU.
- Outside the EU (which includes our market, the US), PWA capabilities on iOS are still limited compared to native apps: no reliable push, no background sync, no rich notification UI, no App Privacy disclosure surface.
- January 1, 2026 Apple moves to a single business model in the EU (Core Technology Commission replaces the Core Technology Fee). Fee structure, not policy posture, changes.
Verdict: April 30 rejection of "PWA-first / Capacitor wrap" still holds for our market. The compounding install + push + ranking + brand-surface value of a real App Store presence is not available via PWA in the US. Reopen this if (a) Apple extends EU PWA capabilities to the US, or (b) the US market becomes a small-enough slice of our user base that EU-style PWA distribution is enough (not foreseeable).
React Native New Architecture status in production (2025-26)
Question: Are recent RN releases addressing the performance gaps the April 30 plan implicitly hedged against?
Findings:
- New Architecture (JSI + Fabric + TurboModules) has been the default since RN 0.76 (late 2024). The old bridge was permanently retired in RN 0.82.
- Performance gains versus the old architecture are substantial: cold start times reduced about 43%, render speeds up about 39%, memory usage down about 26%, JS-to-native call performance up about 40x. Reported App Start Time improvement 3.2s to 1.8s (44% reduction), List Scroll FPS 47 to 59 (25% increase).
- Hermes V1 is the default JavaScript engine on both iOS and Android as of RN 0.84. Ahead-of-time bytecode compilation, lower memory floor, faster cold start versus JavaScriptCore.
- Fabric enables synchronous layout measurement, React 18 concurrent rendering, and direct JSI-backed call paths to native modules. No serialization bridge.
Verdict: April 30 RN/Expo recommendation gets stronger. The performance posture in 2026 closes the gap that historically motivated "well, maybe native iOS for premium feel." For a one-engineer team building a premium consumer health app, RN with the New Architecture is closer to native parity than at any prior point in the framework's history.
Expo SDK 54/55 health (2026)
Question: Is Expo's managed workflow still the right call in 2026?
Findings:
- Expo SDK 54 is the current stable release. Compatible with iOS 18 and Android 15 APIs.
- Expo SDK 55 is in active beta running React Native 0.83 with the New Architecture permanently enabled. Clear upgrade path for production teams.
- Expo is not HIPAA-certified and does not sign BAAs. Expo Application Services (EAS) Build + Update + Submit handle app code, never user data. Patient information stays in our backend, our auth provider, our database. This matters for the BAA-coverage audit (#57): we list Expo as a vendor that processes "code, not user data" and document why no BAA is required (the Expo product never sees PHI).
- EAS Update reduces compliance-critical patch deploy time from weeks to hours via OTA JS updates. Useful for the long tail of UX/copy fixes; binary release still required for new features (App Store policy).
- Healthcare apps shipping on Expo today is a known pattern.
Verdict: Expo managed workflow still appropriate. The "Expo does not sign a BAA" finding is not a blocker; it's a documentation item. Expo never processes user data, so the BAA framework doesn't apply. Document this in the #57 BAA audit with explicit "vendor scope: app code only, no user data" language.
What this research did NOT reopen
- Flutter versus React Native: same arguments as April 30 still hold. Throwing away the team's React + TypeScript fluency is the structural problem; performance was never the deciding factor.
- Native iOS + Android (Swift + Kotlin): same one-person-team economics still hold. The 2026 RN perf data narrows the "native feels better" argument further.
- Team appetite for native iOS: this is a Taha call, not a research finding. Surfaced in "Decision asks" below.
Founder direction (2026-05-15)
After reviewing the draft, Taha gave three explicit directions that override the April 30 plan where they conflict. They are load-bearing for every section below.
- No webview. Native experience end to end. The portal-entry-handoff contract's "Pattern 1" (deeplink into an in-app webview) is rejected. The mobile app is a native experience. Reuse between web and mobile is achieved by simplifying the underlying logic and sharing it (
packages/shared: calculator pipeline, eligibility math, types, zod schemas) - never by embedding web UI. When native enrollment is built, it is built native against the API, not as a wrapped web flow. - v1 scope = the existing web marketing flow. The first mobile release is home, plans showcase, plan filtering, and waitlist capture - to capture users. It builds directly on what is already shipped on web (the
useCalculatorpipeline, the plans browse/filter, the/api/waitlistcapture). It carries no PHI and needs no auth. Enrolling is a later release, built after web member-enrollment is finished, then proceeds accordingly. - CI/CD + automated regression parity. Mobile gets a continuous-integration and automated-regression posture analogous to the web platform's L1-L4 model (preflight, PR-CI, post-deploy smoke, nightly drift), adjusted for what mobile needs (EAS Build, OTA channels, device/simulator runners, Maestro flows). The goal is to preserve the high shipping pace the platform has run at so far.
These directions de-risk v1 substantially: removing PHI and auth from the first release decouples it from the member-portal, agent-portal, and BAA gate work entirely. The blocker list in What unblocks this plan reflects this.
Founder direction (2026-05-26)
Eleven days later, three shipped facts were absorbed into this doc, then three new strategy decisions were made later the same day.
Reality-check confirmations (morning):
- Monorepo refactor is done (ENG-349 M0, 2026-05-17). The "do this NOW" call in Near-term next steps section 1 is fulfilled.
apps/web/,apps/mobile/placeholder, andpackages/shared/(@askflorence/shared) are live. The mobile-v1 build picks up the slot atapps/mobile/directly — no preliminary refactor required. - Voice vendor is ElevenLabs Conversational AI, not the planned Deepgram + Cartesia path. The web Florence implementation (ENG-356, shipped v0.52.0 on 2026-05-19, iterated through v0.56.0) uses ElevenLabs over WebRTC. Founder rationale: ElevenLabs has agents mode out of the box (matches the WOW experience), and moving fast beat the additional procurement work to onboard two separate vendors. On-device iOS/Android voice (
SFSpeechRecognizer+AVSpeechSynthesizer) remains the privacy-preferred default for native; the server-side fallback now points at the same ElevenLabs agent-session surface the web Florence uses. - MongoDB Atlas HIPAA BAA signed (ENG-270). One blocker off the Phase 3 hard-blocker list.
No reversals from the May-15 direction on these three — same native-only, same logic-layer reuse, same CI/CD parity goal.
New strategy decisions (late same day):
- v1 scope pivots to Florence-first. The first mobile release is the native Florence flow, mirroring what
/florencedoes on web today (greeting + ZIP + household + income →find_plansvia the shared pipeline → narrowing viacheck_drugs+check_providers→ mid-conversation email-capture moment → best-plan reveal), built natively so the interactivity loop is premium. Founder rationale (verbatim): "every time a voice action goes over to thinking or listening to Florence inside that there's an immediate UX update, that it feels interactive, feels natural. There's never a moment where the user is done speaking, and there's a system that's thinking or Florence is thinking, and the user doesn't know about it. We have a lot more control over the mobile app side of things... we need to lean into that to provide a really polished experience overall." This control is exactly what mobile web cannot deliver (background tab kills audio, browser permission prompts, no native audio session, no haptics, no lock-screen controls). The marketing-tier funnel (home → plans → filter → waitlist) is reframed as a deferred Phase 1.5 — built only if/when needed after Florence v1 stabilizes. - Demo posture for first ship, test data only. v1 launches with synthetic / made-up data per founder direction (ElevenLabs BAA is NOT a v1 ship blocker on that basis). Real-user mode (and the real-PHI / real-BAA gates) flip in a follow-up after first ship + UX validation. Auth is anonymous to start; email is captured mid-conversation at the same flow point as web
/florence(thependingEmailConfirmmoment inFlorenceNarrowingPanel), at which point the user enters our system. - Analytics is PostHog HIPAA Cloud. Decision-ask #7 answered: PostHog HIPAA Cloud replaces ADR 0009's OpenPanel + GlitchTip path. One workspace covers analytics + error tracking + session replay + feature flags + surveys + LLM observability. Setup is hours not days. ADR 0010 (superseding ADR 0009) and the web-side migration are follow-up PRs after this one merges. Mobile v1 ships against PostHog from day one of the build — the SDK + identity model is engineering hand-instrumentation, not auto-discovered, but the app is laid out (semantic component names,
accessibilityLabeldiscipline, canonicalidentify(emailLowercase)once captured) to be wizard-friendly for PostHog's setup agent to layer on top.
These three late-day decisions de-risk launch speed (demo posture removes BAA blocking, PostHog HIPAA's fast procurement removes analytics blocking) and tighten v1's emotional bet (Florence's premium interactivity loop, not the standard marketing funnel).
Decisions captured
These are the load-bearing decisions, in priority order. Each links to where in this doc the supporting detail lives.
- Stack: React Native via Expo (managed workflow). Native Expo Modules only where premium polish demands native code (iOS document camera + Android ML Kit Document Scanner). See Premium UX stack.
- Native experience, no webview, ever (founder direction). The app is native end to end. Reuse between web and mobile is logic-level only via
packages/shared. No Capacitor, no webview-wrapped web flows, including for enrollment when it is built. - Release order: mobile v1 = native Florence flow (parity with web
/florenceend-to-end, built natively so the interactivity loop is premium); Phase 1.5 marketing-tier funnel (home → plans → filter → waitlist) is optional after Florence v1 stabilizes; native enrollment + member features third, after web member-enrollment ships; agent companion app last. See Phased delivery. (Pivoted 2026-05-26 late from "v1 = marketing-tier funnel.") - v1 is anonymous + demo posture with test data. No real PHI yet — first ship uses synthetic data so the BAA gates (ElevenLabs, etc.) are NOT v1 ship blockers. Email is captured mid-Florence-conversation at the same flow point web
/florenceuses (thependingEmailConfirmmoment inFlorenceNarrowingPanel); from there the user is in our system. Real-user mode + real-PHI gates flip in a follow-up release post-v1. - Repo shape: monorepo via npm workspaces. SHIPPED 2026-05-17 (ENG-349 M0):
apps/web,apps/mobile(placeholder slot),packages/sharedlive on main. Turborepo or Nx only if build time hurts. See Monorepo layout. - API evolution: versioned
/api/v1/*routes + Bearer token auth + zod schemas land alongside the web member-enrollment build (the first PHI surface and the prerequisite for native mobile enrollment), not as a generic Phase 5 add-on. v1 mobile ships against the existing public routes as-is. See Near-term next steps section 3. - Math reuse:
useCalculator,calculateAptc,findSlcspPremium,calculateFpl,deriveCsrTierFromFpl,extractCostShare,mongoDocToCmsPlanall move topackages/shared. Mobile cannot reimplement; silent CMS divergence is an EDE audit finding. See Reuse contract. - Voice: on-device ASR/TTS on native (iOS
SFSpeechRecognizer+AVSpeechSynthesizer, Android equivalents) perflorence-ai/voice.md:134-143. Server-side fallback uses ElevenLabs Conversational AI (the same surface the production web Florence uses, ENG-356) - vendor pivot from the planned Deepgram + Cartesia path, picked to move fast + because ElevenLabs has an agents mode the WOW experience already builds on. Arrives with the Florence-on-mobile phase, not v1. - Auth: native enrollment (later release) uses Bearer tokens (15-min access, rotated refresh) stored in iOS Keychain / Android Keystore via
expo-secure-store. Web portal continues with HttpOnly cookies. Auth middleware accepts both. v1 mobile uses no auth (anonymous demo, same posture as web/florence). - Analytics + observability: PostHog HIPAA Cloud. One workspace covers analytics + error tracking + session replay + feature flags + surveys + LLM observability. Supersedes ADR 0009 (OpenPanel + GlitchTip self-hosted); ADR 0010 to be drafted as a follow-up PR after this one merges. Mobile v1 ships against PostHog from day one — semantic component naming +
accessibilityLabeldiscipline + canonicalidentify(emailLowercase)make the app wizard-friendly for PostHog's setup agent, but the business events are hand-instrumented (not auto-discovered). See Analytics for v1.
Architecture
Monorepo layout (target end state)
ask-florence/ # repo root
|-- apps/
| |-- web/ # current Next.js 16 codebase moves here
| | |-- src/ # existing src/ tree
| | |-- public/
| | |-- next.config.ts
| | `-- package.json # depends on @askflorence/shared
| `-- mobile/ # new Expo app
| |-- app/ # Expo Router
| |-- components/
| |-- lib/
| |-- modules/ # Expo Modules (native shims)
| `-- package.json # depends on @askflorence/shared
|-- packages/
| `-- shared/ # single source of truth
| |-- src/
| | |-- schemas/ # zod schemas for API contracts
| | |-- types/ # Plan, PlanDisplay, CostShare, etc.
| | |-- eligibility/ # csr.ts, aptc.ts, fpl.ts (moved)
| | |-- calculator/ # useCalculator hook + pipeline
| | `-- plans/ # plan transforms (mongoDocToCmsPlan, etc.)
| `-- package.json # name: @askflorence/shared
|-- infra/ # existing AWS/Terraform tree, untouched
|-- scripts/ # existing audit harness, untouched
|-- docs/
|-- package.json # root with workspaces config
`-- tsconfig.base.jsonThe migration from current single-package layout to this monorepo is Phase 0 (see Phased delivery). Recommended timing has moved earlier since April 30 (see Near-term next steps).
Shared package contract
@askflorence/shared exports framework-agnostic code only. No Next.js imports, no React Native imports. The calculator hook is the one nuance: it imports React, which is shared between Next.js and RN, so it is safe. It cannot import next/* or react-native/*.
What moves into packages/shared:
src/lib/types.tstopackages/shared/src/types/src/lib/csr.tstopackages/shared/src/eligibility/csr.tssrc/lib/utils.ts(the math:calculateAptc,findSlcspPremium,extractCostShare,normalizePlan) topackages/shared/src/eligibility/src/lib/owned-plans.ts(themongoDocToCmsPlantransform) topackages/shared/src/plans/src/lib/fetch-plans.tstopackages/shared/src/calculator/src/lib/hooks/use-calculator.tstopackages/shared/src/calculator/- New: zod schemas matching every
/api/v1/*request + response shape
What stays in apps/web: Next.js routes, server components, server actions, API route handlers, MongoDB client setup, SES integration (Resend retired per v0.33.0), anything Node-only.
What lives in apps/mobile: Expo app shell, screens, native modules, RN-specific UI components, TanStack Query setup, secure storage, biometric and push wrappers.
The audit harness in scripts/audit/ continues to import from packages/shared after the move (its calls to fetchPlansForHousehold etc. resolve through the new path). ZERO DIFFS on scripts/audit/calculator-baseline-diff.ts is the gate for the monorepo refactor: same gate that has protected every calculator change since v0.22.0.
API surface: v1 mobile is public, Bearer auth comes with enrollment
Mobile v1 needs no new API work. The marketing-tier flow (home, plans showcase, filtering, waitlist) consumes the endpoints that already exist and are already public:
POST /api/plans- plan lookup + pricingPOST /api/eligibility- CMS proxy (federal) or internal calc (NY)POST /api/counties- ZIP to countyPOST /api/waitlist- waitlist capture (already has the consent sub-document; same posture the web waitlist uses)
No cookies, no tokens, no PHI. v1 talks to these as-is.
Bearer auth + versioned routes are a later-release prerequisite. When native enrollment is built (after web member-enrollment ships), it needs token auth because cookies do not transfer cleanly to native:
- Versioned routes:
/api/v1/*. Web continues to work via cookies; mobile uses Bearer tokens against the same routes. - Auth middleware accepts EITHER cookie OR Bearer token.
- Token issuance: short-lived access token (15 minutes) + refresh token (rotated on each use) for mobile. Stored in iOS Keychain or Android Keystore via
expo-secure-store. - Every request payload and response is validated against a zod schema in
packages/shared/src/schemas/. Same schema validates client-side and server-side.
This work lands alongside the web member-enrollment build - that is the first PHI surface and the first real consumer of token auth, and native mobile enrollment depends on it. The Near-term next steps section 3 discusses the sequencing.
Mobile app structure (Expo)
apps/mobile/app/
|-- (auth)/ # unauthenticated stack
| |-- welcome.tsx
| |-- sign-in.tsx # magic link
| `-- verify.tsx
|-- (tabs)/ # post-auth tab navigator
| |-- _layout.tsx
| |-- home.tsx # Florence AI assistant
| |-- plans.tsx # member's plan(s)
| |-- coverage.tsx # doctor + Rx coverage check
| `-- account.tsx # profile, member card, docs
|-- florence/ # Florence AI flow stack
| |-- chat.tsx
| |-- voice.tsx # voice mode
| `-- result.tsx
|-- enrollment/
| |-- start.tsx # ZIP/age/income (calculator hook)
| |-- verify.tsx # ID capture + verification
| |-- plan-select.tsx
| `-- confirm.tsx
`-- _layout.tsx # root with auth guard, deep-link configState management: TanStack Query for server state, Zustand for the small amount of local UI state (drafts, optimistic updates). No Redux.
Navigation: Expo Router (file-based, mirrors Next.js App Router idioms; same mental model the team already has).
Premium UX stack (the "feels native" defense)
| Concern | Choice | Why |
|---|---|---|
| Animations | Reanimated 3 + Gesture Handler | UI-thread animations, 60 fps guarantee, declarative API. Industry standard. |
| Custom visual effects | React Native Skia | The Florence lamp glow, scroll-driven shimmer, paper texture port cleanly. Closes about 90% of the native-feel gap. |
| Camera | react-native-vision-camera | mrousavy's library, modern standard, frame processors for ML inference on-device. |
| Document/card capture | Expo Module wrapping VNDocumentCameraViewController (iOS) + ML Kit Document Scanner (Android) | Apple's document scanner is unbeatable polish. About 50 lines of Swift, equivalent on Android. The one place premium demands native. |
| OCR for insurance/ID cards | ML Kit Text Recognition via VisionCamera frame processor | On-device, free, fast. PHI never leaves device for OCR. |
| Charts (plan comparison) | Victory Native or react-native-svg-charts | Composable, themeable. |
| Lists (plans, coverage) | FlashList (Shopify) | About 10x perf vs FlatList, drop-in replacement. |
| Forms | react-hook-form + zod resolver | Same schemas as web. |
| Haptics | expo-haptics | Premium feel on key interactions (price reveal, plan select). |
Florence AI on mobile
Not in v1. Florence on mobile arrives in the Florence-on-mobile phase (after native enrollment), tied to the Florence rollout on web. v1 (marketing-tier) does not include Florence. Documented here so the architecture is settled when it does land.
Florence on mobile reuses the same production surface the web Florence runs on: ElevenLabs Conversational AI over WebRTC, gated server-side by /api/florence/agent-session (provisions the agent + returns a short-lived conversation token), guarded by ENG-361 preflight quota. Mobile is a thin native client over that surface - native UI, no webview.
- Voice (primary): WebRTC to ElevenLabs Conversational AI via the same
/api/florence/agent-sessionroute the web uses (implementation). The native client calls the route, receives{ agentId, conversationToken, connectionType, voiceId }, and connects over WebRTC.ELEVENLABS_API_KEYnever leaves the server. Same ENG-361 hard-fail-at-95% quota guardrail applies. - On-device voice path (privacy-preferred): iOS
SFSpeechRecognizerfor ASR +AVSpeechSynthesizerfor TTS, audio never leaves the device, perflorence-ai/voice.md:134-143. Marketable as "your voice stays on your phone." This is a parallel path from the ElevenLabs WebRTC surface — used for quick local lookups; the WebRTC agent surface is used for the full conversational experience. - Text streaming (companion): if a text-only mode is ever exposed, SSE from a future
/api/v1/florence/message(planned inbriefs/member-portal-plan.md:154). React NativeEventSourcepolyfill or native WebSocket. Not in v1. - Conversation history: server-side only (PHI). Mobile fetches the most-recent N messages on app open. Mobile never persists full conversation history locally.
- Image input (snap a card, ask "what does this mean?"): camera capture, upload to S3 with KMS-encrypted bucket, multimodal call via the runtime, response.
The tool surface (florence-ai/tool-surface) is unchanged on mobile. Same tools, same schemas. The post-ENG-390 bulk check_drugs + check_providers tools (architectural fix for parallel-pickers) apply natively. ui_* tools on mobile drive native UI affordances (focus a field, open a plan, highlight a card) instead of the web's panel.
Compliance posture (mobile-specific)
v1 (marketing-tier) is low-compliance by design. The home/plans/filter/waitlist flow handles no PHI. The only personal data it captures is the waitlist signup, which already has a consent sub-document on the existing /api/waitlist route and the same posture the web waitlist runs under today. No CSFLE, no magic-link, no PHI screens, no screen-capture protection, no jailbreak detection needed for v1. App Privacy disclosure for v1 is a short "we collect an email for a waitlist" label.
The table below applies to the later native-enrollment + member-portal phases, where the app handles SSN, immigration documents, DOB, financial data, and authenticated member sessions. It maps CLAUDE.md's compliance-first architecture and the portal subdomain's CSFLE posture per security-compliance/sensitive-data-handling-member-portal.md to RN/Expo:
| Requirement | Implementation |
|---|---|
| TLS 1.2+ minimum | Default in modern iOS and Android. Cert pinning via react-native-ssl-pinning is reserved for the agent app v1; member app may skip pinning (high false-positive risk on cert rotation). |
| Secrets at rest | expo-secure-store (Keychain on iOS, EncryptedSharedPreferences on Android). Never AsyncStorage for tokens. |
| PHI cache | Avoid entirely. Member app refetches on open. If absolutely needed: SQLite via expo-sqlite with SQLCipher (paid Expo Module). Per sensitive-data-handling-member-portal.md, fields like SSN/immigration/DOB are CSFLE-encrypted at rest on the server; mobile NEVER receives plaintext for these. |
| Auth | Magic link (member app: just magic link; agent app later: magic link + TOTP via expo-local-authentication for biometric unlock of the in-app TOTP secret). |
| Session timeout | 15-minute idle reauth, 8-hour absolute reauth. Track via AppState + foreground timer. |
| Push payloads | Generic only ("New message from Florence"). Body fetched in-app post-auth. APNs mutable-content allows a Notification Service Extension to fetch + decrypt body for richer notifications; defer that to v2. |
| Screen-capture protection | expo-screen-capture preventScreenCaptureAsync() on screens with PHI. Auto-blur on app background. |
| Audit log | Every PHI-touching API call audited server-side via existing agent_audit_log (post-Phase 5). Mobile sends a client-context header with platform + app version + session ID for the audit row. Per ADR 0002, the audit log is append-only. |
| Third-party SDK BAAs | Sentry HIPAA tier (BAA needed; tracked under #57); PostHog mobile is out of scope since #75 has retired PostHog in favor of Umami self-hosted; Expo Application Services (no BAA, no PHI processed by Expo; documented as "code only, not user data" per the Update against April 30 baseline section above). |
| Jailbreak/root detection | jail-monkey library on app open, log to audit if detected, soft-warn user. Don't block (false positives). |
| App Privacy disclosures | Both stores require accurate data-usage labels. Privacy and terms (#55, closed 2026-05-12) live at /privacy and /terms v2026.04, ready for App Privacy disclosure. |
Validation: where this could shoot us in the foot (and why it doesn't)
Honest assessment of failure modes, not a confirmation pass.
Feature velocity over time
- Calculator math changes propagate free across surfaces (both consume
@askflorence/shared/calculator/use-calculator). v0.22.0 proved this on the web side (home + /landing both share the hook with ZERO DIFFS on the 12-scenario baseline). - API contract changes are type-checked on both surfaces via shared zod schemas + generated types.
- New screens are per-surface and that is correct, not duplication. Mobile UX patterns differ from web; forced shape-sharing would harm both.
- Risk: features quietly assume cookie auth or RSC patterns and break for mobile clients.
- Mitigation: API v1 + Bearer auth from Phase 5 forces all routes to be transport-agnostic from day one.
Flow updates
- Logic-only changes (e.g., new APTC bracket): edit shared, both surfaces pick it up.
- UX-only changes (e.g., bottom sheet on mobile): per-surface, no shared package change needed.
- Risk: a flow that is fundamentally different between web + mobile gets jammed into a shared abstraction.
- Mitigation: shared package contains primitives (math, types, schemas, low-level state machines), not flows. Flows live per-surface.
Scaling
| Axis | Concern | Reality + mitigation |
|---|---|---|
| Repo | Monorepo bloat | npm workspaces handles 3 to 10 packages fine. Add Turborepo when build time hurts. Far away. |
| Team | Onboarding | Monorepo is easier to onboard a second engineer: one repo, one CI, one tracker. |
| Users | RN can't scale | Bluesky runs 25M+, Discord runs hundreds of millions. Not a constraint. |
| Native modules | Bottleneck on premium features | Expo Modules + config plugins cover about 95%. Custom plugins about 1 day each. |
The real risks worth calling out
- Native ID-verification SDKs (Persona, Stripe Identity, Plaid). Need config-plugin integration with Expo. Most majors have community plugins; if not, about 1 day to write one. Pre-vendor-selection check: confirm Expo config-plugin support before signing a vendor.
- iOS-Android premium-feel parity. iOS document scanner exceeds Android. Mitigation: iOS-first launch via TestFlight, Android one sprint later, accept about 5 to 10% feel delta on Android (industry-standard tradeoff).
- App Store rejection risk. Health insurance + AI invites scrutiny under guidelines 1.4 (Safety), 5.1 (Privacy), 5.2 (IP: insurance carrier logos, plan brochures). Mitigation: /privacy + /terms live, App Privacy disclosure correct, Florence AI framed as decision support not medical advice. First submission expects 1 to 2 rejection rounds; budget accordingly.
- EAS Update OTA limits. Apple TOS allows OTA for bug fixes + minor changes, NOT for new features that bypass review. Mitigation: discipline. New features ship as binary releases. Bug fixes via OTA. Industry norm.
- Push notification PHI leakage. Easy to accidentally put PHI in a push body. Mitigation: lint rule + code review checklist. Bodies only ever say "New notification" or "New plan update"; real content is fetched in-app post-auth.
- Model deprecation on the Florence runtime. Anthropic rotates Claude model IDs. Mitigation: pin model IDs server-side in
FlorenceRuntime, version Florence AI prompts inpackages/shared, canary new versions before promoting. Same control plane the web side uses. - AI medical-advice positioning. Apple's stance on AI medical apps tightened in 2024-25. Florence AI must position as "decision support," not "medical advice." Mitigation: explicit disclaimer flow on first launch + persistent footer (Hims/Hers + K Health pattern).
What would actually shoot us in the foot (paths rejected)
- Native iOS Swift + Android Kotlin (kills a one-person team).
- PWA wrapper (App Store hostile, member-conversion-killing in US).
- Skipping API v1 + Bearer auth in Phase 5 (forces a mobile-blocking rewrite later).
- Skipping the monorepo refactor and reimplementing eligibility math in mobile (silent CMS-divergence = EDE audit finding).
- Starting mobile before the in-flight Phase 5 work stabilizes (guaranteed rework on a moving target).
Verdict: the architecture does not shoot us in the foot. Risks are normal mobile risks with well-trodden mitigations. Highest-compound, lowest-rework path for a one-person team building a regulated consumer health app.
Tooling stack
The full inventory of IDEs, services, dependencies, and accounts needed to build + ship this. Treat as a checklist when work begins.
IDEs + editors
- VS Code (primary): Expo + RN + TypeScript extensions, ESLint, Prettier.
- Xcode: required for iOS native module work, iOS Simulator, App Store Connect submissions, signing cert management.
- Android Studio: Android emulator, Play Store submissions, Gradle work if/when Expo Modules need Kotlin.
- Claude Code as primary force multiplier (existing).
Local dev environment
- Node 20+ (existing).
- npm workspaces (no extra tooling at start).
- Expo CLI:
npx expo. - EAS CLI:
npm i -g eas-cli. - Watchman (macOS, recommended for RN file watching).
- iOS Simulator (built into Xcode).
- Android Emulator (Android Studio AVD).
- Physical device testing via Expo Go (early dev), then custom dev client (once native modules added), then TestFlight + Play Internal (pre-release).
Build + CI/CD
- GitHub Actions (existing): extend with mobile workflows.
- Expo EAS Build: cloud iOS + Android builds, signing, provisioning profile management.
- Expo EAS Update: OTA JS update channel.
- Expo EAS Submit: automated App Store + Play Store submission.
Mobile dependencies (top-level)
Navigation + state:
- expo, expo-router (file-based routing matching Next.js mental model)
- @tanstack/react-query (server state)
- zustand (small UI state)
Forms + validation:
- react-hook-form
- @hookform/resolvers (zod resolver)
- zod (schemas from
@askflorence/shared)
Animation + UI:
- react-native-reanimated (UI-thread animations)
- react-native-gesture-handler
- @shopify/react-native-skia (custom visual effects, Florence lamp glow)
- @shopify/flash-list (high-perf lists)
- moti (optional, declarative animations on top of Reanimated)
Camera + media:
- react-native-vision-camera (mrousavy, modern standard)
- expo-image (high-perf image rendering)
- expo-av (audio capture for Florence voice)
Native + secure:
- expo-secure-store (Keychain or Keystore)
- expo-local-authentication (biometric)
- expo-screen-capture (PHI screen protection)
- expo-haptics (premium tap feedback)
- expo-notifications (push)
- jail-monkey (jailbreak or root detection)
Networking + observability:
- @sentry/react-native (HIPAA tier; verify under #57)
- Umami self-hosted (replaces PostHog per #75; mobile sends events to the same first-party endpoint the web uses)
Custom Expo Modules (we author):
modules/document-camera: wrapsVNDocumentCameraViewController(iOS) + ML Kit Document Scanner (Android) for premium card capture.
Backend services (extending existing AWS stack)
Existing (in use):
- AWS ECS Fargate (apex + portal subdomain production)
- AWS CloudFront + WAFv2 + ALB
- AWS Secrets Manager
- AWS SES (transactional email; Resend retired v0.33.0)
- MongoDB Atlas (HIPAA M10 tier)
- Umami self-hosted (analytics; PostHog retired per #75)
To add for mobile (or extend if already in use):
- AWS Transcribe Streaming: server-side voice input fallback (HIPAA-eligible); on-device ASR is primary per voice.md.
- AWS Polly: server-side voice output fallback (HIPAA-eligible); on-device TTS is primary per voice.md.
- AWS S3 + KMS-encrypted bucket: image uploads (insurance cards, ID, SEP qualifying-event docs).
- Apple Push Notification service (APNs).
- Firebase Cloud Messaging (FCM) for Android push (Expo Push handles both initially; can switch to direct APNs and FCM later for richer payloads).
- The Florence runtime is already on Claude Agent SDK (Haiku 4.5, Sonnet 4.6, Opus 4.7) per
florence-ai/index.md:57; no additional inference vendor needed.
Distribution + accounts
- Apple Developer Program: $99 per year. Business entity needs D-U-N-S number (free from D&B). Apply early; can take days.
- Google Play Console: $25 one-time. Business entity verification required.
- App Store Connect: TestFlight, App Privacy disclosures, submission metadata.
- Google Play Console: Internal, Closed, Open testing tracks. Data Safety form.
Documentation + collaboration
- GitHub Issues + Projects (existing): board #3 "AskFlorence Launch".
- Linear (existing): ENG-* issues track active work.
- Notion (existing): for design system docs, runbooks.
- Figma (when design system arrives; see Design system input).
Cost estimate (mobile-specific monthly run rate)
- Apple Developer + Play Console: about $10 per month amortized.
- EAS Build: free tier covers about 30 builds per month; paid plan starts at $19 per month for more parallelism.
- EAS Update: free tier 1,000 MAU, then $99 per month for Pro.
- Sentry HIPAA: about $80 to $200 per month (volume dependent).
- Umami self-hosted: marginal (already running on AWS).
- AWS Transcribe + Polly + S3: usage-based; expect about $50 to $300 per month at member-app v1 scale (most voice is on-device per voice.md).
- Claude (via the Florence runtime): usage-based; same line item as web Florence (no incremental cost from mobile beyond more queries).
Order of magnitude: mobile v1 (marketing-tier) adds near-zero run-rate - it calls existing public endpoints; the only new line item is EAS Build/Update (free tier covers v1) + ~$40/mo CI. The about $250 to $700 per month figure above applies to the later native-enrollment + Florence + voice phases (Sentry HIPAA, Transcribe/Polly fallback, S3). Lower than the April 30 estimate because PostHog Cloud is retired and Bedrock is replaced by the Florence runtime (same line item as web).
Design system input
The founder is building a brand design system. v1 (marketing-tier: home, plans showcase, filtering, waitlist) is a UI-heavy release, so a mobile token port is a Phase 1 input, not deferred to a later phase. The home and /landing register documented in docs/design-system.md and surfaced at /design-system is the starting point: v1 can begin against a tokens.ts port of the existing web design system (colors, type, spacing) while the premium-polish primitives below are a hard input for the later native-enrollment + Florence phases.
What the design system must specify (token port for v1; full set before native enrollment):
- Type scale for mobile (different from web; needs explicit mobile sizes + line heights for iOS Human Interface Guidelines compliance + Material 3 expressive on Android).
- Color tokens (already in
globals.cssfor web; port to RN-friendly format like atokens.tsexporting both web CSS vars + RN style values). - Spacing scale (mobile-specific: 4 / 8 / 12 / 16 / 24 / 32 typically).
- Component primitives (Button, Input, Card, Banner, Pill, etc.) with mobile variants; touch targets 44pt iOS / 48dp Android minimum.
- Florence AI surfaces (chat bubble, voice mode visual, lamp glow treatment in Skia).
- Iconography: SF Symbols-style on iOS, Material Symbols on Android, or custom icon system that ports from web.
- Motion language: duration scale (fast 150 ms, med 240 ms, slow 360 ms), easing curves, reduced-motion equivalents.
Premium mobile best practices the design system must support:
- iOS HIG: SF Pro Display/Text feel (or carefully chosen alternative font), bottom-sheet patterns, native nav with large titles, swipe-back gesture, dynamic type support, dark mode.
- Android Material 3 Expressive: state layers on touchables, ripple effects, navigation rail/bar patterns, dynamic color (optional).
- Cross-platform consistency in brand voice (Florence aesthetic) while letting interaction patterns be platform-native.
- Haptic vocabulary: rigid for confirmations, soft for state changes, warning for errors. Standardize via
HapticTokensenum. - Skeleton loaders + optimistic UI patterns for every async surface.
- Empty states + error states specified for every screen, not afterthought.
The token port is a Phase 1 input; the full premium primitive set is a hard input before native enrollment. See What unblocks this plan.
Phased delivery
Estimates intentionally omitted (per the user-memory rule on clock-based build estimates). Acceptance criteria, not timelines.
Phase 0: monorepo refactor (prerequisite) - SHIPPED 2026-05-17 via ENG-349 M0
Section kept as historical record of intent + acceptance criteria the actual refactor was held to. The monorepo layout below is now reality on
main. The next phase to start is Phase 1.
Goal: convert the single-package repo into the workspace layout above with zero behavior change.
Acceptance:
apps/web/builds + deploys identically to currentsrc/(run prod build, diff bundle).- All audit harness scripts in
scripts/audit/pass unchanged. scripts/audit/calculator-baseline-diff.tsreturns ZERO DIFFS.packages/sharedis consumed byapps/weband exports the calculator math + types + schemas.- AWS ECR Docker build still works (Dockerfile updated to copy from monorepo root, build
apps/web). - GitHub Actions deploy-prod and deploy-staging workflows still pass (paths updated).
- All existing routes return identical responses.
This is a focused 1 to 2 day refactor. Not a multi-week project. See Near-term next steps section 1 for why Taha should approve this NOW, not after Phase 5 stabilizes.
Phase 1: mobile v1 - native Florence flow (the first mobile release)
This is the first release. It is the native version of /florence — same conversational arc the production web Florence delivers, rebuilt natively so the interactivity loop is premium in a way mobile web cannot match. Demo posture for first ship (test data, no real PHI).
The bet: native control over the audio session + presence + haptics is the differentiator. On mobile web today, every voice action that hands off to "thinking" or "listening to Florence" can have a perceptible gap where the user doesn't know what's happening. Native eliminates that — every state change (user-spoke → system-receiving → Florence-thinking → tool-running → Florence-speaking) has an immediate, deliberate UX signal. That is what v1 is for.
Scope (mirroring /florence end-to-end):
- Launcher / entry — single tap into the dedicated experience. Native equivalent of
FlorenceLauncher. Self-gates on/api/florence/flag. - Conversation session — ElevenLabs WebRTC connection via
/api/florence/agent-session(already shipped, route). SameELEVENLABS_API_KEY-server-side, short-lived conversation token to client, ENG-361 quota preflight. - First message + arc — same persona and flow as web: greeting → ZIP → household composition → income →
find_planstool call → narrowing viacheck_drugs+check_providers(bulk per ENG-390) → email-capture moment → best-plan reveal. - The interactivity loop (the v1 differentiator) — explicit native states with immediate UX feedback: idle, listening-to-user, user-finished-speaking-system-receiving, Florence-thinking, Florence-tool-calling, Florence-speaking. Every transition has an audio cue and/or haptic and/or visual state change. No silent gaps. Equivalent of web's
FlorencePresence+FlorenceVoiceBars+useFlorenceSoundbut with native audio session, native haptics (expo-haptics), Reanimated 3 + Skia for the cinematic. - Native plan card render — Florence's
find_planstool result renders as a native card (equivalent of web'sFlorenceBestPlanCard), CSR-aware (Silver-only when applicable per the FB7/FB15 logic intools.ts:pickDisplayTop). - Narrowing panel + email capture — the mid-conversation
pendingEmailConfirmUX fromFlorenceNarrowingPanelported to native (Florence speaks the proposed email, user taps confirm / edits / cancels). Email-capture is the v1 conversion event — same flow point as web. - Background tolerance — native audio session keeps the conversation alive when the app backgrounds briefly; explicit "session paused" state when it backgrounds longer.
- Lock-screen / Now-Playing controls — pause/resume the Florence session from the lock screen. (Stretch goal for v1; defer to v1.1 if it slips.)
Reuse from packages/shared: the find_plans tool's underlying fetchPlansForHousehold pipeline + types + CSR logic — provably byte-identical to web + CMS via the audit harness.
Explicitly NOT in v1:
- Real-user / real-PHI mode (demo posture only; first ship is test data).
- Marketing-tier funnel screens — home, /plans-equivalent, filter UI, waitlist form. These are reframed as Phase 1.5, optional after Florence v1 stabilizes.
- Enrollment, auth/account, member portal — Phase 3+ as before.
- Push notifications, document capture, member card.
Acceptance:
- App passes App Store Review + Google Play Review. (Health-data declaration TBD — even demo mode discusses health insurance; App Privacy disclosure language drafted with a "we collect an email for follow-up; voice conversation is processed by our AI provider for the duration of the session" framing.)
- End-to-end Florence conversation works on physical iPhone + Android: tap launcher → full conversation → email captured → best plan rendered.
- Calculator baseline diff returns ZERO DIFFS (the
find_planstool path uses the same shared pipeline). - Interactivity loop QA pass: no perceptible-gap moments where the system is doing something and the user doesn't know.
- Mobile CI/CD + regression posture live (see Near-term next steps section 2) — ships WITH v1.
- PostHog HIPAA workspace provisioned + SDK wired (see Analytics for v1 below).
- TestFlight + Play Internal Track distribution working.
Analytics for v1 (PostHog HIPAA, lean event model)
Provider: PostHog HIPAA Cloud. Single workspace covers product analytics + error tracking + session replay + feature flags + LLM observability. BAA signed pre-launch (founder said hours not days; not gating engineering work — engineering starts now, BAA + workspace keys land into Secrets Manager in parallel).
Lean event model — only what the founder explicitly asked to know (per the conversation):
| User-behavior question | Event(s) | Notes |
|---|---|---|
| Did the user open the app? | app_open | Mobile-only baseline. Fires per cold start + foreground return. |
| Did the user tap into Florence? | florence_session_started | Tap from launcher. Mobile-native counterpart to web /florence page-view. |
| Where in the conversation did they reach? | florence_tool_called with { tool_name } property (find_plans, check_drugs, check_providers, etc.) | Funnel-step proxy. Florence's tool calls are the natural waypoints — they fire after the agent has gathered enough context to act. |
| Did Florence get to a plan recommendation? | florence_best_plan_shown with { plan_id, metal_tier, premium_subsidized, has_csr } | The "Florence delivered value" moment. |
| Did the user leave their email (the conversion)? | florence_email_proposed (Florence asked), florence_email_captured (user confirmed), florence_email_skipped (user dismissed without confirming) | The v1 KPI funnel. florence_email_captured is the conversion event analog of web's waitlist_signup_completed. |
| Did the conversation finish or get abandoned? | florence_session_completed (natural end) vs florence_session_abandoned (backgrounded indefinitely, force-quit, hung up) | Drop-off analysis. |
That is the entire v1 PostHog event model. Six event types + a session-level identifier (distinct_id from anonymous device ID until florence_email_captured fires identify(emailLowercase)).
User-input properties (the demographic + product-decision layer)
The event table above tells us what happened. The properties below tell us who it happened to — geographic distribution, household composition, income mix, age bands, drug/provider interest. Florence gathers all of this as natural conversation steps; we register each as a PostHog super-property the moment Florence has it, so every subsequent event in the session inherits the context automatically (PostHog calls these "person properties"). That lets any funnel be sliced by any of these dimensions without re-instrumenting.
These answer the founder's explicit ask: "which ZIP code they're going to, what household size are people entering, what income levels are they entering, things like that... to help us optimize and make decisions for what kind of users we need to cater to and what kind of needs we need to address either with more information or more guided flows."
Super-properties, set once per session as Florence gathers them:
| Property | Source | Why it answers the founder's question |
|---|---|---|
zip_code (string) | Florence parses ZIP | Geographic distribution: which states / counties / urban-rural mix |
state (string) | derived from ZIP via existing pipeline | State-funnel segmentation, SBE vs federal-state mix |
county_fips (string) | derived from ZIP | Sub-state distribution (matters for the CA wave-2 launch and future SBE rollouts) |
household_size (int) | Florence parses household | 1-person vs 2-person vs family flow rates — drives the "single-person guided flow vs family flow" investment decision |
is_married (bool) | Florence parses household | Affects subsidy bands; useful crosscut |
people_ages (array of int) | Florence parses ages | Age distribution; raw shape for cohort charts |
oldest_age, youngest_age (int) | derived | Easier-to-segment scalars |
annual_income (number) | Florence parses income | Income distribution |
fpl_band (enum) | derived via existing FPL helper (below_138 / 138_to_250 / 250_to_400 / above_400) | The eligibility-decision band — answers "how many users are Medicaid-eligible vs CSR-eligible vs full-price" |
eligibility_class (enum) | derived (medicaid / csr / aptc_only / unsubsidized) | The "what kind of help do they need" signal — directly drives whether we invest in Medicaid-handoff UX, CSR-explainer flows, or unsubsidized-buyer flows |
has_csr (bool) | derived | Quick filter for CSR-eligible cohort behavior |
Event-level properties on specific events:
| Event | Added properties |
|---|---|
florence_tool_called (tool_name: "check_drugs") | items_count (int) + items (array of { query, matched_term, covered_plan_count }). Capturing the query and the matched canonical drug name lets us answer "which drugs are users asking about" and later derive product-decision categories (cholesterol drugs, levothyroxine/Synthroid, GLP-1s, etc.) via a server-side enrichment job. Same data lives in florence_conversations server-side anyway; PostHog capture mirrors what's already on the platform when the user submits. |
florence_tool_called (tool_name: "check_providers") | items_count (int) + items (array of { query, matched_npi, covered_plan_count }). Same shape, doctor-side — lets us see which specialties / how many doctors users actually search for. |
florence_best_plan_shown | Already carries plan_id, metal_tier, premium_subsidized, has_csr. Add total_plans_available (int) so we can see "did we surface 1 plan or 49 plans" — proxies for how restricted the user's market is. |
florence_email_captured | time_to_capture_seconds (int, session-start → capture) — speed-to-conversion signal; lets us see which cohorts convert fast vs slow. |
Product questions this property model answers directly:
- "Which ZIPs / states / counties are we attracting?" → super-property
zip_code/state/county_fipscohorts - "What household sizes are people entering?" →
household_sizedistribution - "What income levels?" →
annual_incomehistogram +fpl_bandcohorts - "What ages?" →
people_agesarray +oldest_age/youngest_agescalars - "What kind of users do we need to cater to?" → cross-segment
florence_email_capturedrate by any of the above (conversion-by-cohort) - "Where do users drop off?" → cross-segment
florence_session_abandonedbyfpl_band,household_size, etc. — exposes which user types fail the experience - "What conditions / treatments are users worried about?" → drug-name distribution from
check_drugsitems, plus future categorization job (cholesterol, thyroid, GLP-1, etc.) - "How restricted are the markets we're surfacing?" →
total_plans_availabledistribution
Not in v1 PostHog scope (additive if/when needed)
- Per-presence-state events (idle / listening / thinking / speaking) — too noisy; covered by session replay if we ever need it.
- Florence conversation transcripts — transcripts persist server-side in the
florence_conversationsMongoDB collection (the legal-record-of-conversation perflorence-ai/index.md). PostHog gets structured event properties only. Best practice on three counts: (a) keeps event payloads slim so PostHog usage stays cheap, (b) keeps free-text user content out of the analytics workspace where it's less governable, (c) avoids duplicating data that's already canonically stored in Mongo. - Performance / Core Web Vitals analogs — not requested.
- Error tracking custom config — PostHog autocapture covers it.
- Feature flag exposure events — auto-emitted by the SDK when flags are read.
Wizard-friendly clues
So PostHog's setup agent can layer atop the hand-instrumentation: semantic component names (FlorenceLauncherButton, not Pressable), consistent accessibilityLabel strings, canonical identify(emailLowercase) once captured (anonymous distinct_id before that, never multiple ID schemes), verb-object event naming (matches the schema above), and super-property registration via posthog.register() the moment each demographic field becomes known.
Phase 1.5: marketing-tier funnel screens (deferred, optional)
Was Phase 1 in the morning draft (home + calculator + plans showcase + filter + waitlist). Reframed as deferred after the Florence-first pivot. Status: optional, TBD after Florence v1 stabilizes. Florence may absorb enough of the conversion job that the traditional funnel screens are unnecessary on mobile; or they may be useful as a non-voice fallback path for users who don't want to talk. Defer the decision until v1 has user data. Spec preserved in the doc history if you want to revive it (see PR #353 history).
Phase 2: API v1 + Bearer auth (lands with web member-enrollment)
Not a standalone mobile phase - it is part of the web member-enrollment build, because that is the first PHI surface and the first consumer of token auth. Native mobile enrollment (Phase 3) depends on it. Deliverables:
/api/v1/*versioned routes for the enrollment + member surface.- Bearer token issuance + refresh endpoints under
/api/v1/auth/*. - Auth middleware accepts cookie OR Bearer.
- zod schemas in
packages/shared/src/schemas/for every v1 endpoint.
Acceptance:
- All v1 routes have zod schemas + 100% schema coverage in tests.
- Bearer auth + refresh flow works end-to-end (Postman or curl).
- Token rotation on refresh confirmed (old refresh token rejected after use).
- Audit log captures auth events with platform context.
See Near-term next steps section 3 for the sequencing call.
Phase 3: native enrollment + member features (after web member-enrollment ships)
The second mobile release. Fully native enrollment - no webview (founder direction). Built against the Phase 2 /api/v1/* + Bearer surface, reusing packages/shared types + zod schemas + the useCalculator pipeline. Scope:
- Native enrollment flow: the 9-section intake as native screens (native form controls, native keyboard, offline-draft handling). Same API contract as the web portal; different presentation layer.
- Magic-link + device-bound auth: member email + Bearer tokens in Keychain/Keystore.
- Member card + account: post-enrollment card display, saved plans, document vault for SEP qualifying-event uploads.
- Image capture: insurance card + ID scan via
VNDocumentCameraViewController(iOS) / ML Kit Document Scanner (Android) Expo Modules. - Push: OEP nudges, premium changes, doctor in/out-of-network changes (generic payloads only; body fetched in-app post-auth).
- Coverage check: doctor + Rx per plan (when #17 / #18 backends are done).
This is the first PHI-handling mobile phase; the full Compliance posture table applies here.
Acceptance:
- App passes App Store + Play Review with correct health-data declarations.
- Enrollment produces the same
member_applicationsdoc shape as the web portal for identical inputs. - All compliance posture items implemented + verified (Keychain tokens, screen-capture protection, generic push, idle/absolute timeout, jailbreak logging).
- Calculator baseline diff still ZERO DIFFS.
Phase 4: Florence AI on mobile + on-device voice
Florence native client over FlorenceRuntime (text streaming via SSE, on-device ASR/TTS per florence-ai/voice.md:134-143, AWS Transcribe/Polly fallback). Tied to the Florence rollout on web. Reduced-motion + accessibility audit. Microphone permission flow.
Phase 5: agent companion app (later)
Once mobile v1 + native enrollment are stable AND the agent web portal has real users, ship the agent companion app. Reuses packages/shared + the same Expo project structure (or a sibling apps/agent if scope diverges enough). Features: lead inbox, member detail, push for new submissions, TOTP enrollment, biometric unlock.
This phase is far enough out that detailed planning waits.
Near-term next steps
The three items Taha asked for explicitly: work that should happen NOW not LATER. Each section spells out the call, the cost-benefit, and the recommended sequencing.
1. Monorepo reorganization: recommended NOW, not later - FULFILLED
Update 2026-05-26: this recommendation landed. ENG-349 M0 shipped 2026-05-17.
apps/web/,apps/mobile/(placeholder per README), andpackages/shared/are live on main. The reasoning + acceptance criteria below stay as historical record. The follow-up call: start Phase 1 (mobile v1) work directly against the existingapps/mobile/slot.Disambiguation: "web platform Phase 5" below refers to the AskFlorence roadmap's Phase 5 (full agent + member portal, per CLAUDE.md), NOT this doc's mobile phases. The web platform's Phase 5 is the in-flight web work; this doc's Phase 0 was the monorepo refactor (now done).
The April 30 plan deferred the monorepo refactor to "after AWS migration closes AND the web platform's Phase 5 ships." AWS migration has closed (#47 closed 2026-05-11). The web platform's Phase 5 is actively shipping (agent portal, member portal Phase A). The question now is: should the monorepo refactor happen NOW (before that web work compounds more), or stay deferred until it stabilizes?
Recommended: NOW. Reasoning:
| Factor | If we refactor now | If we defer |
|---|---|---|
| Web work PR shape | Every in-flight web PR lands in the right structure; no rework when mobile begins | A bigger atomic refactor later that touches every web change since |
| Code-share with mobile | New web routes can be authored against packages/shared types from day one; mobile inherits them | Web routes use src/lib/* paths; mobile work later has to retroactively move them |
| Test impact | Audit harness (scripts/audit/calculator-baseline-diff.ts) and Playwright suite (tests/smoke/*) update once | Same one-time update, just later |
| Churn | One 1 to 2 day refactor PR, then web work continues | Zero churn until the refactor lands, but the refactor will be bigger |
| Risk | Low: refactor is mechanical, gated on ZERO DIFFS baseline | Slightly higher: more files to touch atomically |
The deferral argument was "don't churn an active branch." That argument weakens every week the in-flight web work continues without the refactor. Doing it now is one focused PR that touches every consumer in a clean sweep. Doing it later costs the same effort plus reconciliation against whatever has changed in the meantime.
Files affected (full list lives in Critical files below): src/ becomes apps/web/src/; the math/types layer moves to packages/shared/src/; Dockerfile + GitHub Actions workflows + audit harness imports update.
Gate: scripts/audit/calculator-baseline-diff.ts returns ZERO DIFFS on the 12-scenario baseline. Same gate that's protected every calculator change since v0.22.0.
Recommended call: file a Linear issue today: "Phase 0: monorepo refactor." Owner: Taha. Land before further in-flight web work compounds. One-PR shape, no version bump (docs of the move included).
2. Mobile CI/CD + automated regression: ships WITH v1
Founder direction is explicit: mobile gets a CI/CD + automated-regression posture analogous to the web platform's L1-L4 model, adjusted for mobile, to preserve the shipping pace the platform has run at. This is a Phase 1 deliverable - it ships WITH mobile v1, not after. A high-pace native app without a regression net silently regresses the calculator parity and the waitlist capture, which are the entire point of v1.
The four-layer model, mapped to mobile:
| Layer | Web today | Mobile v1 (marketing-tier) |
|---|---|---|
L1 local preflight (npm run preflight) | typecheck + 3 audits + Next.js build + docs build; --full adds Playwright | Add apps/mobile typecheck + ESLint to L1 default. Add a tiny Maestro smoke (launch -> calculator -> plans render -> waitlist submit) to --full against a local/simulator build. |
| L2 PR CI | 7 required checks + Playwright on staging | Add mobile-typecheck (required). Add mobile-e2e (Maestro flows against staging API; soak first, required-check promotion after 1 week clean). EAS Build is NOT a PR-time blocker (free-tier minutes limited). |
| L3 post-build smoke | 11 HTTP checks against deployed origin | After each EAS Build, run the Maestro suite against that build before promoting to TestFlight / Play Internal. v1 is marketing-tier so the smoke is calculator-parity + plans-render + waitlist-write - no PHI cleanup complexity. |
| L4 nightly drift | Playwright + smoke against staging + prod, cron 0 9 * * * UTC | Separate cron 0 10 * * * UTC: EAS Build health, latest TestFlight build Maestro run against staging API, store "latest review status" probe. Auto-file P1 issue on failure, same pattern as web. Catches the same out-of-band drift class (CMS API contract change breaking the calculator) for the mobile surface. |
The calculator-parity regression is the load-bearing one. v1's calculator uses the same useCalculator from packages/shared as web. The existing scripts/audit/calculator-baseline-diff.ts (12-scenario ZERO DIFFS gate) already protects the shared logic. Mobile's L2 adds a thin Maestro flow that drives the native calculator UI through 2-3 of those scenarios and asserts the rendered subsidized price matches the baseline - catching a native-rendering bug that the shared-logic audit can't see.
Test infra choice: Maestro versus Detox.
| Tool | Pros | Cons | Verdict |
|---|---|---|---|
| Maestro (mobile.dev) | YAML-driven flows, fast iteration, low boilerplate, RN-friendly, single binary, cloud option for parallelization | Less RN-aware than Detox; harder to assert on RN internals | Recommended for v1. Faster iteration suits a one-person team. Industry shift toward Maestro for RN/Expo in 2025-26. Mirrors the "Playwright over Cypress" reasoning in ADR 0008 - pick the faster-iterating modern tool. |
| Detox (wix/detox) | RN-native, grey-box (asserts on RN internals), mature | Slow on iOS sim parallelism, historically flaky, more boilerplate | Reserve for if Maestro stops scaling. Fallback for assertions Maestro's black-box approach can't reach. |
EAS Build + OTA cadence (the "keep shipping pace" mechanism):
- EAS Build free tier: ~30 builds/month. Trigger only on release tags + the nightly cron, never per-PR.
- EAS Update (OTA) is the pace mechanism: JS-only bug fixes + copy + non-native changes ship over-the-air in hours, same-day, no App Store review. New native features go through binary release + review (Apple TOS line; respected with discipline). This is the mobile analog of the web's "push to main, deploy when batched" cadence - and it is why the regression net has to exist before v1 ships, since OTA can push a regression to all users in minutes.
- Green-check gating: a mobile release tag requires the latest EAS Build green AND the latest nightly Maestro suite green. Same philosophy as web's required checks.
In-VPC ECS smoke runner (ENG-320) does not extend to mobile. Mobile tests run on macOS GHA runners against the staging API; v1 exercises only public endpoints, so no VPC isolation or Atlas allowlist work.
Cost summary (ships with v1): L1 $0, L2 ~$30/mo, L3 $0 (folded into release workflow), L4 ~$10/mo. Aggregate ~$40/mo on top of the existing ~$41/mo web testing. Well under the free GHA minute cap.
Recommended call: the mobile CI/CD + Maestro harness is part of the Phase 1 (mobile v1) issue, not a follow-up. The PR template + workflow scaffolding land in the same initiative so v1 never ships a build the regression net hasn't seen.
3. API v1 + Bearer auth: sequencing
Mobile v1 does not need this at all. The marketing-tier flow consumes existing public endpoints (/api/plans, /api/eligibility, /api/counties, /api/waitlist) with no auth and no PHI. The April 30 framing ("do during Phase 5") and the earlier draft's "parallel branch in Phase 5" are both superseded by the founder's v1 re-scope: there is no auth surface in v1 to sequence around.
Where it actually belongs: with the web member-enrollment build. That is the first PHI surface, the first consumer of token auth, and the prerequisite for native mobile enrollment (Phase 3). The sequence is:
- Mobile v1 ships against existing public
/api/*routes, as-is. No API work. - Web member-enrollment is built (separate in-flight track). As part of that build, the enrollment + member endpoints land under
/api/v1/*with cookie-OR-Bearer middleware and zod schemas inpackages/shared/src/schemas/. The web portal uses cookies; the schema + Bearer path is built at the same time because the middleware is already open and the marginal cost is low (the April 30 plan budgeted 5 to 10%). - Native mobile enrollment (Phase 3) consumes that
/api/v1/*+ Bearer surface. No retrofit, because step 2 built it Bearer-ready.
Why not defer Bearer to Phase 3 entirely? Because retrofitting Bearer across an already-shipped cookie-only v1 enrollment surface is the exact "mobile-blocking rewrite" the April 30 plan called out. Building cookie-OR-Bearer once, while the enrollment middleware is being written anyway, is materially cheaper than bolting Bearer on later.
Recommended call: do NOT do API v1 work as part of the mobile-app initiative or before mobile v1. Fold the /api/v1/* + cookie-OR-Bearer + zod-schema requirement into the web member-enrollment issue's acceptance criteria, so native mobile enrollment inherits a ready surface. The Phase 0 monorepo refactor is the only API-adjacent prerequisite that should happen now (it puts the schemas in packages/shared so both surfaces share them).
Critical files
To create (Phase 0)
package.json(root, workspace config): replaces currentpackage.json.tsconfig.base.json: shared compiler options.apps/web/package.json: depends on@askflorence/shared.packages/shared/package.json: name@askflorence/shared, typemodule.packages/shared/tsconfig.json.packages/shared/src/index.ts: barrel exports.
To move (Phase 0)
Existing path to new path:
src/toapps/web/src/public/toapps/web/public/next.config.tstoapps/web/next.config.tssrc/lib/types.tstopackages/shared/src/types/index.tssrc/lib/csr.tstopackages/shared/src/eligibility/csr.tssrc/lib/utils.ts(math portions) topackages/shared/src/eligibility/src/lib/owned-plans.ts(transforms) topackages/shared/src/plans/src/lib/fetch-plans.tstopackages/shared/src/calculator/src/lib/hooks/use-calculator.tstopackages/shared/src/calculator/
To touch (Phase 0)
Dockerfile(ininfra/):COPYpaths updated, buildapps/web..github/workflows/deploy-staging.yml: build path..github/workflows/deploy-prod.yml: build path..github/workflows/playwright.yml: working-directory + build paths..github/workflows/nightly-drift-check.yml: same.scripts/audit/*.{js,ts}: import paths updated to@askflorence/shared.scripts/preflight.ts: workspace-aware build commands.- All existing imports inside
apps/web/src/that referenced@/lib/{csr,utils,owned-plans,fetch-plans,types}updated to@askflorence/shared.
To create (Phase 1, mobile v1 - marketing-tier)
apps/mobile/(Expo project: home, calculator, plans showcase, filtering, waitlist).apps/mobile/lib/api-client.ts: typed client over fetch against the existing public/api/*endpoints (no auth in v1).apps/mobile/lib/analytics.ts: Umami event sender (same first-party endpoint web uses).tokens.tsinpackages/shared(orapps/mobile): RN-friendly port of the web design tokens.- Mobile CI/CD scaffolding: Maestro flows,
.github/workflows/mobile-*.yml, EAS Build config (see Near-term next steps section 2).
To create (Phase 2, with web member-enrollment - not a mobile-only phase)
apps/web/src/app/api/v1/route tree for the enrollment + member surface.apps/web/src/middleware.ts(or extend existing): Bearer + cookie auth.apps/web/src/lib/auth/tokens.ts: issuance + refresh + rotation.packages/shared/src/schemas/: zod schemas for every v1 route.
To create (Phase 3, native enrollment + member features)
apps/mobile/modules/document-camera/: Expo Module wrappingVNDocumentCameraViewController+ ML Kit.apps/mobile/lib/secure-client.ts: Bearer + refresh handling overexpo-secure-store.apps/mobile/lib/storage.ts:expo-secure-storewrappers.apps/mobile/lib/audit.ts: client-context header injector.
Reuse contract (no reimplementation)
The non-negotiable: do not reimplement the eligibility math in the mobile app. All of the following are reused from packages/shared:
calculateFpl,calculateMedicaidThreshold,deriveCsrTierFromFplcalculateAptc,findSlcspPremiumextractCostShare,normalizePlanmongoDocToCmsPlanplan transformer (mobile won't call MongoDB directly, but it consumes the same Plan/PlanDisplay shape over the wire)useCalculatorhook (the entire pipeline + state machine)STATE_BASED_MARKETPLACESconstant
Why this matters: the audit harness proves these are byte-identical to CMS. A second implementation in Dart, Swift, or Kotlin would diverge silently, which is an EDE audit finding waiting to happen. The packages/shared constraint is the architectural enforcement.
Verification
Phase 0 (monorepo refactor)
npm installfrom root succeeds, sets up workspaces.npm run -w apps/web buildproduces an identical bundle to pre-refactor (compare bundle sizes + route manifest).npm run -w apps/web devstarts Next.js dev server, all routes render.npx tsc --noEmit -p apps/webclean.npx tsc --noEmit -p packages/sharedclean.npx tsx scripts/audit/calculator-baseline-diff.tsreturns ZERO DIFFS.- Tier 1 to 5 audit harness all pass.
- AWS staging deploy succeeds.
- AWS prod deploy succeeds.
Phase 1 (mobile v1 - marketing-tier)
npx expo prebuildsucceeds for both iOS and Android.- EAS Build green for iOS and Android.
- Maestro suite green: launch to calculator to plans showcase to filter to waitlist submit.
- Calculator baseline diff: native calculator output matches web + CMS for all 12 scenarios (shared
useCalculator; native-render check via Maestro on 2-3 scenarios). - Waitlist submission writes the same
agent_waitlist_submissionsrow shape + consent sub-document as the web waitlist. - App Store Review + Google Play Review approval (marketing app; no health-data declaration, no account).
- TestFlight + Play Internal distribution working for ops/founder team.
- Mobile CI/CD (L1-L4 mobile mapping) live and gating the release tag.
Phase 2 (API v1 + Bearer, with web member-enrollment)
- zod schemas have 100% coverage of v1 routes (lint check).
curlBearer flow: sign-in to access token to call/api/v1/*to refresh to access token rotated.- Audit log shows every auth event with correct context.
- Existing web cookie auth still works (regression check).
Phase 3 (native enrollment + member features)
- Native enrollment produces the same
member_applicationsdoc shape as the web portal for identical inputs. - EAS Build green for iOS and Android.
- Manual smoke on physical iPhone + Android: magic link to native enrollment sections to submit to member card.
- Calculator baseline diff still ZERO DIFFS.
- App Store + Play Review approval with correct health-data declarations.
- Compliance audit:
- Tokens in Keychain/Keystore (verified via OS tools).
- PHI screens have FLAG_SECURE /
preventScreenCaptureactive. - Push payloads contain no PHI (capture + inspect).
- 15-min idle / 8-hr absolute timeout fires correctly.
- Jailbreak detection logs to audit on rooted test device.
- TestFlight + Play Internal distribution working.
What this plan deliberately does not do
- Does not commit to Phase 5 (agent companion app) details: too far out.
- Does not specify Florence AI prompt engineering or model selection (Florence AI directory has its own architecture).
- Does not address App Store / Play Store privacy disclosures in detail: blocks on /privacy + /terms (#55) being live (now done).
- Does not address marketing site changes for app launch (App Store badges, download CTAs, /download page): a separate sprint.
- Does not address localization (Spanish is on the eventual roadmap; mobile v1 is English-only).
- Does not select an ID-verification vendor (Persona, Stripe Identity, Plaid, Veriff): deferred to the native-enrollment phase; Expo config-plugin compatibility is the gating concern.
What unblocks this plan
The founder re-scope collapsed the v1 blocker list dramatically. v1 is Florence-first in demo posture with test data — no real PHI yet — so it does NOT wait on the member portal, agent portal, or the production-BAA gates.
Hard blockers before Phase 0 (monorepo refactor): all cleared.
- AWS migration (#47) closed. Done 2026-05-11.
- Operational hygiene: PostHog/Telegram WAF exclusions (CLAUDE.md PENDING WORK items 0a + 0b). Done; tracking under #75 PostHog retirement (web is now on OpenPanel + GlitchTip per ADR 0009; ADR 0010 reverses this to PostHog HIPAA Cloud as a follow-up) and the WAF rules on #47.
Phase 0 itself: DONE 2026-05-17 via ENG-349 M0.
Hard blockers before Phase 1 (mobile v1, Florence-first demo posture):
Phase 0 (monorepo refactor) merged- DONE. Shared pipeline + types live inpackages/shared.- Design system token port to
tokens.ts(the existing web register is the source; see Design system input). Plus Florence-specific motion + voice-bar + presence-state token spec (the visual vocabulary the interactivity loop uses). Status: not started. - Apple Developer Program + Google Play Console enrollment (lead time days to weeks for Apple business verification; calendar-bound, not work-bound — start now). Status: not started.
- PostHog HIPAA workspace + BAA + project keys in Secrets Manager. Founder confirmed setup is hours not days; not engineering-gating but the SDK keys need to land before v1 ship. Status: procurement track to start now in parallel.
- ElevenLabs Conversational AI proof on React Native. Web Florence uses ElevenLabs's WebRTC SDK; need to confirm the RN SDK or fallback path works for native (most likely
@elevenlabs/react-nativeor a thin WebRTC bridge). Status: needs spike.
Notably NOT blockers for v1 (this is the de-risk): member portal, Phase 5 agent portal, API v1 + Bearer auth, vendor BAAs for real-user processing (#57), consent versioning (#58), CSFLE, magic-link auth, ElevenLabs BAA (v1 uses test data only — BAA gates the real-user flip, not first ship). v1 ships parallel to all of that.
Hard blockers before Phase 3 (native enrollment + member features):
- Web member-enrollment shipped + stable (the API v1 + Bearer surface lands with it; see Near-term next steps section 3). Status: in flight.
MongoDB Atlas HIPAA BAA- DONE 2026-05-15 via ENG-270.- Vendor BAAs (#57) confirmed for: Sentry HIPAA, ElevenLabs Conversational AI (the production voice vendor per ENG-356; supersedes the AWS Transcribe + Polly entry that was here when this doc was written for the planned Deepgram+Cartesia path), APNs, FCM. Expo documented as "no BAA, no PHI processed" per the Update against April 30 baseline section. Status: ElevenLabs BAA needs explicit confirm; others in flight.
- Consent versioning (#58) shipped. Status: in flight.
- Full brand design system with premium mobile primitives (not just the token port) - see Design system input. Status: in flight.
Soft preferences before Phase 3:
- Drug formulary (#17) and provider directory (#18) backends done so the coverage check lands real data, not a placeholder. (Also affects whether v1 can show a real coverage teaser vs a "coming soon.")
Decision asks for Taha
These are the binary or short-answer decisions this doc surfaces. Each has a recommended call; the doc holds until Taha says go or redirect.
Confirm v1 scope = home + calculator + plans showcase + filtering + waitlist?Superseded 2026-05-26 late. New v1 scope is Florence-native flow (parity with web/florenceend-to-end, built natively for the premium interactivity loop, demo posture with test data). Marketing-funnel screens reframed as Phase 1.5, optional.Approve Phase 0 monorepo refactor now?Done. ENG-349 M0 shipped 2026-05-17.- Fold API v1 + Bearer auth into the web member-enrollment issue (not the mobile initiative, not before v1)? Recommended: yes (see Near-term next steps section 3). v1 needs no auth (anonymous demo); building Bearer-ready during enrollment avoids the later retrofit. Status: still open - needs explicit confirm before the web member-enrollment build incorporates the schema.
- Maestro for mobile E2E (over Detox), shipping WITH v1? Recommended: Maestro (see Near-term next steps section 2). Status: still open.
Release order: mobile v1 (marketing-tier) → native enrollment → agent companion app?Superseded 2026-05-26 late. New order: Florence-native v1 → Phase 1.5 marketing-funnel (optional) → Phase 3 native enrollment → Phase 5 agent companion app.- Will native iOS appetite shift if eng headcount grows to 3 or more? Taha call. If yes, this doc gets revisited; the RN/Expo recommendation today assumes the one-to-small-team constraint holds. Status: still open / no-op until headcount changes.
Analytics vendor for mobile v1?Answered 2026-05-26: PostHog HIPAA Cloud across both surfaces (mobile day-one; web migrates from OpenPanel + GlitchTip in a follow-up). Supersedes ADR 0009. ADR 0010 to be drafted in a separate PR. CLAUDE.md analytics references to be updated in a separate PR.- NEW 2026-05-26 late: ElevenLabs Conversational AI on React Native — confirmed feasible? Web Florence is on the ElevenLabs JS SDK over WebRTC. Need to confirm
@elevenlabs/react-native(or the right native WebRTC bridge) is production-ready for the v1 stack. Status: needs spike before Phase 1 starts. Not a strategy decision — a feasibility check.
References
- April 30 working plan (local, historical):
~/.claude/plans/okay-this-is-a-starry-diffie.md portal-entry-handoff.md: the cross-surface entry contract this doc inheritsflorence-ai/index.md: Florence runtime modelflorence-ai/voice.md: voice architecture incl. on-device for nativeflorence-ai/roadmap.md: "Native app timeline" open question answered herebriefs/member-portal-plan.md: portal subdomain split + handoff design intentsecurity-compliance/sensitive-data-handling-member-portal.md: PHI boundary inherited by mobileadr/0006-mongo-user-simplification.md: 4-user Mongo modeladr/0008-e2e-testing-strategy.md: four-layer testing model the mobile section above plugs intodevelopment/testing-strategy.md: operational view of the four layersdesign-system.md: web brand register; mobile primitives extend from here