Skip to content

feat(app): redesigned Home behind the home-redesign A/B flag (recording pill, upgrade in place) - #21711

Open
YinkaAdepoju wants to merge 19 commits into
BasedHardware:mainfrom
YinkaAdepoju:yinka/app-redesign-upgrade
Open

YinkaAdepoju wants to merge 19 commits into
BasedHardware:mainfrom
YinkaAdepoju:yinka/app-redesign-upgrade

Conversation

@YinkaAdepoju

@YinkaAdepoju YinkaAdepoju commented Oct 15, 2026 •

Copy link
Copy Markdown

Summary

A redesigned mobile Home, behind a PostHog A/B flag so it can be measured against today's Home before it ships to everyone.

What the redesigned Home changes (test arm)

One place for a live recording: the bottom pill. There is no recording card above the list anymore. The bottom bar is Ask Omi beside one pill, and the pill always says three things:

What is happening Listening · Reconnecting… · Paused · Recording (Transcribe Later) · Disconnected (Omi dropped) · the problem in a few words (mic taken by a call, transcription failed, storage full)
How it is going (dot) red, breathing = capturing fine · grey = paused or waiting · orange = needs a look
The one next step Stop while recording · Resume while paused

Tapping a healthy pill opens the live transcript. Otherwise it opens a sheet with why, the capture's own controls (Pause/Resume, Transcribe Later's New recording and Stop, retry transcription), the live transcript, Stop, and, for an Omi that dropped, "Record with phone instead". The pill and the sheet read one shared summary (conversations/live_capture_summary.dart), so they never disagree.

Also in the test arm

  • The processing row reads as the conversation it is becoming (condensing glyph, transcript preview, short light sweep; still under Reduce Motion).
  • Text-only empty states on Home and Tasks; Omi pull-to-refresh; Home fades in after onboarding.
  • Page actions sit beside the Home/Tasks switcher (calendar on Home); the top bar (device pill · Search · Activity · Settings) is identical on Home and Tasks.
  • Locked conversations and memories say Upgrade and open the paywall in place (via the paywall-variant router), instead of View Usage → the Usage page.
  • Tasks: a ticked (or reopened) task shows its new state, then folds away. Closable "Teach Omi your voice" card. Omi's first chat message in an empty chat. After onboarding: a dot on Tasks, and three starter to-dos when onboarding found none.

Today's Home (control) is unchanged: the Ask Omi chat bar with the round record button, the recording card above the list, today's processing row, empty states with icons, the calendar in the header, locked rows → Usage. Its widgets are kept in app/lib/pages/home/legacy/ until the experiment is decided; the losing path gets deleted then.

Shared by both arms (not part of the comparison): the Activity bell and pending row (#21689), the Tasks menu in the header (select, group, completed, export), the Settings plan card with Upgrade, and the end-of-onboarding control exposure for paywall-variant.

How the flag behaves

  • Client-evaluated, read once the analytics identity has settled (a listener, no polling), and also during onboarding, so a new reader's Home is decided before they first see it.
  • The arm is saved per account: a reader never switches Home mid-session; a new assignment applies from the next launch if Home was already on screen. Fail-closed: no identity, an unread flag or an unknown variant = today's Home.
  • Exposure: $feature_flag_called for home-redesign, once per account and arm, when Home is first shown after PostHog assigned an arm. $feature/home-redesign then tags later events.
  • Kill switch: set the flag to control 100% (or turn it off); every build shows today's Home without a release.
  • Try it locally (debug builds): --dart-define=OMI_HOME_REDESIGN=on or =off.

Review notes

  • Today rows split by stage with feat(app): declutter Home — Activity bell, pending row in Today, On the Way page #21689: the "on the way" row stands for audio not uploaded yet; once Home lists a Processing row, uploaded audio no longer lights the pending row, so one recording never shows twice (SyncPendingWork.shouldShowOnHome).
  • Fixes found in review, all covered by tests: Data & Privacy keeps Agree & Continue + Use a Different Account; an Omi that dropped no longer blocks recording with the phone; the live transcript can't open twice while the mic starts; the sheet's Stop never starts a new recording; starter to-dos refresh the Tasks list; the deferred firmware prompt checks the device is still connected.
  • app/lib/pages/home/legacy/legacy_home.dart is a copy of main's widgets, not new design: please skim, don't review line by line.

Screenshots

test arm on the iOS simulator against the local dev server (001–004 today; 005–007 from the earlier #21445 branch).

Home, no top card Listening Paused, Resume
Details sheet Tasks dot Chat welcome
Upgrade in place

Testing

  • Full app suite on this branch: 5,904 passed, 20 skipped, 0 failed.
  • make preflight: 30/30 checks pass.
  • New tests: test/unit/home_redesign_test.dart (unread = control, per-account memory, never switches a Home on screen, exposure only after assignment, no read before an identity); off-path tests for locked rows (View Usage → Usage page) and the processing row (today's widget); pill tests for every recording state (no card on Home, Paused + Resume, failure opens the sheet, Omi paused / dropped / Transcribe Later, Record with phone); the Today-rows stage split.
  • Manual: simulator walk-through of the pill (record → Listening → Paused → sheet → Resume → Stop → Processing row) and Home with no top card.
  • Not yet done: a run on a physical device from this branch.

Line-count exceptions

Line-Count-Exception: app/lib/backend/preferences.dart | 1538 -> 1543 | one setting: the closed "Teach Omi your voice" card
Line-Count-Exception: app/lib/pages/settings/transcription_settings_page.dart | 1572 -> 1575 | upgrade opens the paywall in place instead of the Usage page
Line-Count-Exception: app/lib/providers/device_provider.dart | 1598 -> 1614 | a firmware prompt found during onboarding waits until Home is up

Product invariants affected

  • INV-MEM-1 — no change to how memories are stored or reviewed; in the test arm a locked memory row opens the paywall in place instead of the Usage page.

Failure-Class: none

🤖 Generated with Claude Code

Yinka and others added 4 commits October 15, 2026 10:49
…1445, rebased on main)

Home: recording lives in the bottom bar (Ask Omi beside "+ New Recording", live pill while
capturing), the processing row reads as the conversation it is becoming, text-only empty
states, Omi pull-to-refresh, Home fades in after onboarding, a closable "Teach Omi your voice"
card. Chat opens with Omi's welcome and starters. Tasks: ticking or reopening a task shows its new
state, then folds the row away; a more menu. Sign-in and data & privacy: the Omi pendant on the
plain surface; signed-out readers open on sign-in (the old Get started page is removed).

Upgrade: a plan card with Upgrade in Settings; locked conversations and memories, transcription
settings, chat apps, chat quota and the plus meter open the paywall in place (PaywallRouter, then
the plans sheet). Paywall re-ask on app open for free readers behind `paywall-reask` (prereg in
paywall_reask.md). Control is exposed at the end-of-onboarding placement when its payload turns it
on. No Superwall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o-dos when onboarding found none; UI evidence

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… opens the paywall in place

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yinka and others added 7 commits October 15, 2026 11:14
…the pendant)

The launch film is the sign-in look for everyone now, not a debug-only comparison; the pendant
hero and its image are removed. UI evidence swapped to the film captures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tom pill, not a Home card

The pill reads Reconnecting… (no live pulse) and Home shows no separate card; the card stays for
what needs the person (paused, the microphone taken by a call, transcription that failed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…at the tap opens

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ows; no top card

Every state the Home card had now reads in the bottom pill: Listening, Reconnecting…, Paused
(its control is Resume), Transcribe Later ("Recording · Audio saved…"), the mic taken by a call,
transcription failed, storage full, and a pendant that dropped mid-capture ("Disconnected ·
Reconnecting…"). The dot says how it is going: red and breathing while capturing fine, grey when
paused or waiting, orange when something needs a look. Tapping a healthy pill opens the live
transcript; otherwise it opens a sheet with why, the capture card's own controls (Pause/Resume,
New recording, retry), the live transcript and Stop.

One summary (live_capture_summary.dart) feeds both the pill and the card, so they never disagree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n main; read the pendant's reconnecting state once per build

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yinka and others added 3 commits October 15, 2026 13:43
- Data & privacy: Agree & Continue again, and "Use a Different Account" is back (it was required
  but no longer shown, so a wrong account could only go forward by agreeing).
- Sign-in fires the onboarding welcome event the removed Get started page used to fire; drop an
  unused analytics helper.
- Home pill: a pendant that dropped mid-capture no longer blocks recording — its sheet and a long
  press record with the phone. Tapping the pill while the mic starts no longer opens the live
  transcript twice. The sheet's Stop only ever finishes a recording, never starts a new one.
- Starter to-dos refresh the Tasks list once created.
- The deferred firmware prompt checks the device is still connected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ka/app-redesign-upgrade

Resolved: Activity bell sits in the shell header on Home and Tasks (the top bar stays identical);
the device pill keeps its word on every page and shrinks with an ellipsis; pull-to-refresh also
reloads Review; translations keep both branches' strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… uploaded yet while Processing rows show

David's pending row (BasedHardware#21689) and the Processing rows could both stand for the same recording once
its audio reached the server. The pending row now counts uploaded audio only while Home lists no
Processing row; audio still waiting or uploading always counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e, control shows today's

One client-evaluated flag (`home-redesign`, prereg product/experiments/home-redesign.md) decides
which Home a reader sees: the bottom recording pill (no card above the list), condensing processing
row, text-only empty states, pull-to-refresh, page actions beside the switcher, locked rows that
upgrade in place, Tasks fold, closable voice card, chat welcome, Tasks dot and starter to-dos — or
today's Home (chat bar + round record button + live card, kept in app/lib/pages/home/legacy/ until
the experiment is decided). Unread flag = control. The arm is saved per account, read during
onboarding so new readers start in theirs, and never switches a Home already on screen. Exposure is
`$feature_flag_called`, once per account and arm, when Home is first shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yinka and others added 2 commits October 15, 2026 15:08
…ath tests for locked rows and the processing row; flag waits for the identity via a listener, not polling; fresh pill screenshots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t of BasedHardware#21711 into their own PRs

Both now live on their own branches (yinka/paywall-reask, yinka/signin-film). This branch keeps
main's sign-in, Data & Privacy and Get started screens, and no app-open re-ask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks @YinkaAdepoju — this is a well-built PR, and the rebasing of #21445 onto main as one coherent piece was the right call. Reviewed statically on 23eab70. Detailed notes below; several items genuinely need a maintainer's product call before this leaves draft.

What stands out as solid

  • PaywallReask (app/lib/services/experiments/paywall_reask.dart) is fail-closed everywhere I traced: no uid → nothing, flag-read error → nothing, null/empty variant → treated as not-enrolled. The 2/3/5/7-day schedule is per-uid in prefs, and both arms get their exposure recorded at each due moment so control is a real denominator — that's the right A/B shape.
  • Flag hygiene is exemplary: paywall-reask is registered in config/feature-flags.yaml + backend/docs/feature-flag-registry.md, has a prereg (app/lib/services/experiments/paywall_reask.md), and the app_open_reask source is added to both PaywallOpenedSource and PaywallVariantShownSource in the analytics contract. Since the flag isn't in PostHog yet, this ships dark.
  • PaywallExperiment.parse now reads the payload for control too, and PaywallRouter.exposeControl gives control its end-of-onboarding exposure at the same moment variants show — a real fairness fix, and the prereg update (product/experiments/paywall-variant.md) correctly warns that onboarding_paywall must match across arms.
  • home_bottom_controls.dart maps every capture state into the pill with stable ValueKeys, semantics labels/custom actions, and a breathing dot that respects Reduce Motion via OmiMotion.
  • The ux-contract.md §15 addition is accurate — I verified it against variants/yinka/permissions_widget.dart (switch cards, Tap-to-fix, Continue unlocks on every card answered).
  • Good test additions (paywall_reask_test, grow_route_test, tasks_more_menu_test, settings_plan_section_test, upgrade_paywall_test, voice_profile_card_test, chat_welcome_test), and moving integration tests from text-finding to home_ask_omi_button keys makes them more robust.

Product decisions that need a maintainer before merge

  1. Locked conversations/memories now say Upgrade and open the paywall in place (usage_page.dart delegates to openUpgradePaywall), replacing main's locked-row → Usage page. You flagged this yourself — agreed, it's a product call, not a code one.
  2. The app-open paywall re-ask cadence (2/3/5/7 days) is an aggressive monetization surface. The implementation is sound; whether Omi wants it is a product judgment.
  3. _addStarterTasks (onboarding/wrapper.dart) writes three synthetic to-dos to the user's real account via createActionItem when onboarding produced none. Once-per-account guarded and skipped on failed reads, but it is synthetic data in user accounts — worth an explicit sign-off.
  4. The Get started page is removed (device_selection.dart deleted); signed-out users open directly on sign-in over the launch film.
  5. The 3.7 MB onboarding_launch_loop.mp4 is bundled into every install.

Minor, non-blocking

  • paywall_reask_prompt.dart uses a module-level _checking bool as the re-entrancy guard — fine on a single isolate, and it doubles as dedupe, just noting it.
  • _isFree checks plan == PlanType.basic while exposeControl checks plan.isPaid; equivalent in effect, inconsistent in style.

Leaving for human maintainer review — the open questions above are product-direction decisions (monetization surfaces, onboarding entry, synthetic user data), not code defects. Nothing security- or supply-chain-sensitive turned up: no new dependencies (video_player is already on main), no new network paths, no secrets.


by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.

…me copy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@YinkaAdepoju YinkaAdepoju changed the title feat(app): Home redesign + upgrade entry points (rest of #21445, rebased on main) feat(app): redesigned Home behind the home-redesign A/B flag (recording pill, upgrade in place) Oct 15, 2026
@YinkaAdepoju
YinkaAdepoju marked this pull request as ready for review October 15, 2026 19:45
…iment (486169)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@YinkaAdepoju

Copy link
Copy Markdown
Author

Thanks David! Updates since the review (it was on 23eab70):

Full suite 5,904 passing, make preflight 30/30. @kodjima33 the remaining calls are product ones: in-place upgrade on locked rows, and starter to-dos, both only in the test arm.

@kodjima33 kodjima33 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Large mobile Home redesign behind off-by-default A/B flag - feature, approve-only. DIRTY so can't merge anyway.

@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Reviewed the full diff on this head. Thank you @YinkaAdepoju — this is an unusually disciplined experiment PR, and it shows in the details:

Flag mechanics (verified against the code):

  • app/lib/services/experiments/home_redesign.dart fails closed everywhere I traced: unread flag, control, unknown variant, missing identity, and a thrown flag read all keep today's Home. The arm is saved per account, refresh() re-checks the identity after the await so an account switch mid-read can't cross-contaminate arms, and _homeShown prevents a Home flip mid-session. The 89-line unit test covers each of these branches.
  • config/feature-flags.yaml + backend/docs/feature-flag-registry.md registry rows match the implementation (fail closed, client-evaluated, flag inactive until launch), and product/experiments/home-redesign.md is a real pre-registration — primary metric, guardrails including Product Error, an explicit decision rule, and a no-release kill switch. Good.
  • markHomeShown() only records exposure after an arm was saved, so unassigned readers are never counted as exposed. The stage-split SyncPendingWork.shouldShowOnHome cleanly prevents a recording from appearing in both the pending row and Processing rows.
  • _addStarterTasks in app/lib/pages/onboarding/wrapper.dart is once per account, gated on the experiment, and aborts on a failed server read — starter to-dos can't land on top of real ones.

One point worth a maintainer's eye: paywall_experiment.dart now reads the payload for control too, and PaywallRouter.exposeControl records a matched exposure at end-of-onboarding so both arms enter paywall-variant at the same moment. The methodology is right (and documented in product/experiments/paywall-variant.md), but it changes when control readers begin counting as paywall-exposed — worth confirming the running experiment's analysis window accounts for it.

Docs note: the app/docs/ux-contract.md addition documents the first-run permissions switch-card exception. This contract is used as context by AI coding/review agents in this repo, so this edit will shape how agents describe and modify onboarding permissions; the added text matches permissions_widget.dart and is accurate, so no concern — just flagging it since it's agent-consumed guidance.

legacy_home.dart skimmed as requested — a faithful relocation of today's widgets, no surprise edits spotted. No workflow, dependency, or native surfaces touched; the .agent-artifacts/ images are inert.

With the approval on record, the remaining human decision is the launch itself: activating PostHog flag 963799 once this build is in the stores. Leaving needs-maintainer-review for that merge-time product call.


This review was prepared by an AI reviewer operating on the maintainer's behalf.


by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flutter flutter work needs-maintainer-review Needs a human maintainer to sign off before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants