Skip to content

refactor(ui): converge design-token magic numbers (motion, typography, spacing) #430

Description

@Astro-Han

TL;DR

Maka's frontend has a proven governance pattern — converge contract tests that scan CSS/TSX and fail on bare values. Radius is the success story: 0 bare border-radius values, locked by radius-converge-contract.test.ts. But four dimensions still have large magic-number gaps: motion easing/duration, typography, spacing, and icon size. This issue converges them using the same proven pattern, after two independent reviews (OpenAI Codex + a fresh-context adversarial pass) caught real bugs and corrected the plan.

Two bugs were found during review and are folded in:

  • --ease-maka is a dead reference (ui.tsx:30 uses it, but it's never defined — the real token is --ease-out-strong).
  • --spacing: 0.25rem is 3.75px, not 4px — the comment /* 4px base step */ is wrong because html { font-size: 15px } makes 0.25rem = 3.75px. This means Tailwind's spacing scale (p-1 = 3.75px) and the hand-written padding: 8px in maka's CSS are two unrelated scales that don't match.

Current state

Already converged (don't touch):

Dimension Status Guard
radius 0 bare values radius-converge-contract.test.ts
z-index 1 bare value (whitelisted) z-index-contract.test.ts
color / shadow uses tokens design-system-governance-406-contract.test.ts
motion (single curve) cubic-bezier(0.16,1,0.3,1) only in token decl motion-token-converge-contract.test.ts

Gaps (this issue):

Dimension Bare values Notes
icon size={N} 155 sites, 14 distinct values (11-28) makeIcon defaults to 1em already
font-size 436 bare values only --font-size-base: 15px exists, no scale
spacing (padding/gap/margin) 725 bare px --spacing defined but used 0 times
motion easing 166 bare ease/ease-out/ease-in-out should use --ease-*
motion duration 53 bare ms 140ms (×21), 160ms (×11) are the actual high-frequency tuned values

Benchmark

Three peer projects were analyzed (lobehub/lobe-chat, stablyai/orca, craft-ai-agents/craft-agents-oss). None of them have automated guards against bare values — they all rely on convention + component encapsulation (Tailwind default scale / antd tokens / shadcn CVA). Maka's converge-contract approach is unique and proven (radius = 0 bare). The plan extends that pattern rather than copying peer conventions.

What the two reviews caught

Review 1 (Codex):

  • Changing --font-size-base 15→14 is changing the root, which proportionally scales every rem and every Tailwind text-* class — a global visual regression, not a single-point tweak. → Keep root at 15.
  • Full icon convergence violates icon-system-contract.test.ts whose comments explicitly state dense/hero icons are "intentional, not drift". → Don't touch icons.
  • --ease-maka is a dead reference. → Fix it.
  • 18px appears in multiple places (page padding, card gap, preview, sidebar), not just composer.

Review 2 (adversarial, fresh context):

  • --spacing: 0.25rem under a 15px root = 3.75px, not the 4px the comment claims. The "adopt Tailwind's 4px scale" premise was false. → Fix the --spacing value first.
  • Spacing has no single-property anchor like border-radius does. 18px spans 6 semantic contexts (padding, width/height, font-size, grid-columns, box-shadow, position). A blanket contract can't cleanly target only spacing.
  • 140ms (×21) and 160ms (×11) are deliberately tuned feedback durations, all paired with --ease-out-strong. Excluding them from a 120/150/180/280 whitelist would force 32 gratuitous changes. → Don't force a duration scale.
  • 10px (×47) forced to 12 is a +20% size increase on dense meta labels (timestamps, uppercase overlines). The owner accepts this.

Plan

One PR per dimension. Each PR combines governance (tokenize + contract lock) and value convergence, the way radius originally did. Value-changing steps (tier reduction, 18→16) carry screenshot verification within their PR.

PR 1 — motion (lowest risk, do first)

  • Ban bare ease/ease-out/ease-in-out/linear — must reference var(--ease-*).
  • Fix --ease-maka dead reference (ui.tsx:30--ease-out-strong).
  • Do not force a duration scale: 140ms/160ms are tuned values; encourage var(--duration-*) but don't ban them.
  • Extend motion-token-converge-contract.test.ts.

PR 2 — typography

  • Body 15 (--font-size-base untouched — avoids global rem scaling).
  • UI tier 12; reduce 9/10/11/13 → 12 (the 47 10px meta labels grow +20%, owner-accepted).
  • Headings via em scaling off body 15 (h1-h4 ratios pinned in the PR).
  • Introduce typography tokens + converge contract banning bare font-size.
  • Screenshot diff required (tier reduction changes dense meta density).

PR 3 — spacing

  • First fix the --spacing bug: 0.25rem (3.75px under 15px root) → absolute 4px so Tailwind's scale aligns with hand-written px.
  • 18 → 16 (page padding, card gap, preview, sidebar).
  • Define a px spacing scale + converge contract (targets padding/gap/margin; spacing has no single-property anchor like radius, so the contract scopes to those properties).
  • Screenshot diff required.

icon — no change

icon-system-contract.test.ts already correctly treats dense (12-14) and hero (20+) as deliberate call-site values. makeIcon defaults to 1em, tracking font-size automatically. Reopening the dense-tier design decision is a separate discussion.

Out of scope

  • Component sizing (329 bare width/height): separate future evaluation. Spacing's 18px and sizing's 18px are coupled in some surfaces, so a sizing pass may revisit a few values landed in PR 3.
  • User-configurable density: not doing.
  • Full Tailwind migration: recorded as a future independent track. Maka is a hybrid (Tailwind primitives + hand-written CSS recipes with converge contracts); the contracts are stricter than Tailwind (Tailwind allows arbitrary p-[13px], contracts don't), so a full rewrite isn't justified now.
  • docs/design-system.md rewrite: the doc is stale; refresh it as each PR lands, don't block on it.

Key decisions

  1. Root font-size stays 15 — changing it scales every rem globally (Codex finding).
  2. Motion duration isn't scale-locked — 140/160ms are tuned values (adversarial finding).
  3. Icons untouched — dense/hero are deliberate per existing contract (both reviews).
  4. --spacing 3.75px bug fixed first — the "Tailwind 4px scale" premise was false (adversarial finding).
  5. One PR per dimension — governance (token + contract) and value convergence combined per dimension, like radius originally; value-changing steps carry screenshots.

Open assumptions

  • The +20% growth on 47 10px meta labels (PR 2) is accepted; screenshot confirmation in the PR.
  • 18→16 (PR 3) touches multiple surfaces; screenshot confirmation in the PR.
  • Heading h1-h4 em ratios are finalized during PR 2 implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions