7.4 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, decisions, metrics
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02.1 | 05 | ui-shell |
|
|
|
|
|
|
|
|
Phase 02.1 Plan 05: App Shell Composables Summary
Built the four core authenticated-shell composables — ShellViewModel, AppShell, DockBar, FloatingSearchButton — wiring RootNavHost (02.1-04) inside a GlassBackdropSource (02.1-03) and overlaying a bottom chrome column with the SearchPill (02.1-06), DockBar, and FloatingSearchButton.
What Was Built
-
ShellViewModel + ShellState — pure synchronous state machine with three method-per-action signatures (
openSearch,closeSearch,onTabChanged). State is(activeTab, searchOpen)only — no query field; per-tab query state lives inRecipesSearchViewModel/PantrySearchViewModel. Mirrors LoginViewModel's StateFlow shape. -
DockBar — Liquid-glass capsule rendering 4 tabs (icon + label always shown, D-02) when expanded (28dp corner, 56dp tall) and collapsing to a single circular icon-only toggle on the active tab when search opens (22dp corner, 44dp tall, D-05). The collapse is one coordinated motion:
animateContentSizeon the GlassSurface modifier plusAnimatedContentwith a fadetogetherWithtransition, both at 250ms FastOutSlowInEasing per UI-SPEC line 198. Each tab cell exposesRole.Tab + selected + contentDescriptionsemantics; cells satisfy ≥44dp touch targets viadefaultMinSize(44dp, 44dp). -
FloatingSearchButton — 44dp
GlassSurface(cornerRadius = 22.dp)withIcons.Outlined.SearchtintedRecipeTheme.colors.content. Carriessearch_open_a11ycontentDescription. Visibility (only when!searchOpen && activeTab.hasSearch) is gated by AppShell, not the button itself. -
AppShell — authenticated root composable. Wraps
RootNavHostinGlassBackdropSourceso Liquid/Haze backends sample the body through the sharedLocalGlassBackdropState. Bottom chrome is aColumnalignedBottomCenterwithwindowInsetsPadding(WindowInsets.navigationBars) + imePadding()only — nosafeContentPadding()per Pitfall F. Conditionally renders aSearchPillwired to the active tab's SearchViewModel (Recipes or Pantry — both paths covered) above the always-presentDockBar. TheFloatingSearchButtonis overlaid atBottomEnd. Active-tab tracking derives fromNavBackStackEntry.destination.hierarchyvia type-safehasRoute(*Graph::class); aLaunchedEffect(activeTab)keepsShellViewModel.activeTabin sync for back-button and deep-link cases. Tab selection navigates vianavigateToTab(dest.graphRoute)and notifiesvm.onTabChanged(dest).
Plan Output Questions Answered
- Both Recipes and Pantry SearchViewModel paths covered? Yes —
AppShellhas explicitwhen (activeTab)branches for bothBottomBarDestination.RecipesandBottomBarDestination.Pantryfor SearchPill rendering and FloatingSearchButton onClick.PlannerandShoppingare no-ops becausehasSearch = falsealready gates the surfaces. - Compose Unstyled TabGroup vs Row + semantics? Used the
Row + Modifier.semantics { role = Role.Tab; selected; contentDescription }fallback per UI-SPEC line 180 / PATTERNS.md § DockBar line 326. The renderless TabGroup did not offer a cleaner per-cell shape than direct semantics modifiers. hasRoute(*Graph::class)worked? Yes — nav-compose 2.9.2 exposesNavDestination.Companion.hasRouteandNavDestination.Companion.hierarchy. No string-route fallback was needed; iOS K/N compile + linkDebugFrameworkIosSimulatorArm64 both green.- Touch targets: Code-level confirmation — DockTabCell uses
defaultMinSize(minWidth = 44.dp, minHeight = 44.dp), CollapsedDockToggle issize(44.dp), FloatingSearchButton issize(44.dp). Visual sim confirmation deferred to plan 02.1-08's manual smoke (V-09 / V-11 in VALIDATION.md).
Deviations from Plan
None — plan executed exactly as written. The plan's "Implementation note 1" pre-flagged an invalid clickableNoRipple sketch and recommended the MutableInteractionSource + clickable(indication = null) pattern, which is what the final code uses inline at each click site. No autoFix Rules 1–3 needed.
Verification
./gradlew :composeApp:compileKotlinIosSimulatorArm64 -q→ exits 0 (silent)../gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64 -q→ exits 0 (only an unrelated bundle-ID warning).- Material 3 boundary preserved: zero
androidx.compose.material3imports in any of the 4 new files. - Direct Liquid / Haze imports zero in
ui/screens/shell/andui/components/dock/. safeContentPadding()not present in AppShell.
Commits
| Task | Commit | Files |
|---|---|---|
| 1 — ShellViewModel | 5e0aaf9 |
ShellViewModel.kt |
| 2 — DockBar + FloatingSearchButton | 78bb90d |
DockBar.kt, FloatingSearchButton.kt |
| 3 — AppShell | fb4301e |
AppShell.kt |
Self-Check: PASSED
- Files exist:
- FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shell/ShellViewModel.kt
- FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shell/AppShell.kt
- FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/components/dock/DockBar.kt
- FOUND: composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/components/dock/FloatingSearchButton.kt
- Commits exist: FOUND 5e0aaf9, 78bb90d, fb4301e.
- iOS compile + link both green.