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_KEY is 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_KEY present too (only relevant if shipping Play in parallel).
  • Confirm the public build uses the production EAS profile — NOT testflight. The testflight profile sets EXPO_PUBLIC_ENABLE_TEST_BYPASS=true and EXPO_PUBLIC_TESTFLIGHT_INTERVIEW_BYPASS=true; these MUST stay out of the App Store build so test bypasses are disabled.
  • production profile sets EXPO_PUBLIC_API_URL=https://api.civixapp.us (verify in eas.json, no trailing slash).
  • appVersionSource: "remote" + autoIncrement: true on production — 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, or Info.plist requires a fresh eas build + App Store submit.
  • Do NOT run npm run prebuild — it is disabled and would wipe the committed ios/ native code.

2. Vercel production env (then redeploy)

  • REVENUECAT_SECRET_API_KEYCRITICAL. lib/interview/entitlement.ts FAILS 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-api and npm run test:interview-api against prod.

3. RevenueCat dashboard

  • premium entitlement exists and is attached to product civix_pro.
  • civix_pro is added to the App Store app and imported into RevenueCat.
  • The current Offering is marked default/current and contains the civix_pro package (paywall reads price live from offerings.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 premium entitlement end-to-end.

4. App Store Connect

  • Paid Applications Agreement signed and active (IAP will not work otherwise).
  • civix_pro auto-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 testflight for internal QA round (bypass enabled), OR --profile production for the App Store candidate.
  • Info.plist: CFBundleShortVersionString uses $(MARKETING_VERSION) (avoid the ITMS-90473 hardcoded-version reject).
  • UIRequiresFullScreen = true present (prevents the ITMS-90474 iPad multitasking reject).
  • NSMicrophoneUsageDescription present and accurate (voice interview).
  • Podfile.lock committed and current (any new native module needs pod install before pushing to EAS).
  • Submit the production-profile build only via eas submit --platform ios --profile production. Never submit a testflight-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_KEY on 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 → premium entitlement 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_KEY misconfig 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 fixeas 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) → new eas 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_ID to 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-good eas update.
  • Keep the previous known-good build available in App Store Connect to expedite a phased-release halt if a native regression ships.