design-RFC-007 — Token hierarchy (seed / map / alias)

draft

Surveys whether Verge should evolve from its current FLAT token model (`bg`, `surface`, `text`, `accent`, `border`, `focus` declared directly per preset) to a 3-tier hierarchy modeled on Ant Design — **seed tokens** (small input set), **map tokens** (derived by algorithm: light / dark / compact), **alias tokens** (semantic names consumed by components). Owner sign-off required to ratify; this RFC LAYS OUT the three options and trade-offs without picking one.

designRFC007 — Token hierarchy

*tatus* v0.1.0 Draft (20260522). Owner sign-off required before ratification. *mplementation deferred until ratified.*

R1 — Problem

Verge v0 uses *lat tokens*— every preset (canonicalPresets in internal/kinds/verge.go) declares the same handful of names (bg, surface, text, accent, border, focus) directly. The 44 presets × 2 modes (light / dark) = ~88 independent token sets, each hand-curated.

Properties:

Property Status
Easy to read a single preset
Easy to add a new preset ✅ — copy/paste then tune
Algorithmic theming (one seed → full palette)
Compact / spacious density swap
Cross-preset systematic invariants ❌ — manual discipline only
Drift detection between consumer products

The flat model *hipped Verge v0*intentionally — minimalism (v0 = Adwaita 1:1). The trade-off was that systematic theming, density modes, and consumerextension ergonomics were deferred. This RFC reopens that trade-off for Verge v1.

R2 — Prior art

Ant Design — explicit 3-tier

Seed:  colorPrimary = #1677ff, borderRadius = 6, fontSize = 14
  ↓ (algorithm: defaultAlgorithm | darkAlgorithm | compactAlgorithm)
Map:   colorBgContainer, colorBgElevated, colorBorderSecondary, ...
  ↓ (rename for semantics)
Alias: colorBgLayout, colorTextDescription, colorBgSpotlight, ...

Consumers consume *lias tokens* Theming = swap a few seeds. Dark mode = swap the algorithm. Compact = swap the algorithm. 5,000+ alias tokens generated from ~15 seeds.

Material 3 — partial hierarchy

Source HCT hue → Tonal palette (13 tones) → Role tokens
  (primary, on-primary, primary-container, on-primary-container, ...)

Less explicit than Ant — Material bakes the algorithm into the spec. Theming uses *ource color*as the single seed; tonal palette is implicit.

Base Web / Polaris — flat

Base Web (~80 components) uses flat tokens like Verge. Polaris ships flat with tone:* semantic names. Neither offers algorithmic derivation; consumers extend via Provider props.

Adwaita (Verge v0 reference) — flat

GNOME Adwaita uses flat library colors (accent_bg_color, card_bg_color, etc.). No seed/algorithm layer. Verge v0 inherited this directly.

R3 — Options

Option A — Keep flat (status quo)

*echanics* 44 presets × 2 modes continue as the contract. Each preset declares all alias tokens directly. New presets remain "copy/paste + tune".

*ros*

  • Zero migration cost.
  • Each preset is *ully auditable*as a single block of values

    (no derivation hides surprises).

  • Designers and devs can read any preset and see the literal

    rendered values.

  • Aligns with Verge v0 = Adwaita 1:1 philosophy.

*ons*

  • Crosspreset invariants enforced only by audit (`vergecontrast-audit`,

    koder-spec-audit themes) — no structural guarantee.

  • Compact / spacious density mode is a *eparate preset family*

    doubling the set.

  • A consumer wanting a single-color brand override (e.g. red accent

    for an emergency-services product) must fork a full preset.

Option B — Adopt 3-tier (Ant model)

*echanics*

  • *eed tokens*declared in a new internal/kinds/verge_seeds.go:

    seedAccent, seedNeutral, seedDanger, seedSurface, seedRadius, seedFontSize, seedSpacingUnit.

  • *lgorithms*in internal/tokens/algorithms.go: lightAlgorithm,

    darkAlgorithm, compactAlgorithm. Each takes seeds and emits a map of derived tokens (color-bg-container, color-text-secondary, border-radius-sm, …).

  • *lias tokens*are renamed map outputs with semantic names; this

    is what components consume.

*ros*

  • Compact / spacious modes are *rivial*— swap algorithm.
  • Brand override = swap one seed, propagation is automatic.
  • 5–10× compression of preset definitions (44 presets become

    44 seed sets).

  • Cross-preset invariants are *tructural* not policed by audit.
  • Theme drift detection across consumer products becomes

    "does seed X derive to alias Y consistently?" — trivially testable.

*ons*

  • Migration cost — every existing preset must be re-expressed as a

    seed set, then the derivation re-validated against the current literal values.

  • A token's literal value is no longer readable directly — must

    trace seed → algorithm → map → alias. Higher cognitive load.

  • The algorithm becomes part of the contract; changing it changes

    every consumer.

  • More moving parts in tools/design-gen build chain.

Option C — Hybrid (seed → alias, no algorithm)

*echanics*

  • *eeds*decompose alias values into "seed-derived" relations

    (e.g. --kdr-surface = lighten($seed-neutral, 8%)) but *o algorithm layer*sits between them.

  • Each preset declares seeds; alias tokens are computed via a small

    fixed set of SCSSlike helpers (lighten, darken, `withalpha`).

  • No compact / spacious algorithm — density still ships as separate

    presets.

*ros*

  • Compresses preset definitions (~3–4× less code than flat).
  • Still readable — every alias has a one-step derivation.
  • Cross-preset invariants are structural (any seed change propagates).
  • Migration risk lower than Option B (algorithm layer absent).

*ons*

  • No compact / spacious benefit — density still copy/paste.
  • Helper functions become part of the contract.
  • Hybrid sits between "fully readable flat" and "fully algorithmic"

    without the strengths of either.

R4 — Decision criteria

Criterion A — flat B — 3-tier C — hybrid
Migration cost none high (88 presets × derivation tuning) medium
Readability of a single preset ✅ literal ❌ trace 3 layers ◑ one-step derive
Brand-override ergonomics ❌ fork preset ✅ swap seed ✅ swap seed
Density mode (compact/spacious) ❌ separate presets ✅ algorithm swap ❌ separate presets
Cross-preset invariants audit-policed structural structural
Consumer-extension cost high (fork) low (override seed) low (override seed)
Risk of breaking existing consumers none high (every alias may shift) medium
Adwaita 1:1 alignment (v0 philosophy)

R5 — Recommendation (advisory — pending owner sign-off)

*efer the decision.*Verge v0 is *hipping*under flat tokens with 44 presets and the audit pipeline holding cross-preset invariants ("good enough"). Re-opening the model now risks consumer breakage during the homologation acceleration phase.

When the trigger materializes — *irst product that needs a custom brand-override beyond preset swap*OR *irst request for compact / spacious density mode*— re-evaluate. At that point Option B is the strong default; Option C is a fallback if migration risk is too high.

R6 — Migration path (when triggered)

Whichever option ratifies, the migration MUST:

  1. Ship the new model *longside*flat — verge.go keeps emitting

    the flat alias map for one Verge minor version (backwards-compat).

  2. Generate a delta report — "preset X: seed derivation diverges

    from current literal by ΔE = 1.4 in dark mode" — for designer review before flipping.

  3. Update koder_kit / koder_web_kit cross-language token

    distribution (RFC-006) to emit the new seedmapalias triple.

  4. Open per-consumer tickets to migrate hardcoded alias references

    to the new names (if any drift).

  5. Remove the flat shim one Verge minor after the new model ratifies.

R7 — Test contract (when triggered)

  • *1* For every flat preset, the new model derives values within

    ΔE ≤ 2.0 of the original (visual equivalence).

  • *2* All crosspreset invariants previously auditpoliced

    (WCAG AA / AAA contrast ratios, focus-ring contrast, etc.) hold structurally — i.e. the algorithm cannot emit a non-compliant map.

  • *3* Compact algorithm swap halves the spacing unit consistently

    across every alias that references it.

  • *4* Dark algorithm swap inverts neutral lightness while

    preserving accent saturation.

R8 — Open questions

  1. If Option B ratifies: is compactAlgorithm shipped together with

    lightAlgorithm / darkAlgorithm, or is it a Verge v1.1 follow-up?

  2. If Option B ratifies: does internal/tokens/algorithms.go ship as

    pure Go (one binary, design-gen owns it) or as a published library (multilanguage consumers run the algorithm clientside via koder_kit SDK)?

  3. If Option C ratifies: which helper set is closed (lighten,

    darken, mix, with-alpha, …)? OKLCHbased or sRGBbased?

Sign-off

Role Owner
Author @rpm (20260522)
Ratification pending_
Implementation backlog tracked in tools/design-gen/backlog/pending/093-rfc-token-hierarchy-seed-map-alias.kmd

Source: ../home/koder/dev/koder/meta/docs/stack/rfcs/design-RFC-007-token-hierarchy-seed-map-alias.kmd