Production Launch Checklist
Civix — iOS App Store Launch Checklist
App: Civix (US Citizenship Test Prep) · ASC App ID 6759538146 · Apple Team 22GBQ3S2M7
Web API: https://api.civixapp.us (Vercel, root dir apps/web)
Entitlement: premium · Product: civix_pro ($9.99/mo, single tier)
Generated by the launch-readiness review (2026-06-24). The code is GO; the only true blockers are env/config items below.
1. Mobile env / EAS
-
EXPO_PUBLIC_REVENUECAT_IOS_API_KEYis set in EAS for the production profile (EAS Secret / project env). Without it RevenueCat returns no offerings → paywall shows "products still loading" and purchases never complete. -
EXPO_PUBLIC_REVENUECAT_ANDROID_API_KEYpresent too (only relevant if shipping Play in parallel). - Confirm the public build uses the
productionEAS profile — NOTtestflight. Thetestflightprofile setsEXPO_PUBLIC_ENABLE_TEST_BYPASS=trueandEXPO_PUBLIC_TESTFLIGHT_INTERVIEW_BYPASS=true; these MUST stay out of the App Store build so test bypasses are disabled. -
productionprofile setsEXPO_PUBLIC_API_URL=https://api.civixapp.us(verify ineas.json, no trailing slash). -
appVersionSource: "remote"+autoIncrement: trueonproduction— confirm no build-number collision with the last TestFlight build. - Native-change builds are NOT OTA. Any change to native modules (
civics-activity,civics-haptics,civics-realtime-audio,civics-activity-grid,civics-hero-card,civics-answer-choice,civics-officer-stage), the widget extension, pods, orInfo.plistrequires a fresheas build+ App Store submit. - Do NOT run
npm run prebuild— it is disabled and would wipe the committedios/native code.
2. Vercel production env (then redeploy)
-
REVENUECAT_SECRET_API_KEY— CRITICAL.lib/interview/entitlement.tsFAILS CLOSED when this is unset: real paying subscribers get 403'd out of the realtime interview in production. Set the RevenueCat secret (server) key, not the public SDK key. -
OPENAI_API_KEY— required for all AI interview / evaluate / realtime-token routes. -
OPENAI_REALTIME_PROMPT_ID— set to the published prompt id. Diff the dashboard-saved prompt to confirm it still carries: (a) the language-routing rules and (b) the no-real-personal-data / synthetic-identity rule. (Code injects these guardrails on connect too, but the saved prompt should still carry them.) -
UPSTASH_REDIS_URL+UPSTASH_REDIS_TOKEN— without these the rate limiter / OpenAI cost caps fall back to in-memory and become per-serverless-instance, so caps multiply across instances and stop protecting spend. - TestFlight bypass vars —
TESTFLIGHT_BYPASS_ENABLED+TESTFLIGHT_BYPASS_SECRET(mobile:EXPO_PUBLIC_TESTFLIGHT_BYPASS_TOKEN). TestFlight-only; must NOT gate the public production entitlement path. Consider disabling for the public launch window. - Redeploy the Vercel production deployment after changing any env var. Then
cd apps/web && npm run verify-apiandnpm run test:interview-apiagainst prod.
3. RevenueCat dashboard
-
premiumentitlement exists and is attached to productcivix_pro. -
civix_prois added to the App Store app and imported into RevenueCat. - The current Offering is marked default/current and contains the
civix_propackage (paywall reads price live fromofferings.current). - iOS App Store shared secret / App Store Connect API key configured in RevenueCat so server-side entitlement verification resolves real receipts.
- Sandbox purchase resolves the
premiumentitlement end-to-end.
4. App Store Connect
- Paid Applications Agreement signed and active (IAP will not work otherwise).
-
civix_proauto-renewable subscription is Ready to Submit / Approved, attached to this app version, single price tier only. - Subscription group, localized display name, and description set.
- App Privacy labels complete: Microphone (AI voice interview), plus analytics/crash (Sentry) data types; declare data use/linking honestly.
- Screenshots for all required device sizes; no placeholder content.
- Age rating questionnaire completed.
- App description / promo text includes the non-affiliation disclaimer ("not affiliated with USCIS or the U.S. Government").
- Privacy Policy URL (
https://civixapp.us/privacy) and Terms (https://civixapp.us/terms) live and reachable. - Export compliance answered.
5. Build & submit
-
eas build --platform ios --profile testflightfor internal QA round (bypass enabled), OR--profile productionfor the App Store candidate. -
Info.plist:CFBundleShortVersionStringuses$(MARKETING_VERSION)(avoid the ITMS-90473 hardcoded-version reject). -
UIRequiresFullScreen = truepresent (prevents the ITMS-90474 iPad multitasking reject). -
NSMicrophoneUsageDescriptionpresent and accurate (voice interview). - Podfile.lock committed and current (any new native module needs
pod installbefore pushing to EAS). - Submit the production-profile build only via
eas submit --platform ios --profile production. Never submit atestflight-profile build to the public track.
6. Pre-submit on-device SMOKE TESTS (most important)
Run on a real device, signed into a Sandbox/TestFlight account. Repeat the audio cases with AND without AirPods/wired headphones.
Realtime voice / audio routing
- Start an AI interview → officer audio plays through device speaker.
- When you begin speaking, audio does NOT switch routes, duck hard, or flap — no cut-out/glitch at the moment the mic engages.
- Connect AirPods mid-session: audio moves to AirPods cleanly and does not flap as you alternate speaking/listening.
- Wired headphones: stable route, no flapping when you start speaking.
- Remove headphones mid-session: falls back to speaker without crashing the session.
- Officer voice (
cedar) is intelligible; no echo/feedback loop on speaker.
Language routing
- Speak Dari/Pashto to the officer → officer redirects you to answer in English (does not conduct the civics exam in the other language).
- No real personal data is solicited/echoed (synthetic identity rule holds).
Entitlement / paywall gates
- Free account: premium feature is blocked — locked questions (64/128), adaptive flow-zone, native-language study, uncapped AI interviews are gated; tapping a locked feature routes to the paywall.
- Paid account (premium): realtime interview is allowed — no 403 (validates
REVENUECAT_SECRET_API_KEYon Vercel is live). - Free weekly AI interview is reachable for a free user.
- 1 free mock interview/day limit is enforced for free users (2nd same-day attempt blocked/paywalled, resets next day).
- Purchase flow completes in Sandbox →
premiumentitlement activates → gated features unlock immediately. - Restore Purchases works on a fresh install / re-login.
- Paywall close (X) dismisses cleanly; "Maybe Later" also dismisses. No trap.
- Auto-renew disclosure + Terms/Privacy links on the paywall open correctly.
UI correctness
- Quiz answer options do not overlap — check across questions with different line counts (1-line vs 3-line options).
- Practice answer options do not overlap — same check, including when advancing between questions of differing heights.
- Consent gate appears before the interview starts and must be accepted to proceed (both AI and mock realtime).
- PDF export of results generates and opens/shares correctly.
- Tab bar restores after the interview ends (all 5 tabs return).
- RTL languages (fa/ps) render correctly on cold start.
7. Post-launch monitoring (Sentry)
Watch breadcrumbs/errors for:
- Realtime: realtime-token 4xx/5xx, audio-engine init failures, session-start errors, route-change exceptions,
stuck_speaking_recovered, OpenAI rate-limit/cost-cap hits. - Entitlement 403s: spikes on the realtime path =
REVENUECAT_SECRET_API_KEYmisconfig or RevenueCat outage (subscribers failing closed). - Purchases: "No offerings available" / "unavailable" errors, purchase failures, restore failures.
- Gates: free-weekly-interview unreachable, daily-mock-limit logic errors, paywall navigation dead-ends.
- Vercel function logs: Upstash fallback warnings, OpenAI 429s, prompt-id resolution failures.
8. Rollback plan
- JS-only / config / copy / prompt-display fix →
eas update --channel production(OTA). Use for paywall copy, JS gate logic, string fixes, non-native bugfixes. - Native fix (any
ios/native module, widget, pod,Info.plist, new permission, SDK bump) → neweas build+ App Store submit. Cannot be OTA'd. - Server-side fix (entitlement/realtime/prompt/rate-limit) → change Vercel env or code, redeploy prod. For a bad realtime prompt, revert
OPENAI_REALTIME_PROMPT_IDto the previous published version. - Emergency revenue/abuse stop → flip the relevant server gate or rotate
OPENAI_API_KEY/ tighten Upstash caps; pull a broken OTA by publishing a known-goodeas update. - Keep the previous known-good build available in App Store Connect to expedite a phased-release halt if a native regression ships.