Repository navigation
feat(app): onboarding that ends in a first conversation (A/B), paywall re-ask on app open (A/B), Home live pill - #21445
YinkaAdepoju wants to merge 81 commits into
Conversation
Free and Plus users see this month's minutes and an Upgrade button under Plan & Usage, so upgrading is one tap from Settings. Free users route through the Superwall settings_upgrade placement when the local pilot is on; everyone else gets the existing plans sheet. Extracts the standalone plans sheet wrapper so Settings and the Plus meter prompt share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tapping locked content now shows the locked_content paywall (Superwall placement when the local pilot is on, otherwise the plans sheet) over the current screen instead of pushing Plan & Usage first. Locked memories log Paywall Opened with source 'Locked Memory' instead of the mislabelled 'Action Item'. Adds a shared openUpgradePaywall helper used by Settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a free user reaches All set on their first device setup, register the Superwall device_setup_complete placement. Superwall only, with no plans-sheet fallback, so nothing changes when the local pilot is off. Skipped tutorials and replays from Settings are excluded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Finished, skipped and replayed tutorials all close through the same path, which registers the Superwall placement for anyone not subscribed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan & Usage takes the placement and source for the offer it opens on arrival (usage_upgrade by default; transcription_settings_usage from Transcription Settings, chat_apps from Chat Apps, Deep Link source for the unlimited link). Chat quota (chat_quota) and the Plus meter (plus_meter) go through the shared openUpgradePaywall helper, which replaces the two private plans-sheet wrappers. Existing flows are unchanged when the local Superwall pilot is off; each entry now logs Paywall Opened with its source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ask for Bluetooth as the app opens and search for the reader's Omi in the background after sign-in - Name opens with the keyboard up; no artwork after consent - Remove the "How did you find Omi" step - Permissions as switch cards; Continue unlocks once every card is answered (denied shows orange, notifications red with Tap to fix) - Language moves after permissions - New Connect your Omi step: reveal when found, search with instructions otherwise, and "I've ordered mine / Not ordered yet" - A device connected in onboarding skips the post-onboarding tutorial - Progress dots become a single line Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Shared plain step layout (title top, action pinned above keyboard); Name centres its field and keeps Continue working on the first tap - Permissions as separate cards with a custom toggle that carries the state colour (grey unanswered, accent on, orange/red with "!" denied) - Language uses the same layout - Connect Omi: clearer hierarchy, even text wrap, quiet links for "I've ordered mine / Not ordered yet"; rings follow the theme - Progress line level with the back button - Direction-aware slide and fade between steps (off with Reduce Motion) - Quiet device search only when Bluetooth is allowed and on, so no "Enable Bluetooth" alert appears over unrelated steps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Permissions cards and the language picker sit centred like Name - Language title "Choose your language" (one line) - No back button or swipe-back on Connect Omi (settings stay editable later); arrives with a cross-fade, then the device rises in with a light tap and the text fades up in sequence - Bluetooth check waits for the arrival, so no alert covers it - Shorter Bluetooth alert: "Turn on Bluetooth to connect your Omi." Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Onboarding Device Choice was only an event. Also set it as a person property, so people who say they ordered can be compared with Shopify orders later without asking them to link an order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Onboarding's Connect Omi and the in-app pairing page share the pairing provider. Leaving Connect Omi now ends the search and clears "nothing found", so the in-app page cannot open on a stale "No Omi Found". Adds tests for the step: title, deferred search, the two "no Omi yet" answers, a found device row, and the clean hand-off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Not ordered yet" on Connect Omi now opens omi.me in a clean in-app web page (the existing OmiWebPage), tagged _ref=onboarding_not_ordered and logged as Omi Shop Opened; closing it carries on with onboarding. openOmiShop is reusable for later hardware upsell entry points. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Two short lines: storage + AI notes, then "AI processing: Deepgram, OpenAI" in small text (Apple 5.1.2(i) still names who receives data) - Privacy Policy and Terms as small links - "Use a Different Account" moves above the main action so Agree & Continue sits at the same thumb height as every other step - The card rises from the bottom over the sign-in background The old long message stays in the ARB files for now; the provider list needs confirming with the Omi team. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… opens as a sheet with Close - Data & Privacy: button says Continue and sits where every Continue does; "By continuing, you agree to our Terms of Use and Privacy Policy." sits under it in the space the other steps leave free. "Use a Different Account" removed from this screen. - OnboardingCard can leave the bottom inset to its footer. - The Omi store opens as a full-screen sheet with Close (x) instead of Back; OmiWebPage gains an opt-in close mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sation, congrats - Device users: "Wear your Omi"; everyone: "Let's see Omi at work." (auto), two animated slides (notes, tasks), microphone ask for phone users only - "Tell Omi about your day": faded example, live words in a centred note card, Skip top right, one round check (grey until Omi hears something) with listening rings; records through the Omi when it is streaming, else the phone microphone - The dictation goes through the capture pipeline and becomes a real conversation (first on Home); "Writing your notes..." then the summary and tasks animate in; falls back to "Here's what you said" - Congrats with name and rating ask, then the plans sheet, then Home - Skipping shows a dismissible "Welcome to Omi" card on Home - No back or progress line during the activation - Debug-only OMI_ACTIVATION_DEMO=1 simulates speech for servers without speech-to-text - 29 new strings in 49 locales Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Drop the two explainer slides; "Let's try Omi." leads straight to the microphone ask (no device) or the dictation. - Order after the reveal: hooray, rating ask, paywall, Home. - Rating: title and five stars above where the App Store prompt opens; no sub-copy. - Hooray: title above the check; check and confetti follow. - Microphone and dictation titles sit lower, level with each other and clear of the system prompt; Skip is grey. - Back button is the bare icon; shorter consent copy. - Activation flow tests for the full path, skip and fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The dictation turns into a full note: the card stays in place while "Summarizing…" sweeps across the words, then the emoji, title and "Just now" land, a rule draws across, the summary writes itself and the transcript waits behind a toggle. - To-dos slide in one by one with a light tap each, then "Saved to your tasks". - Every titled activation screen (wear, microphone, dictation, summarizing, summary, to-dos, hooray, rating) shares one title size and height. - New copy: "Your day, summarized", "Omi caught your to-dos", "Summarizing…"; a fuller demo summary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dictation through a connected Omi waits up to 5s for it to stream. If
it does not, the reader is asked for the phone microphone first (with
a line saying why) instead of a cold system prompt mid-dictation, and
the recording is handed back to the Omi afterwards.
- Audio the Omi heard before the dictation (unboxing, the screens before)
is closed as its own conversation first, and the result shown is the
conversation whose transcript matches what the reader said.
- After 12s with no words, the dictation says it cannot hear the Omi (or
the reader) instead of listening in silence.
- Live words survive a segment reset mid-dictation.
- Wear tips only for Omi devices, with the device image and accurate
button copy ("press to ask, again to send"); "Just talk. Your Omi is
listening." under the dictation title.
- A firmware update found during onboarding is shown once Home opens,
not over the first-run screens.
- Connect Omi: sentence-case status ("Tap your Omi to connect.",
"Connected"), no duplicate "pairing successful", and the "Don't have
your Omi yet?" links hide while connecting.
- Debug-only OMI_ACTIVATION_DEMO=device to review this path on a
simulator.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: the redesign replaces the voice step, so wrapper.dart keeps the redesign's step navigation; main's voice-step back/resume helpers and their two wrapper tests are dropped with it. The provider and welcome screen keep both sides (main's pairing telemetry and welcome analytics). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts were main's Paywall Opened tracking against the pilot's Superwall openers. Both are kept: openUpgradePaywall now logs main's closed source names (locked_conversation, chat_quota_exceeded, ...) with the current plan, passes the source to the plans-sheet fallback, and skips the log when the opener already sent it. UsagePage keeps main's paywallSource and gains the upgradePlacement for Superwall. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ding The activation's paywall step registers the placement and waits for it to close; when the pilot is off or nothing is shown it falls back to the plans sheet. The pilot keeps its debug-only gate, so release builds are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some iOS builds never make the app inactive under the App Store pop-up (the simulator never does), so closing it left the rating screen waiting for Continue. The pop-up takes every touch while it is up, so the first tap on the screen after asking means it closed: go to the paywall. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The onboarding activation no longer records a voice profile, so "Teach Omi your voice" is the reader's prompt to do it. It now has a close button; closing it hides it for good and a toast points to Settings > Voice Profile. It still hides once a profile exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut, bounded paywall wait - Skip (or leaving) while the activation was still starting left the phone microphone recording into Home: start() now checks a cancelled flag after each wait and stops a microphone that came up after cancel(). - The activation step allowed the iOS edge swipe / Android back to pop the whole onboarding back to the welcome screen; it no longer can. - The Superwall onboarding paywall wait gives up after 5 minutes if the SDK never reports a close, so onboarding cannot hang on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The top bar (device, search, settings) is the same on Home and Tasks. Connect keeps its word on every page; the logo alone read as a spinner. - Each page has one bare action beside the Home / Tasks switcher: the calendar on Home, a "more" menu on Tasks (select, group, show completed, export). This replaces the second row of circles on Tasks and the separate "more" button beside the task search. - Switching pages fades and slides the incoming page in from its tab's side (off under Reduce Motion); both pages keep their state. - Empty Tasks says what will show up there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ask Omi is a round button at the left (tap to type, hold to talk); the main control is a light pill: "+ New Recording", "Stop" while the phone records (a spinner while it finishes), or the capturing pendant with what it is doing (tap for the transcript, hold to record with the phone; an Omi call opens the call). - The live transcript grows out of the pill and shrinks back into it; the iOS edge swipe still dismisses it. Reduce Motion fades instead. - Home's live card steps aside while the pendant is simply listening (the pill says it) and stays for every other state. - Fixes found in review: Stop on a reconnecting phone recording finishes it instead of starting another; a connected pendant that is not capturing keeps "+ New Recording" so the phone is one tap away. - Visual-audit frame and the performance test follow the new controls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @YinkaAdepoju — this is a substantial, well-structured draft. The fail-closed experiment handling in Dependency note: Leaving for human maintainer review — these are product decisions rather than code defects:
Nothing verified as broken, so no change requests from me — the open-questions section is honest and the work is cohesive as one unit. by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with |
…eriment (BasedHardware#21450) Brings in Nik's paywall A/B test (PaywallRouter, flag paywall-variant) and flag-gated web onboarding. Resolution: - openUpgradePaywall: Superwall pilot (when on) -> PaywallRouter arm -> plans sheet; logs Paywall Opened once; forceNative carries the web variant's fallback so it never loops. - Onboarding: the activation ending's paywall asks the router before the onboarding sheet; the experiment's end-of-onboarding placement is skipped when the ending already offered a plan, so nobody sees two paywalls in a row. Keeps the debug onboarding rerun and the web onboarding entry; the first-capture step telemetry stays removed (BasedHardware#21396 revert). - l10n: main's keys kept; the reverted first-capture strings dropped again; regenerated. - Flag registry re-rendered; onboarding-activation-flow notes name its PostHog flag/experiment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l experiment Debug builds no longer configure Superwall unless --dart-define=SUPERWALL_TEST_MODE=true, so every build exercises PaywallRouter by default. Adds widget tests for openUpgradePaywall: control -> plans sheet, a variant arm -> its page, forceNative -> sheet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wall-reask) Free readers keep opening the app but rarely meet a paywall, since every paywall waits for a limit. On launch or resume, a free reader whose re-ask is due gets the usual paywall (openUpgradePaywall -> PaywallRouter), queued in PromptQueue so it never covers a recording or another prompt. Due 2, 2, 3, 4, then every 7 calendar days after the last paywall from any source; the first Home visit only starts the clock. Both arms are exposed at the same due moments; control shows nothing. New Paywall Opened source app_open_reask. Pre-registration: app/lib/services/experiments/paywall_reask.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, control) and the app-open re-ask Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Simulated on Sep 1-11 device buyers who didn't subscribe: median 2 re-asks in their first 30 days (was 3), at most 5 (was 6); the first ask stays on day 2, when most buyers subscribe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p; rating moves on by itself The App Store rating pop-up gives no close callback and on device the app may never go inactive under it, so the rating screen stayed up after the pop-up closed. Once the pop-up has been asked for, the flow now continues to the paywall underneath it. Every onboarding step's primary button uses one shared bottom gap (kOnboardingButtonBottomGap). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tle; no 'still writing your notes' line Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Home fades in; Omi pull-to-refresh - Dictation: the script lights up word by word with the voice (tick per word, waits on silence); Omi's sentences confirm it, or the reader's own words ink in when they said something else. A live waveform shows Omi is listening. - Summarizing: the words stay in the same card and condense (filler folds away, key words draw together, the card shrinks); a three-line mark condenses in place of the sparkle. The dictation subtitle keeps its space so the card does not move. - Summary: rises out of a blur, then a light passes across once. - Leaving onboarding replaces the stack with a fade (omiRootRoute): a slide showed Home moving over a root already redrawn as Home (double Home). - Home and Tasks: iOS pull-to-refresh draws a ring, ticks when armed, then spins (OmiRefreshControl); Android keeps the Material indicator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…once, no flash of the old card While the microphone came up (initialising) the old Listening card showed at the top and the pill still read New Recording. The pill now turns live the moment it is tapped, and the card stays hidden for a starting phone recording as it already was for a running one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nside the card Real notes arrive as markdown (## headings, - bullets, **bold**) and were shown raw; a long summary also outgrew the card, clipping it and pushing it into the title. SummaryText shows headings as bold lines and items as bullets, and caps the summary at 190pt: longer notes scroll inside it behind a bottom fade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Words lit by the voice now stay grey until Omi actually hears them; then each turns full ink with a soft highlight behind it and a small pop, in turn, with a light tick per sentence. The reader's own words (when they said something else) flash in the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the row away The open view drops completed tasks and the completed view drops reopened ones, so rows used to vanish on tap. The row now shows done/undone at once, holds for a beat, folds closed, and only then is the task moved. Fold state is keyed per task so the next row never grows back in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the grey skeleton (blank circle, Processing pill, empty bar) with the saved row's layout: a glyph in the emoji's square whose three lines condense into one, Processing as the title, time and length, and the first words said with a light passing over them. Motion runs ~30 s then rests; reduced motion keeps it still. The timeout state keeps its copy and Try again, now an OmiButton. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…al, Continue at thumb height - Connect Omi: when the Omi connects, a ring draws around it, a check lands on the ring, one pulse goes out and the title reads Connected (device name below) before the step moves on (~1.8 s; reduced motion holds it still). Previously the tutorial opened the same instant. - Device tutorial intro: drop the Skip under Get Started; the close X is the way out. - Tutorial Continue/Finish: the shared OmiButton, ending kOnboardingButtonBottomGap above the safe area like every other onboarding step (was a 56pt pill 24pt from the bottom). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Variant arms saw a paywall at the end of first-run onboarding and were exposed there; control returned early and was only exposed later, if ever. The arms entered the experiment at different moments, which skews per-exposed conversion against the variants. Control now reads its payload too, and when `onboarding_paywall` is on it is exposed at that moment without being shown anything (PaywallRouter.exposeControl, same paid / hidden-UI gates as openVariant). The pre-registration and flag registry note that onboarding_paywall must match on every arm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a no-speech reply told the tutorial the question had failed. A transcription error, quota error, dropped connection or a stream that ended without an answer left it on 'processing your question' for good. sendVoiceMessageStreamToServer now takes onFailed for all of those; the capture controller plays the failure haptic and moves the tutorial to its try-again state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OmiEmptyState's glyph is optional; Home and Tasks drop the forum/check icons above 'No conversations yet' / 'No tasks yet'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a three-way onboarding experiment (#21650) ## Summary Adds Yinka's onboarding (PR #21445: Name → Permissions → Language → Connect Omi → a first real conversation, its summary and to-dos, the paywall, the rating ask) as the `yinka` arm of a three-way first-run onboarding experiment, next to today's native steps (`control`) and Reid's web page (`reid`). Nothing changes for anyone not in the experiment: the flag targets the test account only, and an unread flag, an unknown variant or a bad payload is control. - `OnboardingVariantGate` replaces `OnboardingWebviewGate`: one multivariate PostHog flag `onboarding-variant` (`control` / `reid` with `{"url"}` payload / `yinka`), same identity wait, warm-up, account-switch fence and verified reload. Debug: `--dart-define=OMI_ONBOARDING_VARIANT=reid|yinka`. - Yinka's screens live under `app/lib/pages/onboarding/variants/yinka/` (copied from her branch, imports rebased) with a small `YinkaOnboardingFlow` page that runs them after sign-in and consent, like the web onboarding page. The wrapper still owns completion; it skips the end-of-onboarding paywall placement when the activation already offered a plan. - Shared, additive changes her flow needs: capture controller audio taps (+ a flag to hold the generic "talk for 30 seconds" notice), onboarding provider quiet scan / `endSearch`, Bluetooth in the permissions panel enum, scanning ripple colour, in-app web page close button, the Omi shop helper, 32 strings in all 49 locales. - Out of scope on purpose (not onboarding): her Home live pill, Settings upgrade button, paywall re-ask, Superwall pilot, pull-to-refresh, Get-Started removal. - The wrapper emits `Onboarding Variant Shown` with `$feature/onboarding-variant` for every arm, the experiment's exposure event. PostHog: flag 961069 (test account → `yinka`, everyone else control), draft experiment 484222, dashboard 2212642. ## Testing Simulator (dev flavour, prod backend, `OMI_ONBOARDING_VARIANT=yinka`): Name → Permissions → Language → Connect Omi ("Not ordered yet" opens the store sheet) → "Let's try Omi" → dictation → "You're all set, Nik" → rating prompt → outline paywall (the account's paywall arm) → Home, no second paywall. Kill-and-relaunch mid-flow resumed at Connect Omi. 60 unit/widget tests pass (gate, activation flow, connect step, summary text, existing onboarding tests). ## Product invariants affected - INV-CAP-1 — the capture controller change only adds observer taps (a copy of each phone-mic buffer / pendant packet handed to a listener) and a flag that hides a notice; no uplink, subscription, WAL or frame path is closed, fenced or skipped. ## Failure class (fixes) Failure-Class: none UI-Evidence: none -- new screens are flag-gated to the test account; simulator walkthrough screenshots in the PR description are the evidence. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/BasedHardware/omi/pull/21650?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="View guided diff" src="https://www.cubic.dev/buttons/review-in-cubic-light.svg"></picture></a> <a href="https://www.cubic.dev/action/auto-fix/pr/BasedHardware/omi/21650?returnTo=https%3A%2F%2Fgithub.com%2FBasedHardware%2Fomi%2Fpull%2F21650&source=description" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"><img alt="Turn on auto-fix" src="https://www.cubic.dev/buttons/turn-on-auto-fix-light.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
|
Reviewed statically (draft, suspicious tier — no code executed). This is a big but coherent change — one product outcome (first-run ending in a first conversation, paywall experiment, Home live pill) — so I'm not asking for a split; feedback below is on substance. What looks good, with specifics:
Things that need a maintainer decision before this leaves draft:
Agent-guidance docs note: this PR rewrites Thanks @YinkaAdepoju — the engineering discipline here is genuinely good (fail-closed flags, both-arm exposure, fallback chains, tests per new service). Leaving for human maintainer review: the new payment SDK dependency + uid-sharing default need maintainer sign-off, and the onboarding/paywall product direction (rating placement, re-ask cadence, #21426 supersession) is a product decision, not a code-correctness one. -- by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with |
|
Closing in favour of two smaller pieces: the onboarding itself landed as the |
|
Hey @YinkaAdepoju 👋 Thank you so much for taking the time to contribute to Omi! We truly appreciate you putting in the effort to submit this pull request. After careful review, we've decided not to merge this particular PR. Please don't take this personally — we genuinely try to merge as many contributions as possible, but sometimes we have to make tough calls based on:
Before your next PR, please skim:
If this was declined for direction or taste, maintainers should cite an invariant ID or open a proposed one — ask if that citation is missing. Your contribution is still valuable to us, and we'd love to see you contribute again in the future! If you'd like feedback on how to improve this PR or want to discuss alternative approaches, please don't hesitate to reach out. Thank you for being part of the Omi community! |
Why
Device buyers who use Omi in their first days subscribe; most who don't subscribe barely use it and never see a paywall. The voice intro is the biggest drop inside onboarding. So: one clear use moment with a visible result, then a paywall for everyone, then a Home that isn't empty.
What changed
Onboarding (both arms)
Onboarding Device Choice), so we can finally see buyers we can't match by email.New ending (activation arm)
Home and Settings
Device-test fixes (Oct 14)
From a run on a real iPhone with live transcription:
A/B setup
onboarding-activation-flow(inactive until launched, so everyone gets control). 50/50control(today's voice intro) /test(new ending), user-based.$feature_flag_called, so the experiment counts the custom eventOnboarding Ending Shown, which now carries$feature/onboarding-activation-flow. People who fell back to control (no identity, slow read, error) carry no variant and stay out of the analysis.app/lib/services/experiments/onboarding_activation_flow.md.Paywalls: built on #21450
Every upgrade entry point (end of the activation, Settings Upgrade, Plan & Usage, locked conversations and memories, chat quota, Plus meter) calls one helper,
openUpgradePaywall: Superwall pilot when it's on → otherwise Nik'sPaywallRouter(flagpaywall-variant: control / outline / web) → otherwise the existing plans sheet.Paywall Openedis logged once per open; the web variant's fallback (forceNative) skips the router so it can't loop.OMI_ONBOARDING_RERUN=1) are kept.Paywall re-ask on app open (A/B, flag
paywall-reask)Free users keep opening the app but rarely meet a paywall, because every paywall today waits for a limit. On launch or return to the app, a free user whose re-ask is due gets the usual paywall (same helper, so
paywall-variantstill picks the design), queued inPromptQueueso it never covers a recording, a call or another prompt.control/test. Both arms are exposed ($feature_flag_called) at the same due moments; control shows nothing. NewPaywall Openedsourceapp_open_reask. Pre-registration:app/lib/services/experiments/paywall_reask.md(primary: Upgrade Succeeded within 14 days; guardrail: active days can't drop >5%).Superwall pilot (iOS debug, opt-in)
Named placements:
device_setup_complete(end of onboarding),settings_upgrade,plus_meter,locked_content,usage_upgrade,chat_apps,chat_quota,transcription_settings_usage. Off unless a debug build passes--dart-define=SUPERWALL_TEST_MODE=true; anywhere it's off, the paywall experiment decides.For production: Omi's own Superwall project (replaces the test key) with campaigns for these placements; the plans as in-app purchases in App Store Connect; backend reconciliation of purchases. See
app/docs/superwall-pilot.md.How to try it
flutter run --flavor dev(debug), as a new user: new ending + the paywall experiment's arm (control = plans sheet) by default.--dart-define=OMI_ONBOARDING_ENDING=control: old ending--dart-define=OMI_ACTIVATION_DEMO=1: scripted words on a simulator--dart-define=OMI_PAYWALL_VARIANT=outline(orweb): force a paywall arm--dart-define=SUPERWALL_TEST_MODE=true: Superwall pilot paywall instead--dart-define=OMI_PAYWALL_REASK=test --dart-define=OMI_PAYWALL_REASK_NOW=true: re-ask on every app openRisks / open
OMI_ACTIVATION_DEMO), because the local server has no speech-to-text. Next: a real phone run against a backend with live transcription.Verified
flutter test test/widgets test/providers test/unit test/spine: 4,418 passed (1 skipped, as on main) before the re-ask; full suite on the re-ask commit: 4,425 passed (1 skipped); re-ask tests re-run after the schedule change: pass.scripts/pr-preflight --lane local: 31/31 checks pass.Screenshots (iOS simulator, debug build with scripted dictation)
Repo checks
Product invariants affected
main.dartonly addsSuperwallPilot.configure()/syncUser(uid); routing unchanged, pilot inert outside iOS debug.memory_item.dartroutes the locked-memory upgrade throughopenUpgradePaywall(same source); storage unchanged.capture_controller.dartadds an optionalphoneMicAudioTapthat gets a copy of phone-mic PCM; never consumes, delays or drops audio.Line-count exceptions
Line-Count-Exception: app/lib/backend/preferences.dart | 1514 -> 1519 | one preference for the closable Home voice-profile card
Line-Count-Exception: app/lib/providers/device_provider.dart | 1488 -> 1504 | hold the firmware prompt during first-run onboarding and show it on Home
Line-Count-Exception: app/lib/utils/analytics/analytics_manager.dart | 3131 -> 3138 | one onboarding device-choice event for matching buyers to orders
Line-Count-Exception: app/lib/pages/settings/transcription_settings_page.dart | 1554 -> 1559 | Superwall placement name on the existing usage-page route
Failure class (fixes)
Failure-Class: none
🤖 Generated with Claude Code