Used `BasicText` from compose-foundation rather than Material 3 `Text` to keep shell components Material-3-free per UI-SPEC line 31
Tab screens render inline title + centered EmptyState; chrome bottom inset is owned by AppShell, not screens
All 4 tab VMs ship a marker `isEmpty` field for forward-compatible expansion in feature phases (5/6/8/9)
duration
completed
~10m
2026-05-08
UI-09
Phase 02.1 Plan 07: Tab Empty States Summary
UI-09 anticipatory empty states: a reusable EmptyState(icon, title, subtitle, modifier, action?) composable plus four tab screens (Planner / Recipes / Pantry / Shopping) each rendering an inline title and a centered EmptyState with calm Polish copy from UI-SPEC § Copywriting Contract.
What was built
EmptyState.kt — reusable centered Column with 48dp muted icon, display headline, body subline, optional action slot, wrapped in Modifier.semantics(mergeDescendants = true) {} so VoiceOver reads the empty state as a single announcement (UI-SPEC line 226).
4 tab *Screen.kt files — each Box(background = RecipeTheme.colors.background) containing a Column with status-bar inset + xl top padding, inline tab title in RecipeTheme.typography.title, and a centered EmptyState reading the tab-specific icon (from BottomBarDestination.<Tab>.icon) and resource strings.
4 tab *ViewModel.kt files — each ViewModel exposes a state: StateFlow<*State> with a marker isEmpty: Boolean = true field; no actions in this phase.
strings.xml extended with 8 empty-state keys (Polish copy verbatim from UI-SPEC § Copywriting Contract).
Tasks & commits
Task
Commit
Description
1
1cc4d9d
Add 8 empty-state strings (Polish copy)
2
98baed9
Add reusable EmptyState composable
3
fda8d2a
Add 4 tab ViewModels (StateFlow, no actions)
4
c0ca16c
Add 4 tab screens with inline title + EmptyState
Verification
./gradlew :composeApp:compileKotlinIosSimulatorArm64 -q — exit 0 after each task
./gradlew :composeApp:generateComposeResClass -q — exit 0; new Res.string.empty_* accessors generated
Material 3 boundary preserved: grep -rc 'androidx.compose.material3' [9 new files] returns 0
Zero hardcoded Polish literals in any *.kt — every string flows through stringResource(Res.string.*)
Spacing accessor names verified
RecipeSpacing exposes: xs (4dp), sm (8dp), lg (16dp), xl (24dp), xxl (32dp), xxxl (48dp). Per RecipeSpacing.kt comment: UI-SPEC's 2xl / 3xl are remapped to xxl / xxxl because Kotlin identifiers cannot start with a digit. This plan uses only sm, lg, xl — all plain identifiers, no backticks needed.