A first-run that doesn't dump you on an empty screen
New signups were landing straight on an empty home with nothing to tap. Added a 3-step onboarding — gated on the signup path so it never bounces existing users.
The gap
The audit was blunt about it: sign up, and you're dropped on an empty Today screen with no idea what Mimi even is or what to do first. The code confirmed it — signup.tsx went straight from "account created" to router.replace('/') with nothing in between. For an app whose value only appears once you've imported a video and clipped a line, that empty first screen is where a new user quietly leaves.
The fix
A small first-run flow (onboarding.tsx): three steps — a one-line "what this is," the loop drawn as four rows (import → clip → listen/order/write/speak → review), and a daily-minutes goal (5 / 15 / 30) — ending in a single strong CTA, Import my first video, that hands straight off to the real import screen. A "Skip" and a "Just look around" exit are always there so it never feels like a wall.
The one design decision worth noting
How to trigger onboarding matters more than the screens. The tempting approach is a global launch gate: "on app open, if not onboarded, show onboarding." That's wrong here — every existing account (which never completed a flow that didn't exist yet) would get bounced into onboarding on their next launch. So instead it's gated on the signup path: only a fresh signup routes to /onboarding; a returning login goes straight to Today. The done-flag in SecureStore is just there to keep it from re-showing within a session.
Status
tsc clean. Not yet verified on a device — by design my own existing account skips it, so seeing it live needs a fresh signup. Build 7 was already in TestFlight review before this landed, so onboarding rides along in a later build.