Spec: F19 — Design System & Responsive UI
Status: shipped · Audited: 2026-07-14 · Contract audit: partial Canonical specs: DESIGN.md (tokens, components, surfaces) + TASTE.md (anti-slop rules). This file only fixes the product-level UX bar; it does not duplicate them.
1. Problem & CEO Job
All three jobs depend on it. The product UX bar is “don’t make me think”: Vietnamese SME CEOs and warehouse staff must use Lumi with zero training. Consistency delivers it: one severity color language, button language, and loading pattern, learned once and valid everywhere.
2. Product-level UX Invariants
These bind every feature spec (F01–F25) without being repeated in each:
- 5am test — CEO-facing screens (Brief, Today) readable half-awake on a phone.
- One required field — every create flow starts with a single field (name/title); everything else is progressive disclosure.
- Skeletons, not spinners — layout visible immediately; spinners only full-page.
- Empty states teach — every empty state says what this screen will do and offers the first action. Blank pages are bugs.
- Positive silence — empty risk/decision lists and 0-item briefs render as good news, never as dead ends.
- Errors are actionable — name the fix (“kiểm tra kết nối mạng”), never just the failure.
- One severity palette — low/medium/high/critical map to the same colors on tasks, risks, commitments, brief items.
- Never lose typed text — failed submits preserve input (pulse answers above all).
- Mobile-first for member surfaces (pulse), phone-capable for everything.
3. Scope Boundaries
- In: shadcn/ui (base-nova) exclusively, CSS-variable tokens, Tailwind v4 (CSS config), responsive layout, the invariants above.
- Out: dark mode (until DESIGN.md defines it intentionally), theming/white-label, animation flourish that doesn’t clarify flow.
4. Data Model
n/a — frontend only.
5. API Contract
n/a — frontend only.
6. UI / UX
See DESIGN.md (what to use) and TASTE.md (what never to ship). Component checklist before any new UI: does shadcn cover it (frontend/src/components/ui/)? Does it pass the TASTE.md pre-flight?
7. Test Plan
Detailed proof matrix: F19 design-system test plan.
| # | Case | Type | Where |
|---|---|---|---|
| 1 | Visual audit, 55+ shots across all features | e2e | screenshot-flow.spec.ts → frontend/screenshots/index.html |
| 2 | Type safety | unit | tsc --noEmit |
| 3 | Raw-Tailwind-for-UI violations | review | merge-blocking rule 4 |
| 4 | Canonical web tokens and banned escapes | unit | design-tokens.test.ts; check-design-tokens.mjs |
| 5 | Shared severity language | unit | StatusBadge.test.tsx |
| 6 | Android app paper token matches #F4F0E8 | Android JVM | MobilePrimitivesTest |
| 7 | Vietnamese CEO surfaces at 375px and 768px | browser | mobile-screenshot-flow.spec.ts; currently blocked by unavailable Chromium revision 1223 |
8. Current Behavior
Shipped as an operating contract. The design system exists through DESIGN.md, TASTE.md, shared
UI components, native token mirrors, and screenshot-driven review. The 2026-07-14 contract audit
found and corrected Android’s stale PaperWhite value with a red-then-green JVM regression. Focused
web token and severity proofs pass locally. Browser journeys collect but did not launch because
Chromium revision 1223 is unavailable; native runtime and maintained visual-diff evidence remain
explicitly blocked.
9. Error States
| # | Error | Response | Notes |
|---|---|---|---|
| ES1 | shadcn/ui import fails at runtime | Entire rendering tree collapses; no per-component fallback | No E2E catches a broken import chain |
| ES2 | Screenshot flow visual diff failure | Screenshots generated but no baseline diff; regression is human-detected only | No automated visual regression testing |
| ES3 | Missing CSS variable for a token | Browser uses inherited/unset color; element may be invisible | No test verifies all referenced CSS variables are defined |
| ES4 | DESIGN.md vs CSS token set drift | Two PRs modifying token block can produce doc ↔ code mismatch | No automated sync check |
| ES5 | Screenshot flow missing file in gallery | Gallery links to missing file if screenshot not captured (test failed before snap) | test.afterAll runs regardless; no guard for missing files |
10. Diagrams & Docs Touched
DESIGN.md · TASTE.md · frontend/AGENTS.md → UI Rules.
11. Implementation Audit (2026-06-10)
| # | Claim | Status | Evidence |
|---|---|---|---|
| 1 | shadcn/ui (base-nova) exclusively; CSS-variable tokens; Tailwind v4 CSS config | ✅ match | Operating contract; frontend/src/components/ui/; tsc --noEmit gate |
| 2 | 9 UX invariants enforced via 55+ screenshot shots + TASTE.md pre-flight in review | ⚠️ partial | screenshot-flow.spec.ts generates review material, but no maintained baseline/diff or current human sign-off proves all nine invariants. |
| 3 | Invariant 7: one severity palette — low/medium/high/critical consistent across tasks/risks/commitments/brief items | ✅ match | Closed 2026-07-06. StatusBadge’s statusToVariant() map is the single source of truth; StatusBadge.test.tsx now locks the severity language — the four levels map to four distinct canonical variants and aliases route to their level. Two surfaces that hand-rolled a divergent map (CompaniesPage had medium→grey; SupportListPage PRIORITY_VARIANT_MAP had medium→grey/low→outline) were migrated to statusToVariant. |
| 4 | Invariant 3: skeletons on all pages — no auto-detect of new pages without skeleton states | ⚠️ gap | E2; screenshot flow covers existing pages but can’t catch new pages missing skeletons. 2026-06-18 visual QC found 03-today screenshots could capture skeletons before the loaded page; screenshot flow now waits for [data-slot="skeleton"] to clear before snap. |
| 5 | Design token usage: brand colors hardcoded in some component files (bg-[#0E3EB8], text-[#102A43]) | ✅ match | Closed. frontend/scripts/check-design-tokens.mjs bans stock palette classes, raw/stock shadows, and arbitrary #hex in frontend/src; enforced in CI (ci-fe.yml) and by src/test/_shared/design-tokens.test.ts. |
| 6 | Screenshot flow single-width only; mobile invariants (5am test, mobile-first) not verified | ⚠️ proof blocked | Implementation closed 2026-07-06. tests/e2e/mobile-screenshot-flow.spec.ts defines phone (375) and tablet (768) journeys with horizontal-overflow assertions. The 2026-07-14 contract run could not launch either journey because Chromium revision 1223 is absent and the CDN install fails DNS; prior results are not current-commit browser evidence. |
| 7 | DESIGN.md and CSS token block can drift (no automated sync) | ⚠️ gap | ES4; two PRs modifying token block independently |
Summary: deterministic web token/severity checks and the Android token regression pass. Browser, native runtime, cross-platform parity, visual approval, and routed loading/empty/error-state inventory remain explicit CUE gaps; F19 is partial, not green.
Edge Cases Catalog (formerly §11, 2026-06-12; E1–E10): Key items integrated above. Remaining: E1 missing shadcn component for new pattern (governance gap, no lint rule); E3 empty state regressions (no content assertion); E7 Tailwind v4 @import path verification; E8 Base UI vs shadcn pattern mixing; E9 Lucide icon inconsistency; E10 “loaded empty” vs “not loaded” visually identical. Full catalog in git history (2026-06-12 changelog entry).
12. Open Questions
- How should one deterministic parity checker represent the canonical values shared by DESIGN.md, web, shared KMP, Android, iOS, macOS, and Watch without making generated artifacts authoritative?
- Which visual baselines require human approval, and what diff threshold avoids both noise and false-green screenshots?
- Which routed pages may legitimately fast-load without skeletons, and how will the inventory prove authored empty and actionable error states?
13. Bug / Change Log
2026-07-28 — Folded-L identity replaces the clarity-star lockup
Type: Cross-platform brand-system refresh
- Lumi now uses the supplied folded-L geometry across canonical web SVGs, Android adaptive and splash vectors, iOS/macOS app icons, Play listing artwork, and legacy Android launchers.
- Descriptively named source exports live under
brand/source/2026-folded-l/; exact duplicate exports were removed, andscripts/generate-brand-svg-assets.mjsowns canonical SVG generation plus thefrontend/public/mirrors. - The horizontal
umiwordmark is upright. Italic or oblique letter geometry is not a supported logo variant. - The folded-L to
uclear space is approximately 0.8× the wordmark stem width; excess separation that makes the mark and name read as unrelated elements is not a supported lockup. DESIGN.mdnow distinguishes the folded-L identity from the separate four-point clarity motif used sparingly for verified insight.
Proof boundary:
- Source identity, exact dimensions, digest custody, frontend compilation, and Android resource compilation are deterministic. Store rendering and named-device launcher appearance remain runtime/visual evidence, not consequences of source validation.
2026-07-19 — Native content and material hierarchy closes framework-default color leaks
Type: Mobile design-system regression fix
Acceptance contract:
- Shared KMP content that does not declare a more specific semantic color inherits Civic Navy, never Compose’s framework-black fallback.
- Android primary actions use Lumi Blue with white content; secondary and text actions use Lumi Blue on white or transparent surfaces.
- Android’s complete Material 3 light scheme maps every surface/tint role to a Lumi token, keeps the app canvas Paper White without tonal elevation, and uses the canonical 4/8/12/16dp shape ladder instead of stock purple or pill defaults.
- iOS native navigation titles use Civic Navy and navigation actions use Lumi Blue without replacing the platform’s Liquid Glass material.
- Semantic errors use Risk Red on both platforms. Pure black remains permitted only for system chrome, vector masks, and transient scrims.
Proof boundary:
- Deterministic policy and platform unit/build gates prove source ownership and compilation. Commit-bound simulator/emulator screenshots are required to assess rendered balance; named physical-device approval remains open.
2026-07-24 — Android splash owns background and icon separately
- Android 12+ treats the splash screen as a window and masks the supplied icon. The app now supplies
PaperWhite through
windowSplashScreenBackgroundand the Lumi vector directly throughwindowSplashScreenAnimatedIcon; it no longer supplies a square-background layer list as the icon. This preserves the system’s adaptive-icon masking boundary instead of letting it crop the composed asset or reveal a mismatched background. - The source policy proves the theme wiring.
F19.GAP002remains the named-device rendering proof.
2026-07-19 — Native mobile chrome restores Lumi Blue authority
Type: Mobile design-system regression fix
Summary:
- Android selected navigation had resolved through
onPrimaryContainer(Civic Navy), so the active tab looked black instead of carrying the canonical Lumi Blue authority signal. - iOS installed a custom transparent
UITabBarAppearancewithout assigning selected and inactive colors, allowing UIKit defaults to override the SwiftUI brand tint; the app AccentColor asset was also empty. - Selected navigation and key shell actions now use Lumi Blue, inactive navigation uses Soft Slate, and content remains Civic Navy on warm paper. This preserves DESIGN.md’s 3–8% blue budget instead of washing whole screens in brand color.
Proof boundary:
- Repository policy proof guards both native shells and the iOS accent asset. Named-device visual review at normal and 200% text remains required before claiming rendered parity or subjective visual approval.
2026-07-14 — Contract audit identifies Android paper-token drift
Type: Bug Fix / Verification
Summary:
- The canonical
PaperWhitevalue is#F4F0E8in DESIGN.md, web, shared KMP, and iOS, while the Android app design-system mirror used the superseded#FAF8F4value. - Added the test-first F19 proof matrix, captured the Android regression red, corrected the one token, and migrated the feature into the Contract Graph with explicit browser/native/visual gaps.
Impact:
- Android app surfaces now use the accepted cross-platform paper background. The registry is partial until remaining evidence closes.
Tests:
:core:designsystem:testDebugUnitTest --tests '*MobilePrimitivesTest*'failed against#FAF8F4before the correction and passes afterward.- Focused web token/severity Vitest proofs pass. The 375px/768px Playwright nodes collect but remain
blocked: Chromium revision 1223 is absent and its CDN install fails with
getaddrinfo ENOTFOUND cdn.playwright.dev.
2026-06-18 — Visual QC screenshot flow waits for loaded UI
Type: Test / QC Harness
Summary:
- Full frontend visual QC started by generating
frontend/screenshots/index.html. - Initial run exposed a real install-state blocker: Vite could not resolve declared font packages because
node_moduleswas stale. Reinstalled with lockfile-compatiblepnpm@9.15.9; no lockfile change intended. - The first successful screenshot run then exposed a harness race: Today screenshots captured skeletons before the loaded page.
screenshot-flow.spec.tsnow uses canonical auth routes, Vite overlay detection, onboarding-gate clearing for demo users, and waits for skeletons to clear before screenshots.
Impact:
- Screenshot evidence is now more trustworthy for page-by-page visual QC.
- Remaining F19 gaps still stand: automated visual diffing, mobile breakpoint matrix, and token drift enforcement.
2026-06-18 — Restore viewport-only screenshot support for sticky-shell pages
Type: Test / QC Harness
Summary:
- The screenshot helper had regressed to always using
fullPage: true, even when a flow requested a viewport-only capture. - This made scrolled screenshots of sticky-shell pages like
/settingslook visually broken because the shell/header was duplicated inside the capture. tests/e2e/snap.tsnow accepts an explicitfullPageoption again, so page flows can choose viewport captures where that produces truer evidence.
Impact:
- Visual QC screenshots for sticky layouts better match what users actually see.
2026-06-14 — Restructure to TEMPLATE.md section order; expand §11 audit (SP fleet)
Type: Documentation Change
Summary:
- Numbered “Current Behavior” as §8.
- §8 Diagrams → §10; §9 Implementation Audit (verdict) → §11 (7-row table); §10 Open Questions → §12; §11 Edge Cases absorbed into §11 note; §12 Error States → §9; §13 Bug Log stays.
- §11 audit: 7 rows, 2 ✅, 5 gaps (palette cross-feature, skeleton auto-detect, token hardcoding, mobile coverage, doc/CSS drift).
2026-06-12 — Add Edge Cases (E1–E10) and Error States (ES1–ES5) to F19 spec
Type: Documentation Change