26 KiB
phase, slug, status, shadcn_initialized, preset, created, revised
| phase | slug | status | shadcn_initialized | preset | created | revised |
|---|---|---|---|---|---|---|
| 2.1 | app-shell-navigation-search-foundation | draft | false | not applicable | 2026-05-08 | 2026-05-08 |
Phase 2.1 — UI Design Contract
Visual and interaction contract for the App Shell, Navigation & Search Foundation. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
Stack note: This is a Kotlin Multiplatform + Compose Multiplatform mobile project (iOS-primary, Android secondary). shadcn is not applicable — the design system is built on Composables / Compose Unstyled primitives + a local
RecipeThemetoken scaffold + aGlassSurfaceprimitive backed by Liquid → Haze → flat fallback chain.
Design System
| Property | Value |
|---|---|
| Tool | none (Compose Multiplatform; shadcn is web-only) |
| Preset | not applicable |
| Component library | Composables / Compose Unstyled (renderless primitives, locally restyled by Recipe components) |
| Icon library | Compose Material Icons Outlined (androidx.compose.material:material-icons-extended) — Material Icons stays even though the visual layer leaves Material 3; outlined variants align with the calm Liquid-Glass aesthetic |
| Font | System default (FontFamily.Default) for v1; SF Pro on iOS / Roboto on Android via platform default. No custom font shipped this phase. Phase 10 may revisit. |
| Glass primitive | GlassSurface composable in ui/components/glass/, layered over Liquid (io.github.fletchmckee.liquid:liquid) → Haze (dev.chrisbanes.haze:haze) → flat translucent fallback |
| Theme entry | dev.ulfrx.recipe.ui.theme.RecipeTheme { content } providing a LocalRecipeColors, LocalRecipeTypography, LocalRecipeSpacing, LocalRecipeShapes, LocalRecipeGlass CompositionLocal set |
Material 3 boundary: Material 3 stays only as legacy auth-screen scaffolding (PostLoginPlaceholderScreen, login). New code in ui/screens/{planner,recipes,pantry,shopping} and ui/components/ MUST NOT introduce androidx.compose.material3.* imports. Use RecipeTheme tokens.
Spacing Scale
Declared values (all multiples of 4, all within the standard set {4, 8, 16, 24, 32, 48, 64}):
| Token | Value | Usage |
|---|---|---|
xs |
4dp | Icon-to-label gap inside dock pill; chip internal padding |
sm |
8dp | Compact inline spacing; gap between dock and floating search/action button; floating dock vertical offset above the bottom safe-area; dock vertical padding; inter-tab gap inside dock; empty-state icon-to-headline gap |
lg |
16dp | Default screen content padding; empty-state headline-to-subline gap; search pill horizontal padding |
xl |
24dp | Section padding; horizontal screen edge inset for empty-state body |
2xl |
32dp | Layout-level gaps; vertical breathing room above empty-state block |
3xl |
48dp | Large vertical separators (e.g. between top safe-area and an empty-state's icon when centered visually rather than mathematically) |
Revision note (revision 1, 2026-05-08): CONTEXT D-14 originally locked the scale as 4/8/12/16/24/32. The 12dp step (md) was retired during UI-SPEC verification because no usage in this phase required 12dp specifically — every prior 12dp reference was remapped to 8dp (tighter chrome read more like a native iOS dock cluster). The scale extends upward with 2xl (32dp) and 3xl (48dp) so empty-state vertical rhythm has expressive headroom. Re-introduce a 12dp token in a later phase if a real geometric need surfaces in execution; the rest of the system can absorb that without churn.
Exceptions:
- iOS safe-area insets are added on top of these tokens via
WindowInsets.safeContent— never hardcode status-bar or home-indicator padding. - Touch target minimum: 44dp on iOS, 48dp on Android. Dock tab cells and the floating search button MUST satisfy this even if visual padding is smaller — use a transparent expansion via
Modifier.minimumInteractiveComponentSize()or equivalent. - Dock geometry: 56dp expanded height, 44dp collapsed height. These are absolute pixel values driven by touch-target ergonomics, not spacing-scale tokens.
Typography
Four named text styles, two weights (Regular 400, Semibold 600). Use system default font family; let the platform pick SF Pro / Roboto.
| Role | Size | Weight | Line Height | Letter Spacing | Usage |
|---|---|---|---|---|---|
display |
28sp | 600 (Semibold) | 1.2 (≈34sp) | -0.2sp | Empty-state headline (the calm, anticipatory line) |
title |
20sp | 600 (Semibold) | 1.2 (≈24sp) | 0sp | Inline tab title at top of each screen body (no top app bar — D-04) |
body |
16sp | 400 (Regular) | 1.5 (≈24sp) | 0sp | Empty-state subline; search input value text; default screen body copy |
label |
13sp | 600 (Semibold) | 1.2 (≈16sp) | 0.1sp | Dock tab labels (always shown, both active + inactive — D-02); chip text |
Scale enforcement: No raw TextStyle(fontSize = ...) in screen code. All text styles come from RecipeTheme.typography.{display,title,body,label}. The title role is the only header style this phase ships — there is no headline / h1..h6 cascade because there's no top app bar (D-04) and screens don't yet have multi-level content hierarchy.
Polish-language readiness:
- All four roles must render Polish diacritics (ą, ć, ę, ł, ń, ó, ś, ź, ż) without clipping. Line-height ratios above (1.2 / 1.5) leave headroom for
ąandŻaccents. - Long Polish tab labels constrain the
labelrole:Spiżarniais the longest (9 chars including diacritic). Dock label cells must accommodate this without truncation at default font scale; with system font scaling at 1.3× the dock may compress label visibility (active-only) — this is acceptable in v1 and revisited in Phase 10.
Color
Light + dark schemes are both defined this phase (CONTEXT D-15) and follow the system setting. The mockup palette is reference, not ported. Tokens are exposed as semantic roles (CONTEXT D-14), never raw hex in screen code.
Semantic roles (60/30/10 + supporting)
| Role | Light value | Dark value | Usage (60/30/10 mapping) |
|---|---|---|---|
background |
#F7F5F1 (warm off-white) |
#0F1113 (near-black warm) |
Dominant 60% — full-screen background behind every tab |
surface |
#FFFFFF |
#1A1D21 |
Secondary 30% — solid card / sheet / search-pill substrate when glass is unavailable (flat fallback) |
surfaceGlass |
#FFFFFF @ 60% alpha |
#1A1D21 @ 55% alpha |
Tint layer composited inside GlassSurface (dock, search pill, floating action button); the Liquid/Haze blur reads through this |
content |
#0F1113 |
#F1EFEA |
Primary text on background and surface |
contentMuted |
#6B6E73 |
#9AA0A6 |
Empty-state subline, inactive tab label, secondary captions |
accent |
#D97757 (warm terracotta) |
#E48A6E |
Accent 10% — see "Accent reserved for" below |
separator |
#E5E1DA |
#2A2D31 |
Hairline dividers (1dp); inter-tab separators inside dock if used |
borderCard |
#E5E1DA @ 60% alpha |
#FFFFFF @ 8% alpha |
Outline on glass surfaces (dock, search pill) for depth in light mode and edge clarity in dark mode |
destructive |
#C0392B |
#E57368 |
Reserved — no destructive actions exist in this phase, but the token is declared so feature phases (sign-out confirmation, plan-entry deletion) inherit it |
Accent reserved for
The accent color (warm terracotta, 10% of pixel real estate target) is used only for:
- Active dock tab — the wider, emphasized active tab cell uses
accentat full opacity for its icon + label color, on asurfaceGlasssubstrate. Inactive tabs usecontentMuted. - Search input caret + selection highlight — the cursor in the open search pill, and any text-selection range.
Accent is NOT used for:
- Dividers, borders, separators
- Empty-state icons (those use
contentMutedper D-10 — calm, low-saturation) - The dock substrate itself (that is
surfaceGlass, notaccent) - Standard body text
This list is exhaustive for this phase. Future phases extend it — primary CTA buttons (Phase 5+), shopping-list checked items (Phase 9), etc.
60/30/10 audit (this phase only)
- 60%
background— yes; the four tab screens are predominantly empty (empty states), so the warm off-white / near-black background dominates. - 30%
surface/surfaceGlass— yes; the dock pill, the floating search button, and the search pill are the only substantial non-background surfaces in the shell. - 10%
accent— yes; only the active tab and the search caret carry accent. Quantitatively below 10%, which is correct for a calm shell.
Copywriting Contract
All strings go through Compose Resources (composeResources/values/strings.xml or per-locale equivalents). No literal Polish strings in .kt files. Resource keys are namespaced by feature: shell_*, empty_*, search_*. Polish copy is the v1 ship language; the resource catalog is multi-locale-ready for Phase 11.
Tab labels (CONTEXT D-03 — order: Planer, Przepisy, Spiżarnia, Zakupy)
| Resource key | Polish copy | English placeholder (not shipped) |
|---|---|---|
shell_tab_planner |
Planer |
Planner |
shell_tab_recipes |
Przepisy |
Recipes |
shell_tab_pantry |
Spiżarnia |
Pantry |
shell_tab_shopping |
Zakupy |
Shopping |
Empty states (CONTEXT D-10, D-11 — anticipatory tone, icon + headline + subline, no CTA)
| Tab | Icon (Material Outlined) | Headline (display) | Subline (body) |
|---|---|---|---|
| Planer | Icons.Outlined.CalendarMonth |
Twój plan tygodnia czeka |
Wkrótce zobaczysz tu zaplanowane posiłki. |
| Przepisy | Icons.Outlined.MenuBook |
Tu pojawi się Twoja książka kucharska |
Po dodaniu pierwszych przepisów zobaczysz je w tym miejscu. |
| Spiżarnia | Icons.Outlined.Inventory2 |
Spiżarnia jest jeszcze pusta |
Wkrótce zobaczysz tu wszystko, co masz pod ręką. |
| Zakupy | Icons.Outlined.ShoppingCart |
Lista zakupów czeka na Twój plan |
Gdy zaplanujesz tydzień, zobaczysz tu, czego brakuje. |
Resource keys: empty_planner_title / empty_planner_subtitle, empty_recipes_title / empty_recipes_subtitle, empty_pantry_title / empty_pantry_subtitle, empty_shopping_title / empty_shopping_subtitle.
Tone rules:
- Forward-looking: "Wkrótce", "Po dodaniu", "Gdy zaplanujesz" — signal the feature is real, not broken.
- No "Brak danych", no chatty onboarding ("Witaj!"), no exclamation marks.
- Subline ends with a period. Headline does not.
- No CTA buttons (CONTEXT D-12). The
EmptyStatecomposable'sactionslot is reserved unused this phase (D-13).
Phase 11 caveat: copy may be tuned during the localization pass. Resource keys above are the contract; copy strings are best-current.
Search affordance (CONTEXT D-06 through D-09)
| Resource key | Polish copy | Purpose |
|---|---|---|
search_open_a11y |
Otwórz wyszukiwanie |
Content description for the floating search-icon button (icon-only) |
search_close_a11y |
Zamknij wyszukiwanie |
Content description for the collapsed dock toggle when search is open (D-05) |
search_clear_a11y |
Wyczyść |
Content description for the clear button inside the search pill (visible when query is non-empty) |
search_placeholder_recipes |
Szukaj przepisów… |
Search pill placeholder on Przepisy tab |
search_placeholder_pantry |
Szukaj w spiżarni… |
Search pill placeholder on Spiżarnia tab |
Search body content: none (CONTEXT D-07). No "no results" copy this phase. Phase 5 wires real result rendering. Empty SearchSurface body renders an empty Box matched to background.
Error / sign-out (out of scope for this phase but tokens reserved)
This phase introduces no error surfaces (auth errors are Phase 2 territory; sync errors are Phase 4+) and no destructive actions. The destructive color and a future confirm_signout_* resource family are NOT defined here — they ship with their owning phase.
CTA / primary action
This phase has no primary CTA button. The shell is navigation chrome and empty surfaces. The accent color contract above declares accent reservation; the first real primary CTA ships in Phase 5 (recipe browse).
Component Inventory (this phase)
Composables introduced in composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/:
| Composable | Path | Built on | Visual contract |
|---|---|---|---|
RecipeTheme |
ui/theme/RecipeTheme.kt |
CompositionLocal scaffold | Provides RecipeColors, RecipeTypography, RecipeSpacing, RecipeShapes, RecipeGlass to descendants |
GlassSurface |
ui/components/glass/GlassSurface.kt |
Liquid → Haze → flat | Single primitive consumed by dock, search pill, floating buttons. Same token API across all three backends (color, opacity, radius). Compile-time backend selection per target; debug-build runtime toggle (CONTEXT D-16, D-17) |
AppShell |
ui/screens/shell/AppShell.kt |
Compose Unstyled Scaffold-equivalent |
Auth-gated root: hosts root NavHost + the bottom dock + the floating search/action surface. Renders background color edge-to-edge under safe-area insets. |
DockBar |
ui/components/dock/DockBar.kt |
Compose Unstyled TabGroup-equivalent + GlassSurface |
Floating bottom pill, 4 tabs (icon + label always — D-02), active tab wider with accent foreground; collapses to single circular icon-only toggle when searchOpen == true (D-05). Capsule shape: full-pill (height/2 corner radius). Height: 56dp; collapsed height: 44dp. |
FloatingSearchButton |
ui/components/dock/FloatingSearchButton.kt |
Compose Unstyled Button + GlassSurface |
44dp circular glass button, search icon (Icons.Outlined.Search) tinted content. Adjacent to dock with sm (8dp) gap. Visible only on Przepisy + Spiżarnia tabs (D-06). Hidden when searchOpen == true. |
SearchPill |
ui/components/search/SearchPill.kt |
Compose Unstyled TextField (renderless) + GlassSurface |
Inline bottom search pill (D-09). Capsule shape. Holds: leading search icon, text input (placeholder per tab), trailing clear button (visible when query non-empty). Substrate: surfaceGlass. Body content behind it stays visible. Height: 44dp. |
EmptyState |
ui/components/empty/EmptyState.kt |
Plain Compose | Reusable EmptyState(icon: ImageVector, title: String, subtitle: String, action: (@Composable () -> Unit)? = null) — D-13. Vertical center on screen. Icon 48dp tinted contentMuted. Spacing: icon → 8dp (sm) → headline (display) → 16dp (lg) → subline (body, color contentMuted). action slot is below subline at 24dp (xl) gap when present; unused this phase. |
Screen scaffolds |
ui/screens/{planner,recipes,pantry,shopping}/{Tab}Screen.kt |
RecipeTheme + EmptyState |
Each: inline tab title at top in title style + lg padding, then centered EmptyState. Background: RecipeColors.background. |
Renderless primitive boundary: Where Compose Unstyled provides a renderless primitive (button, text field, tab group), Recipe components MUST consume it and apply local styling, not implement the gesture/a11y semantics from scratch. This is the explicit project decision (PROJECT.md § Components: Composables / Compose Unstyled).
Interaction Contracts
Dock state machine (CONTEXT D-05)
States:
Expanded— default. 4-tab pill, all icons + labels visible, active tab wider withaccentforeground.Collapsed— whensearchOpen == true. Single circular cell showing only the active tab's icon, no label, height 44dp (vs 56dp expanded).
Transition: single coordinated animation (not two independent ones — explicit user intent in CONTEXT specifics). Suggested duration: 250ms with a standard easing (e.g. FastOutSlowInEasing); planner picks final curves and Phase 10 tunes on real device.
Tapping the collapsed dock = setSearchOpen(false) = re-expand + close search.
Search affordance (CONTEXT D-06 through D-09)
- Visible only on
Przepisy+Spiżarniatabs. FloatingSearchButtontap →searchOpen = true→SearchPillslides up / fades in,DockBarcollapses,FloatingSearchButtonhides. Coordinated with the dock-collapse animation as one motion.- Closing: tap collapsed dock OR system back gesture →
searchOpen = falseANDquery = ""(D-08). Re-opening starts blank. - Query state lives in the per-tab
SearchViewModel(one for Recipes, one for Pantry); no persistence across close, tab-switch, or app launch. - Body of search surface: renders nothing this phase (D-07). The
SearchPilloverlays the existing tab body; the body remains visible behind it.
Tab navigation (UI-03 / CONTEXT D-03)
- Default landing tab on first sign-in:
Planer(D-03 — departs from REQ listing order, which research confirmed non-binding). - Tab order in dock (left→right): Planer / Przepisy / Spiżarnia / Zakupy.
- Each tab owns an independent nested
NavHost(CONTEXT D-03 + research ARCHITECTURE recommendation), so future detail screens preserve back stacks per tab. - Tab switch preserves the destination tab's back stack; selecting an already-active tab pops to its root (standard mobile pattern).
- No tab-bar hide-on-scroll behavior this phase (deferred — CONTEXT § Deferred).
Accessibility
- Each dock tab cell:
Modifier.semantics { role = Role.Tab; selected = isActive; contentDescription = "$tabLabel${if (isActive) ", aktywna" else ""}" }. FloatingSearchButton:contentDescription = stringResource(Res.string.search_open_a11y).- Collapsed dock toggle:
contentDescription = stringResource(Res.string.search_close_a11y). - Search pill clear button:
contentDescription = stringResource(Res.string.search_clear_a11y); visible only when query is non-empty. - Touch targets: dock tab cells and the floating search button MUST be ≥ 44dp on iOS, ≥ 48dp on Android.
- Focus order when search opens: search input field receives focus on open; soft keyboard appears; the collapsed dock toggle is in the tab order after the clear button.
- Empty-state regions:
Modifier.semantics(mergeDescendants = true) { ... }so VoiceOver reads the headline + subline as one announcement, not two.
Glass / Liquid contract
GlassSurface is the only entry point to glass effects this phase. Direct calls to Liquid or Haze APIs from screen code are forbidden — those only live inside GlassSurface's internal backend selection.
Backend selection
| Backend | When engaged | Notes |
|---|---|---|
| Liquid | Default on iOS + Android where Liquid 1.1.x compiles cleanly for the target | Pixel-sampling refractive approximation; matches PROJECT decision and CLAUDE.md convention #10 |
| Haze | Compile-time fallback if Liquid does not ship for a target, OR runtime debug-toggle override | Plain blur; no refraction |
| Flat | Compile-time fallback if neither Liquid nor Haze is available, OR debug-toggle override | Solid translucent surface using surfaceGlass token; no blur |
Selection mechanism (CONTEXT D-17):
- Compile-time per target: the build picks the backend at build time. No runtime branch in production binaries.
- Runtime debug toggle (debug builds only): stored via
multiplatform-settings, surfaced through a hidden settings entry or build flag. Lets the developer switch backends on-device for visual comparison.
Surface parameters
The dock, search pill, and floating search button all consume the same token API:
| Parameter | Value | Notes |
|---|---|---|
| Tint color | surfaceGlass (light: white@60%, dark: dark@55%) |
Composited inside the glass effect |
| Corner radius | 28dp for the dock pill (full-pill at 56dp height); 22dp for the collapsed dock toggle (full-pill at 44dp); 22dp for the search pill (full-pill at 44dp); 22dp for the floating search button (full-circle at 44dp) | All chrome elements are pill / circle, never rectangular |
| Border | 1dp borderCard outline |
Provides edge clarity especially in dark mode |
| Elevation / shadow | Soft drop shadow: y-offset 8dp, blur 24dp, opacity 12% in light mode; opacity 0% (no shadow, just border) in dark mode | Applied via Modifier.shadow() outside the glass clip |
| Blur radius (Liquid + Haze) | Initial value: 24dp. Phase 10 tunes on real device. Planner may pick library-specific equivalent. | |
| Refraction (Liquid only) | Library default initially; tune in Phase 10. |
Chrome-only constraint (CLAUDE.md #10 + PITFALLS Pitfall 5): Glass surfaces are applied to dock, search pill, and floating search button only. NEVER over scrolling content. The empty-state area, tab body, and any future list rows are flat — no GlassSurface wraps them.
Fallback test plan (informational)
Each backend must render visually distinct but functionally identical chrome. Acceptance: switching the debug toggle between Liquid / Haze / flat keeps the dock, search pill, and floating button in the same geometry, with the same content positioning, only the substrate effect changes.
Layout & Safe Area
- Root container: full-screen, edge-to-edge.
WindowInsets.statusBarsis consumed by tab body content (top inset added to the inline tab title's top padding).WindowInsets.navigationBars+ iOS home-indicator inset are consumed by the dock's bottom offset. - The dock floats
sm(8dp) above the bottom safe-area inset. The search pill and floating search button sit at the same vertical baseline as the dock when active. - iOS keyboard avoidance: when the search input has focus, the search pill animates above the soft keyboard via
imeAnimationSource/imePadding(). The dock's collapsed toggle rides up with it (single coordinated motion). - No top app bar (D-04). The inline tab title sits at the top of each screen body with
xl(24dp) top padding above the status-bar inset, thenlg(16dp) below before screen content (or before the empty-state vertical centering region).
Registry Safety
| Registry | Blocks Used | Safety Gate |
|---|---|---|
| shadcn official | none — not applicable (Compose Multiplatform stack) | not required |
Compose Unstyled (composables.com) |
renderless primitives (Button, TextField, TabGroup-equivalent) — locally restyled by Recipe components | not required (first-party renderless library; no third-party code lifted into the project) |
Liquid (io.github.fletchmckee.liquid:liquid) |
consumed as a Gradle dependency, not as copied source | dependency review passed — date 2026-05-08; no source code lifted |
Haze (dev.chrisbanes.haze:haze) |
consumed as a Gradle dependency, not as copied source | dependency review passed — date 2026-05-08; no source code lifted |
No third-party shadcn registries declared. No source-code blocks vended into the repo. Standard Gradle dependency review applies.
Out-of-Scope Boundaries (this UI-SPEC)
These intentionally have no contract here and are owned by later phases:
- Recipe list rendering, grid spec, card style — Phase 5
- Real planner grid, day cells, slot cells — Phase 6
- Pantry inventory rows, category headers — Phase 8
- Shopping list rows, checked-state styling, category groupings — Phase 9
- Theme polish (final color palette tuning, custom font) — Phase 10
- Animation curves and durations beyond the dock-collapse 250ms default — Phase 10 tunes on real device
- Real-device Liquid parameter tuning (refraction strength, specular highlights) — Phase 10
- Polish copy final pass — Phase 11
- Profile / settings / sign-out chrome placement — Phase 3 onward (no top bar exists yet — D-04)
Pre-Population Audit
| Field | Source |
|---|---|
| Tab order, default landing | CONTEXT D-03 |
| Tab labels (Polish) | CONTEXT D-03 + REQUIREMENTS UI-03 |
| Dock shape, label visibility | CONTEXT D-01, D-02 |
| Top app bar absence | CONTEXT D-04 |
| Dock-collapse-on-search transition | CONTEXT D-05 + user verbatim |
| Search affordance scope (which tabs) | CONTEXT D-06 |
| Search behavior this phase | CONTEXT D-07, D-08, D-09 |
| Empty-state pattern + tone + no CTA | CONTEXT D-10, D-11, D-12 |
EmptyState composable signature |
CONTEXT D-13 |
| Theme scaffold scope | CONTEXT D-14 |
| Light + dark schemes | CONTEXT D-15 |
| GlassSurface fallback chain | CONTEXT D-16 |
| Compile-time + debug toggle | CONTEXT D-17 |
| Compose Unstyled foundation | PROJECT.md Key Decisions + CLAUDE.md tech stack |
| Liquid first / Haze fallback | PROJECT.md + CLAUDE.md #10 |
| Strings externalized | CLAUDE.md #9 + REQUIREMENTS UI-01 |
| Material 3 boundary | PROJECT.md + CONTEXT discretion default |
| Material Icons Outlined | CONTEXT discretion default |
| Spacing scale 4/8/16/24/32/48 | CONTEXT D-14 (12dp step retired during UI-SPEC verification — see Spacing § Revision note) |
| Typography 4 styles, 2 weights | gsd-ui-researcher recommendation aligned with CONTEXT D-14 named scale |
| Color hex values | gsd-ui-researcher recommendation (mockup is reference, not ported — CONTEXT D-15) |
| Empty-state copy strings | gsd-ui-researcher recommendation; subject to Phase 11 copy pass |
| Touch target minimums | iOS HIG / Material guidelines + accessibility default |
| 250ms transition duration | gsd-ui-researcher reasonable default; CONTEXT discretion + Phase 10 tunes |
No user questions asked this round — CONTEXT.md, PROJECT.md, REQUIREMENTS.md, and CLAUDE.md collectively answered every load-bearing decision. Discretionary defaults (color hex values, typography sizes, copy strings, animation duration) are recorded above and revisitable in Phase 10/11.
Checker Sign-Off
- Dimension 1 Copywriting: PASS
- Dimension 2 Visuals: PASS
- Dimension 3 Color: PASS
- Dimension 4 Typography: PASS
- Dimension 5 Spacing: PASS
- Dimension 6 Registry Safety: PASS
Approval: pending