Skip to main content

Rewhaven Design System — Resolved Decisions (2026-06-17)

Captured from a grilling session before building the design_system components. These are binding for the build. They sit on top of the existing token foundation (ColorTokens dark+light from the marketsite OKLCH palette, MotionTokens spring/glide, DsTheme ThemeExtension accessor) and the spec's design guidance (§6 "tokens-first component library… calm, accessible, emoji-forward"; §7 a11y NFRs).

The seven decisions

1. Identity — "Same soul, app-tuned"

The app carries rewhaven.com's palette, motion curves, and type personality so site→app feels like one brand — but the scales are app-tuned: tighter spacing, functional list density, smaller display type than the marketing site. We do NOT copy the site's airy spacing/type scales. Calm comes from the palette, spacing rhythm, and motion restraint — chosen deliberately, not inherited.

Build the foundational atoms + the gallery now. App-specific molecules (chore row, wallet card, today tile — anything bound to a domain model) get built in the app when the screen that needs them is built, then promoted into the DS once proven. Rationale: unlike the data domain (knowable up front), UI molecules are discovered from real screens; building them speculatively calcifies wrong assumptions (POC trap #4 — components built ahead of screens, then 17 pages bent around them).

Architectural consequence: design_system stays model-agnostic — it has no dependency on client_sdk. Atoms take primitive props (strings, callbacks, variant enums). Domain-bound molecules live in the app first.

Atoms vs molecules (refined 2026-06-17)

The DS layers as atoms/ · molecules/ · templates/. It contains atoms (single, indivisible token-styled primitives — DsTextField, DsIconButton) AND generic UI molecules: compositions of two or more atoms with no domain binding (no client_sdk model, no app strings). The three generic identity molecules are:

  • DsPasswordFieldDsTextField + reveal toggle DsIconButton.
  • DsUsernameFieldDsTextField + leading person icon + clear DsIconButton; username autofill ([AutofillHints.username]), plain-text keyboard.
  • DsEmailFieldDsTextField + leading mail icon + clear DsIconButton; email autofill ([AutofillHints.email]), email keyboard.

Username and email are deliberately distinct molecules: on a sign-up screen a member supplies BOTH (username != email), so each is its own field. (Earlier, sign-in mis-used DsUsernameField as the email field with [email, username] autofill + an email keyboard; that's now DsEmailField, and DsUsernameField is username-only.) These are reusable across any app and so belong in the DS now. Together the three identity molecules (DsEmailField, DsUsernameField, DsPasswordField) compose the sign-up, forgot-username (recover via email), and forgot-password (reset via email/username) flows — which is exactly why email and username are kept as distinct molecules. Domain molecules (model-bound — chore row, wallet card, today tile) are still built per-screen and promoted into the DS only once proven, exactly as decision 2 requires; this refinement does not relax that rule. (Concretely: DsPasswordField moved from atoms/molecules/ — it was mis-filed as an atom — and DsTextField gained proper leading/trailing slots so a 44px control sits inside its rounded border without clipping the corner.)

3. Theme default — Dark

Dark is the default everywhere (matches the marketsite). Light is fully supported as a toggle. Hard build requirement: because dark is default, the dark palette MUST clear WCAG 2.2 AA contrast for body text (light-on-dark is where legibility is hardest, and this audience is least forgiving of it). Verify contrast on the dark set as part of the build.

4. Typography — Split: brand display + hyperlegible body

  • Bricolage Grotesque — display/headings (brand personality, used sparingly).
  • Atkinson Hyperlegible — body, UI, and numerics (engineered to disambiguate letterforms for low vision; the app's audience is neurodivergent / often low-literacy kids). Use a tabular style for token amounts so columns align.

5. Accessibility — AA floor baked into every atom; Focus Mode reserved

Every interactive atom ships, from day one, with: ≥44px hit targets, semantic labels, visible focus indicators, honors reduced-motion (MediaQuery.disableAnimations), and AA-contrast tokens (incl. the dark set). A11y is the product thesis ("built for every kind of mind"), not a later pass.

"Never red" scope (clarified 2026-06-17): "never red" governs STATUS (good/waiting/almost — the scaffold philosophy, where a kid's undone chore is "waiting" grey-blue, never punitive red); FORM/VALIDATION errors DO use the soft error (red) token, per the western error convention.

Focus Mode (Tier-3): reserve the structure now — a third ColorTokens variant (high-contrast, low-stim) + a density flag on the theme — so the mode slots in later with no atom rewrite. Do NOT build Focus Mode screens/behavior yet. (Reserving a token set is cheap structure, not a speculative component — so this does not violate decision 2.)

6. Iconography — Two-track + bundled emoji font

  • Content/identity = emoji, rendered via a bundled Noto Color Emoji font so a given emoji (kid avatar, the chore/reward emoji a parent picks — already model data) looks identical on phone, tablet, and TV. Cross-device consistency matters for pre-literacy kids who recognize "the fox chore."
  • UI chrome = line icons (Lucide) for nav, buttons, affordances.
  • Rule: emoji = identity, icons = controls.

A Widgetbook app in gallery/ (non-member, own lockfile — like client_sdk/example). Provides knobs (live prop tweaking), theme switching (dark / light / Focus), and device frames (phone / tablet / TV).

Process (revised 2026-06-18 — Widgetbook leads, doesn't trail): the Widgetbook harness is built at the START of Slice 2, BEFORE the atoms — so every atom is validated against real Flutter rendering, never an HTML/mock stand-in. Each atom's definition-of-done is three artifacts: (1) the widget, (2) a Widgetbook story with knobs, (3) golden tests across dark / light / Focus — the CI-enforced regression gate (real Flutter PNG snapshots; a visual regression fails the build). The Widgetbook web build is deployed live (an always-current, browsable gallery — toggle themes/devices yourself) once the first atom batch exists; hosting TBD (home server like the POC app, or Cloudflare Pages). "Show me" from here on means a real render (golden output or a Widgetbook screenshot), never an HTML approximation.

Build implications

  • Tokens: finalize the app-tuned spacing scale (4px base, tighter than the site) and the split type scale (Bricolage display / Atkinson body + tabular numerics). Add the reserved Focus ColorTokens. Verify dark-set AA contrast.
  • Atoms: each interactive atom ships with the a11y floor + golden tests across themes + a Widgetbook story with knobs.

Finalized atom inventory (from the POC audit, 2026-06-17)

A read-only audit of the POC's component layer (which discovered the real set across ~17 screens) validated our draft (~11/14) and corrected it. Final set:

Token groups — SEVEN (the POC's draft mentioned only 4; do not drop the rest)

Color (role-based, light+dark, kidColor(i), spend/save/give, coin, good/waiting/almost — never harsh red) · Typography (carries no colour) · Spacing (4px base, s1..s12 — the single most-used token in the POC, 720 refs; get the scale right) · Radius (sm..pill) · Elevation (level1/2/3 + focus-ring, brightness-aware) · Motion (clamp to zero on reduced-motion) · Icon (emoji ignore tint / glyph tints to ink). Plus Breakpoints (phone 640 / tablet 1024) as a layout token.

Atoms to ship now (ordered by proven POC traffic)

  1. DsButton — primary/ghost/soft/danger; loading shimmer that ALSO swallows taps (anti-double-submit); icon/trailing; expand; ≥44px. (POC #1 atom, 43 files)
  2. DsCard — elevated lvl1/2, onTap, radius. (#2, 24 files)
  3. DsTextField — label, hint, errorText, suffix, obscure, onBlur. (#3, 19 files)
  4. DsSheet — modal bottom sheet (drag handle + title + scroll), theme-inject internally. (highest-value ADD — 13 files used the app's sheet, not the DS's)
  5. DsSegmented — form input AND in-page tabs. (under-weighted in draft; 9 files)
  6. DsTag (kinded) + DsStatusChip (good/waiting/almost dot) — split the draft's single chip; never red.
  7. DsAvatar + DsAvatarStack (overlap + ring; colour from kidColor).
  8. DsCoin + DsTokenPill (tabular amount, give/save/spend + coin colours) — the draft's DsTokenAmount, split in two.
  9. DsStepper (−/value/+, clamped, 44px) — NEW vs draft; heavy in editors.
  10. DsIconButton (44×44, required semantic label) — NEW vs draft; ubiquitous.
  11. DsCheck (off/partial/on tri-state — sub-step completion needs partial)
    • DsSwitch (pill).
  12. DsProgressBar (bar only; defer the ring until a screen needs it).
  13. DsSection (title + trailing action + dense) — the draft's DsSectionHeader.
  14. DsRow / DsListTile (generic leading·title·subtitle·trailing) — add deliberately; its ABSENCE in the POC caused 4+ near-duplicate domain rows.
  15. DsEmptyState (icon + title + body + action) — confirmed absent in the POC (screens improvised); add it.
  16. (optional, only if an early screen needs it) DsGateChip · DsBonusChip · DsBadge(count).

Template

DsAdaptiveScaffold — phone tabs / tablet rail / desktop sidebar (640/1024). Bring nearly verbatim from the POC's HBAdaptiveScaffold — its single best reuse.

Build PER-SCREEN, NOT in the DS (validates decision 2)

The POC pre-built these as DS molecules/organisms and the app used ~none of them, hand-rolling domain versions instead. So defer: all chore/wallet/goal/ ledger/approval/budget cards & rows, all editor-sheet form layouts, the subtask builder, and trait/color/settings widgets. The DS provides the container (DsSheet) + fields (DsTextField/DsStepper/DsSegmented); the form layout stays in the feature.

One structural mistake to NOT repeat

The POC ran two token sources (the DS tokens AND a parallel app/lib/outside/theme/ stack), forcing ugly double theme-injection. The rebuild keeps one token source: design_system's DsTheme.

Status of each decision

All seven are RESOLVED and binding. Decision 3 (dark default) was the one the user took against the initial recommendation (which was light-when-unset); its contrast consequence is captured above as a build requirement.