Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

11 - Design System Mapping (Houses Module)

Maps the Houses module onto the shared design system: tokens in 00-shared/02 (Design Tokens) and system mapping in 00-shared/04 (Design System Mapping). The critical module problem: color is a free string stored on the server (house.schema.ts:15-16) - the client must map it to design tokens safely.


1. Token mapping (from 00-shared/02)

House fieldDesign tokenNotes
nametext/display (detail) / text/title (card)typography scale
codetext/label chipmono-ish? no - standard label, uppercase
mottotext/body italic, color/text-muted-
colour surfaceshouse palette → color/primary-adjacent swatch setsee §3
destructive (delete)color/errorDeleteHouseDialog CTA
offline / conflictcolor/warning bannersshared AppOfflineBanner

2. Component → token wiring (from 00-shared/03)

Widget (07)Token bindings
HouseColorCardswatch strip = resolved house colour; text onSurface; chip surface-variant
HouseHeaderbanner = house colour; text onPrimary over scrim
MemberTileavatar hue derived from admission number (deterministic, not house colour)
ColorPickerField8 preset swatches from house palette (see §4)

3. House colour resolution (critical path)

Server stores any string (house.schema.ts:15-16); the client must never trust it as a valid Color. Resolver order:

  1. Exact match against a known palette table (name or hex) → token colour.
  2. Hex parse (#RRGGBB, #RRGGBBAA) → validated; fall through on failure.
  3. Unknown/named colour not in table → deterministic fallback: hash of code picks a token from the house palette (stable per house, so lists don't shift).
  4. Missing color → neutral token (surface-variant).
Color resolveHouseColor(String code, String? color) {
  if (color != null) {
    final hex = _tryParseHex(color);
    if (hex != null) return hex;
    final named = _palette[color.toLowerCase()];
    if (named != null) return named;
  }
  return _palette.values.elementAt(code.hashCode % _palette.length); // stable fallback
}
  • Contrast: banner text is always onPrimary over a 24% black scrim - arbitrary user colours pass AA by construction (00-shared/09).

4. Preset palette (8 swatches for ColorPickerField)

NameHexContrast vs white text
Crimson#BA1A1AAA+
Royal Blue#0B57D0AA+
Emerald#146C2EAA+
Gold#8A5A00AA+
Purple#5B21B6AA+
Teal#00695CAA+
Maroon#7B1E3BAA+
Slate#455A64AA+

All swatches resolve through the same resolver (§3) so a stored hex round-trips identically. QA: contrast matrix test in 14 §6.

5. Typography & spacing

ElementToken
Card nametext/title-medium, weight 600
Card mottotext/body-small, 1-line ellipsis
Detail nametext/display-small
Code chiptext/label-large, letter-spacing 0.5
Members headertext/title-medium + count AppBadge
Grid guttersspace/16; card padding space/16; banner height 180

6. Motion tokens

ElementToken (00-shared/08)
Card staggerm-fast (150 ms, 40 ms stagger)
Sheet/dialogm-base (320 ms, easeOut)
Swatch selectm-fast (120 ms)
Hero (swatch → banner)m-base

7. Dark mode & theming

  • House colours are brand colours: used identically in light/dark; the scrim keeps AA on both (00-shared/02 dark tokens).
  • Fallback neutral in dark = surface-variant-dark.
  • Golden test for the 8 presets × light/dark (00-shared/10 §6).