Repository navigation
refactor(ui): converge design-token magic numbers (motion, typography, spacing) #430
Description
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-makais a dead reference (ui.tsx:30uses it, but it's never defined — the real token is--ease-out-strong).--spacing: 0.25remis 3.75px, not 4px — the comment/* 4px base step */is wrong becausehtml { font-size: 15px }makes0.25rem = 3.75px. This means Tailwind's spacing scale (p-1= 3.75px) and the hand-writtenpadding: 8pxin 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-base15→14 is changing the root, which proportionally scales everyremand every Tailwindtext-*class — a global visual regression, not a single-point tweak. → Keep root at 15. - Full icon convergence violates
icon-system-contract.test.tswhose comments explicitly state dense/hero icons are "intentional, not drift". → Don't touch icons. --ease-makais 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.25remunder a 15px root = 3.75px, not the 4px the comment claims. The "adopt Tailwind's 4px scale" premise was false. → Fix the--spacingvalue first.- Spacing has no single-property anchor like
border-radiusdoes. 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) and160ms(×11) are deliberately tuned feedback durations, all paired with--ease-out-strong. Excluding them from a120/150/180/280whitelist 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 referencevar(--ease-*). - Fix
--ease-makadead 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-baseuntouched — avoids global rem scaling). - UI tier 12; reduce 9/10/11/13 → 12 (the 47
10pxmeta labels grow +20%, owner-accepted). - Headings via
emscaling 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
--spacingbug:0.25rem(3.75px under 15px root) → absolute4pxso 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.mdrewrite: the doc is stale; refresh it as each PR lands, don't block on it.
Key decisions
- Root font-size stays 15 — changing it scales every rem globally (Codex finding).
- Motion duration isn't scale-locked — 140/160ms are tuned values (adversarial finding).
- Icons untouched — dense/hero are deliberate per existing contract (both reviews).
--spacing3.75px bug fixed first — the "Tailwind 4px scale" premise was false (adversarial finding).- 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
10pxmeta 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
[-]Frontend design-token governance: converge motion / typography / spacing magic numbers[/-][+]refactor(ui): converge design-token magic numbers (motion, typography, spacing)[/+]refactor(ui): ban bare ease keywords, enforce var(--ease-*) tokens
refactor(ui): ban bare ease keywords, enforce var(--ease-*) tokens (#431
refactor(ui): converge font-size to three-tier token scale (#430 PR2)
fix(ui): bump settings hint to --font-size-base, slim contract test (#…
test(ui): catch font: shorthand bypass in typography contract (#430 PR2)
refactor(ui): converge font-size to two-tier token scale (#430 PR2) (#…
Astro-Han commentedon Jul 2, 2026
PR2 Typography — Merged (PR #437)
Three-tier font-size token scale landed:
--font-size-base: 15px/--font-size-ui: 13px/--font-size-caption: 11px--text-{xs,sm,base}aliases single-source the tokensfont:shorthand bypass, negative cases, token pin, Tailwind alias pinReview verdicts
docs/design-system.mdis stale and not the current governance source; all affected selectors were 11px on main, changing to--font-size-caption(11px) is zero visual change..settingsField smallbumped from--font-size-ui(13px) to--font-size-base(15px).EM_REand duplicate bare px/rem scan.font:shorthand.PR3 Spacing — Not yet done
PR3 is the last PR in the #430 plan, not yet landed. Scope:
--spacing: 0.25rembug. Under the 15px root this equals 3.75px, not the 4px the comment claims. Change to absolute4pxso the Tailwind scale aligns with hand-written px.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:
p-1.5impossible (without explicit arbitrary value). Hand-written CSS relies on contracts to catch violations after the fact, requiring ongoing test maintenance./* 13 → 14 legibility floor */drift from code over time. Tailwind'stext-smneeds no explanation because there is notext-[13px]option.But not rushing — costs are clear
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:
styles/holds large surface styles,styles/settings/holds sub-modules, butmaka-tokens.cssandstyles.csssit in the renderer root.settings/has 37 files (good), butrenderer/root has 20+app-shell-*.tsfiles flat — actions / events / view-models mixed together.onboarding.css), TSX uses feature names (app-shell-*). Same feature lives in two places with different names.packages/uifully flat. 40+ files insrc/with no sub-directories (exceptprimitives/).Suggested structure (co-location principle):
maka-tokens.css split
Current 1797 lines mix 9 responsibilities. Should split into:
tokens.css— variable definitions only (light + dark + palette catalog), ~600 linesrecipes/directoryanimations.cssstyles/base.cssRecommendation: 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: 16pxto unify chrome icons; dense (12-14px) and hero (20+) are deliberate pericon-system-contract.test.ts. Not reopening this.docs/design-system.mdis staleConfirmed by owner. The "#430 plan said refresh as each PR lands" was not executed. This doc is not treated as a governance source.
refactor(storybook): redesign Spacing story for --space-* scale (#430 …
fix(ui): --spacing 3.75px bug -> 4px ruler + define --space-* scale (#…
refactor(ui): replace all bare px spacing with var(--space-*) tokens (#…
test(contract): spacing-converge-contract bans bare px in padding/gap…
docs: refresh design-system §1.5 spacing for --space-* scale (#430 PR3)
refactor(storybook): redesign Spacing story for --space-* scale (#430 …
refactor(ui): converge spacing to --space-* scale, ban bare px (#430 …
fix(contract): tighten clamp/max/min + semantic var tracing per review (
feat(design): establish foreground-secondary alias + delete unused -9…
refactor(design): replace 444 text call sites with semantic foregroun…
test(design): add foreground-tier contract test + mark roadmap complete
feat(design): establish foreground-secondary alias + delete unused -9…
test(design): add foreground-tier contract test + mark roadmap complete
feat(design): converge foreground text tiers 5→3 (issue #430 PR4) (#479)