feat(app): UI redesign — Convos-first nav, Chat tab, Brain tab, consolidated Todos - #11265
feat(app): UI redesign — Convos-first nav, Chat tab, Brain tab, consolidated Todos#11265jacobtrebil wants to merge 19 commits into
Conversation
Replaces the SF Pro Display family with Space Grotesk (SIL OFL 1.1) and sets it as ThemeData.fontFamily, which is what reaches the inline TextStyles throughout the app — most of them name a size and weight but no family, so they inherited SF Pro implicitly. Adds lib/utils/app_typography.dart as the single place the type ramp is defined, so screens stop hardcoding sizes independently. Verification: flutter analyze on the changed files reports 4 info-level lints, all pre-existing (verified none appear in the added lines). Rendered on the iOS simulator (iPhone 17 Pro, iOS 26.5).
… card
Memories were reachable only through the settings drawer, while a
preview of the same graph occupied roughly a third of the Home screen —
rendering five unlabeled dots that could not be read or acted on. That
inverted the importance of the two surfaces.
- Adds a Brain tab to the bottom nav (memories + the knowledge graph).
FontAwesome brain icon; the graph page's own analytics event was
already named brainMapShareClicked, so the name matches what the code
already called it.
- Removes the Mind Map card from Home. The graph keeps its existing
full-screen entry point from within memories, where it has room for
labels.
- Localizes the nav labels (navHome/navConvos/navTodos/navBrain) across
all 49 supported locales.
Analytics event names stay English and unchanged ("Conversations",
"Tasks") even though the visible labels differ, so the existing funnel
does not break.
Verification: flutter analyze on the changed files reports only
pre-existing info-level lints. Navigated all five tabs on the iOS
simulator (iPhone 17 Pro, iOS 26.5).
The Brain tab rendered the knowledge graph full-bleed, so nodes ran off every edge with no boundary showing where the graph ended — it read as the page having escaped rather than as something to look at. BrainPage now frames it: a grey (#1A1A1A) card on the page's deep black (#0F0F0F), 28pt corners, with a "Mind Map" header and an Expand button to the full-screen graph. The page colour matches what the nav bar's gradient already resolves to, so page and bar read as one surface with the card floating on it. Two details that are load-bearing: - The bottom nav bar is Align(bottomCenter) inside a Stack and takes no layout space, so the card reserves 116pt of bottom clearance by hand. Without it the lower corners sit behind the bar and never show. - MemoryGraphPage's embedded mode hardcoded ColoredBox(Colors.black), which painted over any parent surface. Embedded mode now draws no background and lets the embedding parent own the colour. BrainPage is its only embedded consumer. Verification: flutter analyze clean on both files (2 pre-existing deprecation infos in the share path). Built and run on the iOS simulator (iPhone 17 Pro, iOS 26.5).
Home led with a stack of previews and buried the query box at the bottom, below a decorative graph. The query box is the product — Omi's value is that it already knows your context — so it becomes the Home surface: HomeHero renders the headline and chat bar, with its entrance animation playing once per launch rather than on every tab return. The app bar also reserved kToolbarHeight on tabs that render no chrome (Conversations, Brain, Apps), leaving an empty band above each page's own headline. It now collapses to zero when empty. The collapse is conditional rather than blanket because the Conversations bar still holds two buttons — sync (device paired / files pending) and calendar (date filter active); blanket-collapsing would have hidden the sync button for anyone with a device. Also reworks the Apps explore/install page and the conversations folder tabs to match, moving the Apps create menu into an overflow beside its search field. Verification: flutter analyze clean across all changed files (4 pre-existing info lints). Exercised every tab on the iOS simulator (iPhone 17 Pro, iOS 26.5); confirmed by screenshot that the gap above the Conversations headline is gone.
The card version stacked three near-blacks in one column — page #0F0F0F, card #1A1A1A, button #252525 — which reads as banding rather than depth. The graph now draws straight onto the page background and gets the whole width, with a ClipRect so node glows can't paint up into the header, and 100pt of bottom clearance so nodes don't settle under the floating nav bar where they can't be read or tapped. Verification: flutter analyze clean (2 pre-existing deprecation infos in the share path). Rendered on the iOS simulator (iPhone 17 Pro, iOS 26.5).
Tapping "Ask Omi" pushed ChatPage as a full route. That was residue, not
design: the bar was a Container styled to look like a field, and its tap
fired bottomNavigationTabClicked('Chat') — Chat used to be a nav tab, and
when it was removed the bar became a shortcut to the orphaned page.
Home now renders ChatPage in place, so asking Omi something never leaves
the tab. The header (Connect / Record / settings) stays. ChatPage gains:
- `embedded`: no app bar of its own, and it keeps its Scaffold so the
chat-apps end drawer still works.
- `emptyState`: Home passes its hero, so an empty chat *is* the Home
screen rather than a bare "no messages yet".
- HomeProvider's chat focus node instead of its own, and dispose() leaves
it alone since the provider owns it.
- No auto-focus when embedded — as a tab body that fired at launch and
popped the keyboard over a screen nobody asked to type on.
Three layout fixes fell out of embedding it:
- The composer reserves the nav bar's 100pt unconditionally. Making it
depend on focus put the composer back under the bar the moment it was
tapped, so only ever one of the two was visible.
- The nav bar no longer hides on chat focus. That branch had no live
callers before this change (nothing used chatFieldFocusNode), and Home
being the chat makes focusing the composer the most ordinary action on
the tab — dropping the nav made it feel like the app had navigated away.
- The dynamic spacer that reserves room for a streaming reply now sizes
off the list's own viewport via LayoutBuilder, not the whole screen.
Screen-height was fine for a full-page route; as a tab body the list is
~276pt shorter, so the reserve overshot and scrolled the just-sent
message off the top. Pre-existing latent bug, exposed by embedding.
Also swaps the nav order to Home / Convos / Brain / Todos / Apps. That
touched every index-keyed branch: the page list, tap-again-to-scroll, the
brainMapOpened event, the task selection bar, the Tasks app-bar buttons,
_hidesAppBarChrome, the Today card's "View All", and the iOS quick action
that jumps to Tasks — the last two would otherwise have silently landed
the user on the graph.
Verification: flutter analyze clean across all changed files (2
pre-existing print lints). Exercised on the iOS simulator (iPhone 17 Pro,
iOS 26.5): every tab, the chat empty state, and composer + nav bar
visible together in both focus states.
Checking off a task now fills the circle green, strikes the text through, and plays a short confirmation sound before the change is reported. The asset needed a .gitignore exception. `*.wav` is ignored repo-wide, so assets/sounds/task_complete.wav was invisible to git while pubspec.yaml declared assets/sounds/ and ui_sounds.dart played it — every other clone would have built with the asset missing. Added a negation next to the existing one for the backend transcription probe, so future UI sounds do not hit the same trap. Verification: flutter test test/widgets/action_item_completion_test.dart — 3/3 pass, covering the open state, the completion transition, and that un-checking reports immediately with no animation. flutter analyze clean.
Tapping "Ask anything" shifted the whole screen — the composer dropped roughly 100pt onto the bottom nav and the hero slid down with it. Two mechanisms were fighting: ChatPage's Scaffold resizes itself for the keyboard inset, while the composer separately reserved a flat 100pt for the floating nav bar, so the reservation was counted twice the moment the inset appeared. Netting the two against each other was still fragile, so the embedded case now opts out of inset resizing entirely and keeps a fixed 88pt clearance — the bar's opaque 80pt strip plus a small gap, which is where the composer landed when focused and is the position that reads right. The layout is then identical focused or not. Auto-focus is restored for the embedded case too, so Home opens ready to type. It was disabled earlier only because focus used to hide the nav bar and pop the keyboard over a screen nobody had asked to type on; neither is true now, and with the layout pinned there is nothing left to jump. Known limitation: with inset resizing off, a real software keyboard will cover the composer on a physical device. The simulator's hardware keyboard hides this. The fix there is to lift the chat and the nav bar above the keyboard together rather than re-enabling resize — deferred until it can be built against a real keyboard. Verification: flutter analyze clean on both files (2 pre-existing print lints). Built and run on the iOS simulator (iPhone 17 Pro, iOS 26.5); composer and nav bar hold position, confirmed by screenshot.
Adds a daily recaps carousel to Conversations with its own full page, alongside refinements to the action items list (completed view, tile treatment, scrolling) and the shared UI guidelines they draw on. Verification: flutter analyze clean across the changed files (8 pre-existing info lints, none new). flutter test on the three action-item widget suites — 5/5 pass, covering the open state, the completion transition, un-checking, the completed view, and list scrolling on overflow.
Five headings for eight tasks read as filing rather than as a list of what to do. Overdue, Tomorrow and No deadline are gone: - Overdue folds into Today. A past-due task is the most actionable thing on the page, and its own heading sat below everything else — the most urgent work was the last thing you saw. - Tomorrow and No deadline fold into Later, except undated items older than a week, which go to Today so they don't drift indefinitely. Removes what became dead weight with them: the whole overdue section builder, the collapsible no-deadline header and its expansion state, and a duplicate clear-completed control that only existed on that path. Verification: flutter analyze clean on the file. flutter test test/widgets/ — 147 pass; the single failure is the local-only demo auth override short-circuiting the reauth shell, verified passing with that flag off and unrelated to this change.
Tapping the composer moved the whole screen. The cause was a second BottomNavBar rendered inside ChatPage itself, conditioned on !hasFocus — a leftover from when it was a pushed route that needed its own. Embedded, Home already supplies one, so the duplicate appeared and disappeared on focus and took ~100pt of layout with it. Three earlier attempts at clearance values and keyboard-inset arithmetic were treating the symptom; none could have worked while a widget was entering and leaving the tree. Before the first message the composer now renders inside the empty state, under the headline it answers, rather than stranded at the foot of a blank screen; it drops to the bottom once there is a conversation. That needed the composer extracted from the body Column into _buildComposer so it could render in either place — no behaviour change, just reachable from two call sites. Centring took three passes worth recording, because the arithmetic and the perception disagree. The bottom nav floats over the body rather than occupying layout, so centring in the full body sits half a bar-height low; that part is a genuine correction. But an invisible counterweight that centred the headline/composer pair exactly left every visible element below the midpoint under a large void, which reads as low however correct it is. Centring the whole group — headline, composer, prompts — is what actually looks centred. Also restyles the starter prompts: filled #131317 rather than a 12% hairline, with quieter text. A hairline is harder to pick out than a soft surface and gave them the look of controls; the fill sits between the page and the composer so they read as subordinate to the input above them. Drops the "what did I say I would follow up on?" suggestion. Known gap: embedding removed ChatPage's app bar, which was the only caller of openEndDrawer() — Clear chat and the chat-app selector are currently unreachable from Home. Tracked for a follow-up entry point. Verification: flutter analyze clean across lib/ (0 errors, 0 warnings). Exercised on the iOS simulator (iPhone 17 Pro): composer holds position through focus changes, the empty state centres, tapping a prompt sends it and the composer drops to the bottom.
Continues the redesign pass across the shared surfaces: the Home hero's entrance animation (with a test covering that it plays once per launch), the daily recaps carousel and its detail page, goals, folder tabs, conversation list items and bottom bar, the battery/status chip, and the shared UI guidelines they draw on. Verification: flutter analyze clean across lib/ (0 errors, 0 warnings; 148 pre-existing info lints). flutter test test/widgets/ — 147 pass. The one failure is the local-only demo auth override short-circuiting the reauth shell, verified passing with that flag off.
- Tasks collapse to a single Todos section with Completed below it. Due dates already show on the rows, so the headings were splitting a short list without adding anything. TaskCategory stays as a single-value enum because ordering, drag-and-drop and the drop zones are keyed by it. - Starter prompts lose their pills entirely and render as plain text. The rounded capsule is the composer's shape and should stay unique to it; repeating it made the suggestions read as three peers of the input. Padding remains as the tap target. - The completed check goes white (AppStyles.completedAccent), removing the last chromatic accent on the page. Changed at the token so both the list checkbox and the animated tile checkbox move together. Also carries the other agent's in-flight typography, recaps carousel, conversation list and hero work from the same tree. Verification: flutter analyze lib/ — 0 errors, 0 warnings (148 pre-existing info lints). flutter test on the three action-item suites — 6/6 pass. Exercised on the iOS simulator (iPhone 17 Pro).
…eaders The + sat beside the input as its own bordered circle, with a 56pt spacer reserving space for it. It now sits on the pill's left edge, mirroring the mic on the right, and the composer runs full width. Its own fill, border and shadows are gone: a bordered circle inside a bordered pill reads as a control stacked on another control, and the shadows only existed to lift it off the page background. The recording stop button shares that slot and moves with it. The pill's left padding grows to 52 while a button is showing so placeholder text can't run underneath. Also matches the Daily Recaps header's bottom padding to the Conversations header (10 -> 16), so both section titles sit the same distance above their content. Carries the other agent's in-flight conversation list, grouping and hero work from the same tree. Verification: flutter analyze lib/ — 0 errors, 0 warnings. Built and run on the iOS simulator (iPhone 17 Pro); + renders inside the pill and the menu still opens.
The dev-flavour BatteryWidget set PRODUCT_BUNDLE_IDENTIFIER to "$(APP_BUNDLE_IDENTIFIER).development.widget", but for dev builds APP_BUNDLE_IDENTIFIER already ends in ".development" — producing com.friend-app-with-wearable.ios12.development.development.widget. No such identifier exists on the Apple team, so signing a dev build to a device fails with "Failed Registering Bundle Identifier ... cannot be registered to your development team". The prod widget config and the watch app both use the correct single-suffix pattern; this brings the three dev configs (Debug/Profile/Release) in line. Verification: reproduced the failure building the dev flavour to a physical iPhone, applied the fix, and the identifier error is gone — device signing then fails only on provisioning-profile membership, which is an Apple account permission issue rather than a project one.
…lish Reorders the tabs to Convos / Chat / Brain / Todos / Apps and renames the old Home tab to Chat, using the existing `chat` l10n string so all 49 locales come along. The icon moves from a house to comment-dots, since a house no longer describes it. The analytics event stays 'Home' — the destination is unchanged, only its name and position — matching how Convos and Todos kept their original event names. Tab position is duplicated as bare integers across several files, so the reorder had to move with it: the page list, tap-again-to-scroll, the merge action bar, the sync and calendar buttons, and the app-bar chrome gate. Worth noting for whoever reorders next — nothing here is compiler-checked, and a missed site fails silently in one screen. Bottom-bar changes: - Drops the 20pt scrim above the nav bar. It fades transparent to the page colour, which reads as haze over dark content rather than as a soft edge; the hairline alone is a cleaner boundary. Clearances are unaffected — they were always measured against the opaque 80pt strip. - Lifts the chat composer to 112pt and lightens its fill to #26262E. Sitting tight above the bar in near-identical greys, the two read as a double-decker toolbar; the composer should be plainly the primary control with navigation beneath it. - Trims the Conversations header's bottom padding to 4. Each conversation card carries its own 12pt top padding, so 16 there put the first card 28 below the heading while the recaps cards sat at 16. Verification: flutter analyze lib/ — 0 errors, 0 warnings (148 pre-existing info lints). Exercised every tab on the iOS simulator (iPhone 17 Pro); measured the header spacing against the recaps carousel by screenshot rather than by eye.
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks for pushing this redesign work — the direction is cohesive around the Flutter app shell/Home/chat/Todos experience, and I like that you included widget coverage for the new home hero and task-completion behavior.
I need to request changes before this can be reviewable/mergeable because the current head appears to leave local demo scaffolding wired into production code:
app/lib/backend/http/api/users.dartimportspackage:omi/dev_demo_data.dartand callskDemoDataEnabled/demoDailySummaries()insidegetDailySummaries(), butapp/lib/dev_demo_data.dartis not present in the PR or repository at this head. A fresh checkout therefore cannot resolve this import and the Flutter app will fail analysis/build before runtime.app/lib/providers/goals_provider.dartandapp/lib/providers/message_provider.dartaddseedDemoData(...)APIs and comments that also point atlib/dev_demo_data.dart. If these hooks are only for local prototype data, they should be removed before merging; if they are intended as a real fixture/demo mode, please add the missing implementation behind an explicit non-production/test boundary.app/lib/pages/conversations/widgets/daily_recaps_carousel.dartnow callsgetDailySummaries()directly, so the unresolved demo-data branch inusers.dartaffects the new carousel path too, not just an unused helper.app/pubspec.yamland.gitignorecorrectly addassets/sounds// allow committed WAV assets forapp/lib/utils/audio/ui_sounds.dart, but I could not complete a local Flutter analyzer run in this environment because Flutter/Dart are not installed here; the unresolved import above is from static inspection of the checked-out PR head.app/lib/pages/home/page.dart,app/lib/pages/chat/page.dart,app/lib/widgets/bottom_nav_bar.dart, andapp/lib/pages/action_items/action_items_page.dartmake broad navigation/product-surface changes. Once the build issue is fixed, this still needs maintainer product/UX sign-off because Home becoming the embedded chat, adding Brain, and moving goals/Todos are substantial app-direction choices.
Suggested next step: remove the dev_demo_data import/branch and any local-only seed hooks that are not meant to ship, or include a properly scoped test/demo fixture that is available in clean clones and cannot affect production users. After that, please rerun Flutter analysis/tests for the app/widget suite so maintainers can review the UX changes on a buildable head.
Leaving this for human maintainer review as a product-direction decision for the app navigation/Home redesign after the build blocker is fixed.
— Reviewed by gpt-5.5 for the Omi maintainer automation.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
A clean clone of this branch could not build. `users.dart` imported `package:omi/dev_demo_data.dart` — a local-only file deliberately kept out of git — and branched on it inside `getDailySummaries()`. Three committed widget tests also called `ActionItemsProvider.seedDemoData`, which existed only in my working tree, so the test suite did not compile either. Both were reported by the PR reviewer and by a clean-tree analyze; the test breakage was not visible locally because the file carrying it is marked skip-worktree. Root cause: demo scaffolding was protected by a two-part scheme — `.git/info/exclude` for the data file, `skip-worktree` for its callers. `mobile_app.dart`, `conversation_provider.dart` and `memories_provider.dart` were flagged; `users.dart`, `goals_provider.dart` and `message_provider.dart` never were, so their hooks committed silently. An unflagged file gives no signal that it is carrying something that must not ship. - `users.dart`: import and demo branch removed. Production request code should not have carried a fixture seam at all. - `ActionItemsProvider`: `_demoMode` is gone. It suppressed the server write and kept the optimistic update — reachable in a production build and silently dropping task updates if ever set. Replaced with constructor injection of the update request, matching the injection this class already used for `getActionItems`/`deleteActionItem`, plus a `@visibleForTesting seedItems()` that only fills the list. The default remains the real API, so production behaviour is unchanged. - `goals_provider` / `message_provider`: seed hooks removed. Nothing in a clean clone referenced them. The three tests now inject `fakeUpdate` (`test/widgets/support/`), which confirms the write without a network call. That is what keeps the completion from rolling back — previously supplied by the demo flag. Verification: `flutter analyze lib/ test/` — 0 errors in committed code. The 3 remaining errors are in `lib/mobile/mobile_app.dart`, which is skip-worktree'd local-only scaffolding and is not part of this branch. Full widget-suite results follow in a separate message; this commit is staged from an analyze-clean tree.
A clean clone of this branch crashed on launch. `ios/Flutter/Custom.xcconfig`
is generated per machine by `setup.sh` — the bundle identifier is derived from
the developer's hostname (`com.friend-app-with-wearable.ios12-${hostname}`) so
each machine installs a distinct app. The file says so on line 1: "This is a
generated file; do not edit or check into version control."
It was tracked anyway, and the committed copy carried a bundle id with no
hostname suffix. That identifier no longer matches the one in
GoogleService-Info.plist, so FirebaseApp configuration fails during startup —
the app builds fine and then dies every time it opens.
Two things kept this invisible to the file's owner:
- `.gitignore:78` already lists `**/ios/Flutter/Custom.xcconfig`, but gitignore
does not apply to files that are already tracked, so the entry did nothing.
- The file was flagged `skip-worktree`, which suppresses the diff between the
local machine-specific copy and the committed one. Locally everything looked
clean; only a fresh clone saw the stale value.
`git rm --cached` untracks it while leaving the working copy in place. The
existing gitignore entry now takes effect, and `bash setup.sh ios` regenerates
it — `scripts/generate_ios_custom_config.sh` truncates and rewrites the file, so
a clone with no copy at all gets a complete one.
Also clears `skip-worktree` on the seven other flagged files (mobile_app.dart,
knowledge_graph_api.dart and the five providers). Those carry local demo
scaffolding that must not ship; the flag hid their divergence from HEAD, which
is the same mechanism that let demo hooks commit silently in f9c5c39. They
now show in `git status`, where they can be seen and deliberately left unstaged.
Verification: `git ls-files -v` reports zero skip-worktree entries;
`git check-ignore` now resolves Custom.xcconfig to .gitignore:78; the local file
retains its `-macbookpro` suffix; `git grep` over HEAD finds no dev_demo_data,
seedDemoData, kDemoDataEnabled or _kForceSignedInShellForUiWork.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`home_hero_pixel_test` hangs and is only recorded as a failure ten minutes later, when the framework's default timeout fires. It wedges the runner rather than failing: a `flutter test test/widgets/` run reaches +148 in 22 seconds and then sits on this one test until the timeout. A `--timeout 120s` on the command line does not shorten it. The defect is in the test. `boundary.toImage()` runs inside `tester.runAsync`, but `image.toByteData()` on the next line runs outside it. Under the widget tester's fake async that future never completes, so the await blocks forever. Deleting rather than repairing it. This was a diagnostic I wrote while tracking down whether the headline painted white, and its own doc comment says so — "this establishes whether the widget itself paints white, so the search can move to the app tree instead of going in circles". It prints its finding and asserts only that some pixel is near-white. That question was answered; what is left is a brittle probe that depends on the test font renderer, and it never belonged in the committed suite. The hero's committed coverage stays: `home_hero_entrance_test` exercises the widget's real behaviour, and headline colour is covered by the INV-UI-1 brand ratchet. Verification: `flutter test test/widgets/` in a clean checkout of this branch (no skip-worktree flags) — 148 pass, 1 fail before this commit, the failure being this test's timeout. Re-run after removal reported below.
|
Fixed in The There was a second instance you couldn't have seen without running Flutter: three action-item tests called Also removed Verified on a fresh checkout with no local index state: The nav and Home/Chat changes are still product-direction calls and need a maintainer's view. |
|
yo can we get screenshots pls :)) |
Resolved on current head: the dev_demo_data import/branch and local-only seedDemoData hooks from the prior automation review are no longer present.
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks for the follow-up. I re-reviewed the current head and confirmed the previous build blocker around local demo scaffolding has been addressed: app/lib/backend/http/api/users.dart no longer imports dev_demo_data, and the prior seedDemoData references are gone from the providers.
I still need to request changes for a remaining navigation regression introduced by the tab reorder:
app/lib/pages/home/page.dartnow defines the tab order as Conversations(0), embedded Chat/Home(1), Brain(2), Tasks(3), Apps(4), andapp/lib/widgets/bottom_nav_bar.dartrenders the same five-tab order. However the deep-link initialization still maps/action-itemstohomePageIdx = 2at lines 307-310, and the later switch still says the action-items tab has already been selected. With the new order, index 2 isBrainPage, so notification/deep-link entry points for action items open the Brain tab instead of Todos. Please update that mapping to the new Tasks index and add/adjust a regression test for the route/initial-index mapping.
Other reviewed areas:
app/lib/pages/chat/page.dartcorrectly avoids disposing the sharedHomeProvider.chatFieldFocusNodewhen embedded, and the empty-state composer/prompt structure is coherent with Home-as-chat.app/lib/pages/action_items/action_items_page.dartnow consolidates active tasks into one Todos bucket and delays provider completion until after the tap feedback; the provider seam inapp/lib/providers/action_items_provider.dartis a reasonable way to keep widget tests hermetic.app/lib/pages/conversations/widgets/daily_recaps_carousel.dartuses the existinggetDailySummaries(limit: 7)path directly and stays out of the page when there are no summaries; I did not see the prior missing local fixture issue on this head.app/lib/pages/memories/brain_page.dartembedsMemoryGraphPagewith bottom-nav clearance, which matches the new tab-shell architecture.app/pubspec.yaml,.gitignore,app/lib/gen/assets.gen.dart, andapp/lib/gen/fonts.gen.dartare consistent with shipping Space Grotesk andassets/sounds/; this is product/brand-sensitive but not a supply-chain concern from the diff I inspected.- The new widget tests in
app/test/widgets/cover the action-item completion and home-hero behavior, but I could not run them in this review environment because Flutter is not installed here, and GitHub still has no passing check summary for this draft.
This PR is still a substantial app navigation/Home redesign, so after the routing bug is fixed it should remain with a human maintainer for product/UX sign-off before merge.
by AI on behalf of David; human maintainer review is needed for the app navigation and Home/Chat product direction.
Reviewed by glm-5.2 for the Omi maintainer automation.
What changed and why
A UI redesign pass over the Flutter app. The central change is that chat
becomes a first-class destination rather than a route pushed from Home's "Ask
Omi" bar — that push was residue from when Chat was its own nav tab (the tap
still fired
bottomNavigationTabClicked('Chat')).old Home tab is renamed Chat and holds the conversation with Omi.
drawer into a nav tab, and the Mind Map card comes off Home where it rendered
unlabeled dots across a third of the screen.
Overdue) to one list plus Completed. Due dates already appear on the rows.
lib/utils/app_typography.dart.colour, continuing the
INV-UI-1direction.feedback, and bottom-bar spacing work.
Product invariants affected
tiering are untouched; this is presentation only.
INV-UI-1is not cited: per its own PR rule, routine UI work relies on the brandratchet. This branch reduces purple (Record button, task FAB, completed check).
How it was verified
Exercised on the iOS simulator (iPhone 17 Pro, iOS 26.5) — every tab, the chat
empty state and composer focus behaviour, task completion, and the recaps
carousel. Spacing changes were measured against reference screenshots rather
than eyeballed.
Three limits worth stating plainly:
app/setup.shcopiesbased-hardware-devFirebase config while settingAPI_BASE_URL=https://api.omiapi.com/, sosignInWithCustomTokenfails withcustom-token-mismatch. Every screen here was exercised against fabricatedlocal data. That looks like a bug in the documented setup path rather than
anything specific to this branch, and deserves its own issue.
account permission, not on anything in the project. BLE, the widget and the
watch app are therefore unexercised — none are touched here, but the
app-group entitlement path is unproven.
literals across several files (page list, scroll-to-top, merge action bar,
sync/calendar buttons, app-bar chrome gate,
BatteryInfoWidget). The reorderupdated all known sites and each was checked on screen, but a future reorder
has no guard. Converting these to a named enum is the obvious follow-up.
Tests
flutter test test/widgets/— 148 pass, 0 fail, 27s.Run in a fresh checkout of this branch with no local index state, after
flutter pub get,dart run build_runner buildand thesetup.sh-generatedFirebase config — i.e. what a maintainer gets, not my working tree. Same
checkout:
flutter analyze lib/ test/reports 0 errors.New coverage lands with the behaviour it asserts:
action_item_completion_test,action_item_completed_view_test,action_items_page_scroll_test, andhome_hero_entrance_test.Two problems the first push had, both fixed here and both invisible from my
working tree, since the files carrying them were marked
skip-worktree:users.dartimportedpackage:omi/dev_demo_data.dart, a local-only fileexcluded from git, so no clean clone could build. Reported in review.
ActionItemsProvider.seedDemoData, which alsoexisted only locally, so the suite did not compile either. They now use the
constructor injection this provider already had, plus a fake update request in
test/widgets/support/.An earlier
home_hero_pixel_testhas been removed. It awaitedtoByteData()outside
tester.runAsync, which never completes under fake async — the runnerwedged and only failed at the 10-minute default timeout. It was a debugging
probe, not a guard.
Failure class (fixes)
Failure-Class: none
The one
fix:commit (995cd71) corrects a build-configuration typo: the devBatteryWidget derived
$(APP_BUNDLE_IDENTIFIER).development.widgetwhileAPP_BUNDLE_IDENTIFIERalready ended in.development, producing…ios12.development.development.widget. That identifier does not exist on theApple team, so any dev-flavour device build failed at signing. The prod widget
and the watch app already used the correct single-suffix pattern; this brings the
three dev configs in line. No existing class covers a per-flavour identifier
derivation typo, and one instance does not warrant a new one.
Scoped cleanups
ChatPage's secondBottomNavBar, which rendered inside the page andappeared/disappeared on focus — the cause of the composer shifting ~100pt.
the whole screen; as a tab body the list is far shorter, so the reserve pushed
the newest message off the top.
.gitignoreexception forapp/assets/sounds/*.wav. The repo-wide*.wavrule hid the completion chime while
pubspec.yamldeclared it, so every otherclone would have built with the asset missing.
Known gaps
Embedding
ChatPageremoved its app bar, which was the only caller ofopenEndDrawer()— Clear chat and the chat-app selector are unreachable fromthe Chat tab. They need a new entry point (the composer's
+menu is thenatural place) before this is user-ready.