Skip to content

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

Closed

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.

Activity

  1. changed the title [-]Frontend design-token governance: converge motion / typography / spacing magic numbers[/-] [+]refactor(ui): converge design-token magic numbers (motion, typography, spacing)[/+] on Jul 2, 2026
  2. Astro-Han commented on Jul 2, 2026

    @Astro-Han
    ContributorAuthor

    PR2 Typography — Merged (PR #437)

    Three-tier font-size token scale landed:

    • --font-size-base: 15px / --font-size-ui: 13px / --font-size-caption: 11px
    • 413 bare px/rem font-size values across 22 CSS files converged to token references
    • Tailwind --text-{xs,sm,base} aliases single-source the tokens
    • Contract test 9/9 passing — covers font: shorthand bypass, negative cases, token pin, Tailwind alias pin

    Review verdicts

    • P1 PermissionDialog @kenji review — Rejected. docs/design-system.md is stale and not the current governance source; all affected selectors were 11px on main, changing to --font-size-caption (11px) is zero visual change.
    • P2-1 Settings hint 14px legibility floor — Accepted and fixed. .settingsField small bumped from --font-size-ui (13px) to --font-size-base (15px).
    • P2-2 em backdoor — Rejected. em is a relative unit, not an absolute value; the contract banning absolute px/rem is sufficient.
    • P3 test simplicity — Accepted and fixed. Removed unused EM_RE and duplicate bare px/rem scan.
    • P3 font: shorthand bypass — Accepted and fixed. Scanner now catches bare px/rem inside font: shorthand.

    PR3 Spacing — Not yet done

    PR3 is the last PR in the #430 plan, not yet landed. Scope:

    1. Fix --spacing: 0.25rem bug. Under the 15px root this equals 3.75px, not the 4px the comment claims. Change to absolute 4px so the Tailwind scale aligns with hand-written px.
    2. 614 bare px values in padding/gap/margin converge to a spacing scale.
    3. Define spacing scale + converge contract (scoped to padding/gap/margin; spacing has no single-property anchor like border-radius).
    4. 18px → 16px touches multiple surfaces, requires screenshot verification.

    Future independent track: full Tailwind migration + file structure cleanup

    Why we reconsidered Tailwind migration

    The goal for maka's governance is "reduce divergence and choice." From that angle Tailwind is stronger than hand-written CSS + contracts:

    • Pre-emptive vs after-the-fact. Tailwind's fixed steps make p-1.5 impossible (without explicit arbitrary value). Hand-written CSS relies on contracts to catch violations after the fact, requiring ongoing test maintenance.
    • Comments rot. Design-intent comments like /* 13 → 14 legibility floor */ drift from code over time. Tailwind's text-sm needs no explanation because there is no text-[13px] option.
    • Standardization is Tailwind's core selling point. This is exactly what maka wants.

    But not rushing — costs are clear

    • 22 hand-written CSS files, 600+ rules to rewrite
    • Complex custom surfaces (permission dialog, onboarding hero) produce long className strings in Tailwind, less readable than current CSS
    • Tailwind default steps (4/8/12/16/20/24px) don't match maka's current values (10/14/18px) — either extend Tailwind config or accept visual changes

    Performance is not a decision factor

    Tailwind v4 JIT generates only used classes; final CSS size is comparable to hand-written. Both are static CSS with no runtime overhead.

    File structure issues

    Current symptoms:

    1. CSS scattered in two places. styles/ holds large surface styles, styles/settings/ holds sub-modules, but maka-tokens.css and styles.css sit in the renderer root.
    2. TSX partially organized. settings/ has 37 files (good), but renderer/ root has 20+ app-shell-*.ts files flat — actions / events / view-models mixed together.
    3. CSS and TSX naming diverge. CSS uses surface names (onboarding.css), TSX uses feature names (app-shell-*). Same feature lives in two places with different names.
    4. packages/ui fully flat. 40+ files in src/ with no sub-directories (except primitives/).

    Suggested structure (co-location principle):

    renderer/
      tokens/              # design tokens
      features/            # by surface, CSS + TSX together
        chat/
        settings/
        onboarding/
        ...
      shared/              # cross-surface shared code
    

    maka-tokens.css split

    Current 1797 lines mix 9 responsibilities. Should split into:

    • tokens.css — variable definitions only (light + dark + palette catalog), ~600 lines
    • Component recipes (lines 805-1717, 870 lines) — split by surface to corresponding CSS, or a recipes/ directory
    • Animations — separate animations.css
    • Base styles — merge into styles/base.css

    Recommendation: tokens.css split and file structure cleanup should be planned together with the Tailwind migration, not done separately. Most component recipes will disappear during migration (replaced by utility classes); splitting now means re-splitting later.

    icon size — no change

    #430 plan explicitly excludes icons. PR #81 already introduced --icon-size: 16px to unify chrome icons; dense (12-14px) and hero (20+) are deliberate per icon-system-contract.test.ts. Not reopening this.

    docs/design-system.md is stale

    Confirmed by owner. The "#430 plan said refresh as each PR lands" was not executed. This doc is not treated as a governance source.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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