You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Maka's frontend governance pattern — converge contract tests that scan CSS/TSX and fail on bare values — has locked radius, z-index, color, shadow recipe, motion easing, font-size, spacing, foreground tiers, state tokens, and icons. A scoped review (three read-only GPT-5.5 passes against shadcn/ui + Base UI + maka's own CSS) surfaced that this is not the full picture: typography still has no line-height/font-weight/letter-spacing tokens, several "already governed" dimensions have holes (raw motion duration, raw box-shadow outside recipes, font-variation-settings bypassing font-weight), and a swath of hand-written components have Base UI counterparts that were never migrated (disclosure, toast, alert-dialog, tooltip, dialog/drawer, combobox). This issue tracks every unmanaged spec in one place and converges them in 6 PRs, using the same proven pattern as #430 / #448 / #499. A P0 conflict was caught during review: scroll-area is locked to OverlayScrollbars by overlay-scrollbars-contract.test.ts, so it is explicitly kept out of scope (deliberate choice, not a migration target).
Current state
Already converged (don't touch):
Dimension
Status
Guard
radius
0 bare values
radius-converge-contract.test.ts
z-index
semantic tokens
z-index-contract.test.ts
color
uses tokens
design-system-governance-406-contract.test.ts
shadow recipe
--shadow-* recipes
design-system-governance-406-contract.test.ts (recipe refs only — raw box-shadow is a gap, see #13)
motion easing
--ease-* only
motion-token-converge-contract.test.ts (easing only — raw duration/amplitude is a gap, see #8)
font-size
3-tier scale (base 15 / ui 13 / caption 11)
typography-converge-contract.test.ts
spacing
14-tier --space-* (padding/margin/gap only)
spacing-converge-contract.test.ts
foreground tiers
3 semantic aliases
foreground-tier-contract.test.ts
state tokens
hover/selected
state-token-governance-499-contract.test.ts
icons
size token + library seam
icon-system-contract.test.ts
scroll-area
locked to OverlayScrollbars
overlay-scrollbars-contract.test.ts (deliberate, out of scope)
--focus-ring is color-only; ring width/offset mixed (ring-offset-2/1/0, box-shadow: 0 0 0 2px var(--ring))
~63
PR2
8
motion duration + amplitude
raw transition-duration/shorthand ms + undefined --duration-fast referenced + bare transform: amplitude
~59
PR2
9
tabular-nums
39 present, "fill per surface" per roadmap §1.6
—
PR3
10
min-w-0 truncation
~110 present; missing sites are a historic bug source
—
PR3
11
min-width on text-swap elements
chat layout-shift root cause per roadmap §4.1
—
PR3
12
concentric radius nesting
only 2 calc(var(--radius-modal)-…) sites; settings-modal inner cards / tool-card pre unaudited
—
PR4
13
surface elevation + raw box-shadow
"input slightly darker" / "dropdown above parent" rules exist (P-INSET did composer exception); ~71 raw box-shadow outside recipe refs
~71
PR4
14
border stroke width/style
only border color tokenized; border-width/border-style/shorthand bare
~266
PR4
15
component sizing / control height
h-9/h-10 + bare 34/38/52px; Button/Input/Badge size systems don't align
—
PR4
16
responsive / breakpoint / container
bare @media (max-width: …), width: min(…), modal/chat-measure widths
—
PR4
17
disclosure → Base UI Collapsible/Accordion
reasoning/turn-thinking/tool-activity/permission-raw use <details>; code comments say "future Base UI Accordion path"
4+
PR5
18
toast → Base UI Toast (or fix comment)
toast.tsx hand-written; ui.tsx comment falsely claims it wraps Base UI Toast
—
PR6
19
alert-dialog (confirm dialog)
toast.confirm() hand-sets role="alertdialog"
—
PR6
20
tooltip → Base UI Tooltip
no wrapper; native title= used as tooltip across the app
—
PR5
21
number-field → Base UI NumberField
gateway/proxy port inputs hand-convert with Number(...)
2
PR5
22
input canonical
ui.tsxInput is native; primitives/input.tsx (Base UI) not exported from barrel
—
PR8
23
Base UI style-hook convention
data-slot missing on ui.tsx wrappers; boolean state hook semantics mixed ([data-active] vs [data-active="true"]); no component-level hook map / CSS var whitelist; className(state) form undecided
One PR per verification boundary (not per dimension), same pattern as #430. Value-changing PRs carry screenshot verification. Each PR splits into 2 commits: ① tokens + contracts ② replacements + verify.
PR 1 — typography converge (P0, do first)
All typography numeric convergence. Same type (token + contract banning bare values + full replacement), same verification (dual-theme screenshot on text).
line-height: 4 --leading-* tokens aligned to a Tailwind leading-scale subset (drop relaxed/loose, zero sites): --leading-none: 1, --leading-tight: 1.25, --leading-snug: 1.375, --leading-normal: 1.5. Snug is the one tier that can't be cut: 40 sites at 1.3-1.4 (13/11px small-text dense zones) collapse to tight (too tight) or normal (too loose) at ±0.1-0.2. Add line-height-converge-contract.test.ts banning bare line-height numbers; whitelist the 4 tokens + inherit/initial. Replace ~160 sites → var(--leading-*). Largest delta: .maka-bubble-assistant 1.68 → 1.5 (chat core; screenshot required). Bridge --leading-* to Tailwind leading-* utilities in @theme inline, same as spacing.
font-weight: converge 8 values to a small semantic scale (e.g. --font-weight-regular/medium/semibold/bold = 400/500/600/700). Variable-weight Geist + system-ui means 550/620/650/680 are mid-axis picks to snap.
letter-spacing: tokenize + contract. Encode two semantic rules from roadmap §1.6 / §4.1: no tightening on CJK; ALL-CAPS short labels get +5-12% tracking. The 14 values converge to a small scale.
font-variation-settings: ban bare wght/wdth axis values that bypass font-weight (e.g. chat-message.css:88"wght" 550, "wdth" 105); fold axis weights into the font-weight token.
Non-typography visual numeric convergence. Same type, same verification (dual-theme screenshot).
opacity: converge ~58 bare numbers to semantic tokens (disabled / pending / muted / live).
focus-ring recipe: --focus-ring is color-only; tokenize ring width/offset and converge outline/outline-offset/ring-offset-* mixed usage into one recipe.
motion completion: ban raw transition-duration/shorthand ms; fix the undefined --duration-fast reference (token file has no such token); tokenize transform: amplitude (press scale, slide distance) so motion contract covers duration + amplitude, not just easing.
PR 3 — anti-layout-shift contracts (P1)
Three "fill + contract" items preventing text/number width changes from breaking layout. Same type (audit missing sites + contract), same verification (find-the-gaps).
tabular-nums: audit which numeric surfaces lack tabular-nums; contract flags numeric containers missing it.
text-swap min-width: audit state labels / token counts / "已复制" feedback for min-width locked to the widest state; contract to prevent chat layout-shift.
min-w-0 truncation: contract that flex/grid text containers must have min-w-0 to truncate; flags missing sites that caused historic truncation bugs.
PR 4 — layout surface & sizing tokens (P2)
Layout visual rules + sizing tokenization. Mixed type (rule systematization + numeric converge), same verification (nesting audit + screenshot).
PR 5 — style-hook convention + small Base UI migrations (P3)
The cross-cutting style-hook convention (the foundation the other migrations follow) + the smaller, more mechanical Base UI adoptions. Same verification (migration + consistency audit + screenshot).
style-hook convention (Add cache-aware request shape diagnostics #23, first commit — the foundation): (1) add data-slot to all ui.tsx Base wrappers (primitives have it, ui.tsx wrappers don't); (2) adopt Base UI's native boolean state attributes (attribute-presence [data-active] / [data-open] / [data-checked] / [data-selected]) — maka's renderer CSS has zero state-attribute selectors today, so there is nothing to break and no [data-active="true"] override to maintain; (3) document a per-component state hook map (Tabs data-active / Select data-selected / Checkbox data-checked / popup data-open etc.) as a doc comment in the barrel; (4) whitelist CSS var hooks (--anchor-*/--available-*/--active-tab-*); (5) decide className(state) function form only if a migration in this PR actually needs it (defer otherwise).
disclosure → Collapsible: migrate the four <details> sites (reasoning/turn-thinking/tool-activity/permission-raw — all independent single sections, not grouped, so Collapsible not Accordion) to Base UI Collapsible (code comments already say "future Base UI Accordion path").
tooltip: add a Base UI Tooltip wrapper (primitives/tooltip.tsx with data-slot); migrate the clearest icon-only-action button title= usages (app-shell-chrome-actions 7, browser-panel 4, artifact-pane collapse) — 13 sites. The longer tail (truncation spans, SelectTrigger titles, status-badge icons, OnboardingHero submit) are tooltip-eligible but need per-site label-vs-tooltip judgment and are deferred to a follow-up; title= on label-prop components (SettingRow/MetricCard/DailyReviewTopList) is NOT a tooltip and stays.
number-field: use Base UI NumberField for the two gateway/proxy port inputs (general-settings proxy port, open-gateway port), dropping the Number(...) hand-conversion.
input canonical (Add DeepSeek context budget diagnostics #22) moved to PR8: primitives/input.tsx already carries a different structure (a data-slot="input-control" span wrapper + inner Base UI input, ring-[3px]) than the native ui.tsxInput (single <input> + inputClasses, focus-visible:ring-2), and bare-field-chrome-contract.test.ts pins the native's data-maka-field-chrome + border-input + ring-2. Retiring the native is not a clean look-preserving swap — it needs inputClasses ported into primitives/input.tsx, InputGroup updated to avoid double chrome, and the chrome contract rewritten — so it is its own focused PR.
PR 6 — toast + alert-dialog (P3)
The toast system + confirm dialog, split out because the toast rewrite is a sizable verification boundary of its own (auto-dismiss timing, focus, a11y).
toast → Base UI Toast (or fix comment): either migrate toast.tsx onto Base UI Toast, or correct the false ui.tsx comment that claims it already wraps Base UI Toast.
alert-dialog: route toast.confirm() and PermissionDialog's hand-set role="alertdialog" through Base UI AlertDialog.
PR 7 — Base UI large migrations (P3)
Larger Base UI adoptions that each touch broad surfaces. Same type (migration), same verification (migration + screenshot), but separated from PR5 because each is sizable.
dialog/drawer: route provider-config-sheet, OAuth login, bot QR, keyboard-help, command-palette hand-written role="dialog" + useModalA11y through Base UI Dialog/Drawer.
combobox/autocomplete: route SearchModal, CommandPalette, provider model-picker hand-written listbox/kbd-nav/aria-activedescendant through Base UI Combobox/Autocomplete.
Retire the native ui.tsxInput onto the Base UI primitives/input.tsx as the one canonical Input. Split out of PR5 because it is not a clean look-preserving swap (see the PR5 note): primitives/input.tsx carries a different structure + ring-[3px], and bare-field-chrome-contract.test.ts pins the native's data-maka-field-chrome + border-input + ring-2.
input canonical (Add DeepSeek context budget diagnostics #22): port maka's inputClasses styling into primitives/input.tsx as the default so the 44 usages across 9 files keep their look; update InputGroup to keep its inner input bare (avoid double chrome); rewrite bare-field-chrome-contract.test.ts for the new structure; retire the native ui.tsxInput; export primitives/input.tsx as the canonical Input from the barrel.
Out of scope
scroll-area migration: overlay-scrollbars-contract.test.ts locks primitives/scroll-area.tsx to OverlayScrollbars (custom os-theme-maka theme, autoHide: 'move') and explicitly bans @base-ui/react/scroll-area. OverlayScrollbars is a deliberate choice; "全面使用 Base UI" stops here.
New Base UI components with no maka counterpart: spinner / kbd / input-group / empty / chat have no Base UI equivalent and stay hand-written (chat is a domain component, not a primitive gap).
TL;DR
Maka's frontend governance pattern — converge contract tests that scan CSS/TSX and fail on bare values — has locked radius, z-index, color, shadow recipe, motion easing, font-size, spacing, foreground tiers, state tokens, and icons. A scoped review (three read-only GPT-5.5 passes against shadcn/ui + Base UI + maka's own CSS) surfaced that this is not the full picture: typography still has no line-height/font-weight/letter-spacing tokens, several "already governed" dimensions have holes (raw motion duration, raw box-shadow outside recipes,
font-variation-settingsbypassing font-weight), and a swath of hand-written components have Base UI counterparts that were never migrated (disclosure, toast, alert-dialog, tooltip, dialog/drawer, combobox). This issue tracks every unmanaged spec in one place and converges them in 6 PRs, using the same proven pattern as #430 / #448 / #499. A P0 conflict was caught during review: scroll-area is locked to OverlayScrollbars byoverlay-scrollbars-contract.test.ts, so it is explicitly kept out of scope (deliberate choice, not a migration target).Current state
Already converged (don't touch):
radius-converge-contract.test.tsz-index-contract.test.tsdesign-system-governance-406-contract.test.ts--shadow-*recipesdesign-system-governance-406-contract.test.ts(recipe refs only — rawbox-shadowis a gap, see #13)--ease-*onlymotion-token-converge-contract.test.ts(easing only — raw duration/amplitude is a gap, see #8)typography-converge-contract.test.ts--space-*(padding/margin/gap only)spacing-converge-contract.test.tsforeground-tier-contract.test.tsstate-token-governance-499-contract.test.tsicon-system-contract.test.tsoverlay-scrollbars-contract.test.ts(deliberate, out of scope)Gaps (this issue) — grouped by PR:
20pxmagic-0.018emmis-tightening, ALL-CAPS0.08em)"wght" 550, "wdth" 105(bypasses font-weight)var(--mono-font, monospace)×1, inconsistent mono fallback ×2,Georgia, serif×1--focus-ringis color-only; ring width/offset mixed (ring-offset-2/1/0,box-shadow: 0 0 0 2px var(--ring))transition-duration/shorthand ms + undefined--duration-fastreferenced + baretransform:amplitudecalc(var(--radius-modal)-…)sites; settings-modal inner cards / tool-cardpreunauditedbox-shadowoutside recipe refsborder-width/border-style/shorthand bareh-9/h-10+ bare34/38/52px; Button/Input/Badgesizesystems don't align@media (max-width: …),width: min(…), modal/chat-measure widths<details>; code comments say "future Base UI Accordion path"toast.tsxhand-written;ui.tsxcomment falsely claims it wraps Base UI Toasttoast.confirm()hand-setsrole="alertdialog"title=used as tooltip across the appNumber(...)ui.tsxInputis native;primitives/input.tsx(Base UI) not exported from barreldata-slotmissing onui.tsxwrappers; boolean state hook semantics mixed ([data-active]vs[data-active="true"]); no component-level hook map / CSS var whitelist;className(state)form undecidedrole="dialog"+useModalA11yaria-activedescendantCard/Table;.settingsRows,.settingsMetricCard,.maka-error-card,settingsStatsTablehand-writtenBadge+PrimitiveBadge+.settingsBadge/.settingsConnectionBadgecoexistPlan
One PR per verification boundary (not per dimension), same pattern as #430. Value-changing PRs carry screenshot verification. Each PR splits into 2 commits: ① tokens + contracts ② replacements + verify.
PR 1 — typography converge (P0, do first)
All typography numeric convergence. Same type (token + contract banning bare values + full replacement), same verification (dual-theme screenshot on text).
--leading-*tokens aligned to a Tailwind leading-scale subset (droprelaxed/loose, zero sites):--leading-none: 1,--leading-tight: 1.25,--leading-snug: 1.375,--leading-normal: 1.5. Snug is the one tier that can't be cut: 40 sites at 1.3-1.4 (13/11px small-text dense zones) collapse to tight (too tight) or normal (too loose) at ±0.1-0.2. Addline-height-converge-contract.test.tsbanning bareline-heightnumbers; whitelist the 4 tokens +inherit/initial. Replace ~160 sites →var(--leading-*). Largest delta:.maka-bubble-assistant1.68 → 1.5 (chat core; screenshot required). Bridge--leading-*to Tailwindleading-*utilities in@theme inline, same as spacing.--font-weight-regular/medium/semibold/bold= 400/500/600/700). Variable-weight Geist + system-ui means 550/620/650/680 are mid-axis picks to snap.wght/wdthaxis values that bypassfont-weight(e.g.chat-message.css:88"wght" 550, "wdth" 105); fold axis weights into the font-weight token.var(--mono-font, monospace)×1, inconsistent mono fallback ×2,Georgia, serif×1 — fold in here.PR 2 — visual state & motion tokens (P1)
Non-typography visual numeric convergence. Same type, same verification (dual-theme screenshot).
--focus-ringis color-only; tokenize ring width/offset and convergeoutline/outline-offset/ring-offset-*mixed usage into one recipe.transition-duration/shorthand ms; fix the undefined--duration-fastreference (token file has no such token); tokenizetransform:amplitude (press scale, slide distance) so motion contract covers duration + amplitude, not just easing.PR 3 — anti-layout-shift contracts (P1)
Three "fill + contract" items preventing text/number width changes from breaking layout. Same type (audit missing sites + contract), same verification (find-the-gaps).
tabular-nums; contract flags numeric containers missing it.min-widthlocked to the widest state; contract to prevent chat layout-shift.min-w-0to truncate; flags missing sites that caused historic truncation bugs.PR 4 — layout surface & sizing tokens (P2)
Layout visual rules + sizing tokenization. Mixed type (rule systematization + numeric converge), same verification (nesting audit + screenshot).
pre.box-shadowoutside recipe refs onto--shadow-*recipes.border-width/border-style(only color is tokenized today); converge ~266 sites.width/height/min-height/max-width+ control heights (h-9/h-10+34/38/52px); align Button/Input/Badgesizesystems onto one scale. (Recorded in refactor(ui): converge design-token magic numbers (motion, typography, spacing) #430 as a separate track; folded here.)@media (max-width: …)breakpoints,width: min(…), modal/chat-measure widths. (spacing-contract explicitly excludes width/inset/grid.)PR 5 — style-hook convention + small Base UI migrations (P3)
The cross-cutting style-hook convention (the foundation the other migrations follow) + the smaller, more mechanical Base UI adoptions. Same verification (migration + consistency audit + screenshot).
data-slotto allui.tsxBase wrappers (primitives have it,ui.tsxwrappers don't); (2) adopt Base UI's native boolean state attributes (attribute-presence[data-active]/[data-open]/[data-checked]/[data-selected]) — maka's renderer CSS has zero state-attribute selectors today, so there is nothing to break and no[data-active="true"]override to maintain; (3) document a per-component state hook map (Tabsdata-active/ Selectdata-selected/ Checkboxdata-checked/ popupdata-openetc.) as a doc comment in the barrel; (4) whitelist CSS var hooks (--anchor-*/--available-*/--active-tab-*); (5) decideclassName(state)function form only if a migration in this PR actually needs it (defer otherwise).<details>sites (reasoning/turn-thinking/tool-activity/permission-raw — all independent single sections, not grouped, so Collapsible not Accordion) to Base UI Collapsible (code comments already say "future Base UI Accordion path").Tooltipwrapper (primitives/tooltip.tsxwithdata-slot); migrate the clearest icon-only-action buttontitle=usages (app-shell-chrome-actions 7, browser-panel 4, artifact-pane collapse) — 13 sites. The longer tail (truncation spans, SelectTrigger titles, status-badge icons, OnboardingHero submit) are tooltip-eligible but need per-site label-vs-tooltip judgment and are deferred to a follow-up;title=on label-prop components (SettingRow/MetricCard/DailyReviewTopList) is NOT a tooltip and stays.Number(...)hand-conversion.primitives/input.tsxalready carries a different structure (adata-slot="input-control"span wrapper + inner Base UI input,ring-[3px]) than the nativeui.tsxInput(single<input>+inputClasses,focus-visible:ring-2), andbare-field-chrome-contract.test.tspins the native'sdata-maka-field-chrome+border-input+ring-2. Retiring the native is not a clean look-preserving swap — it needsinputClassesported intoprimitives/input.tsx, InputGroup updated to avoid double chrome, and the chrome contract rewritten — so it is its own focused PR.PR 6 — toast + alert-dialog (P3)
The toast system + confirm dialog, split out because the toast rewrite is a sizable verification boundary of its own (auto-dismiss timing, focus, a11y).
toast.tsxonto Base UI Toast, or correct the falseui.tsxcomment that claims it already wraps Base UI Toast.toast.confirm()andPermissionDialog's hand-setrole="alertdialog"through Base UI AlertDialog.PR 7 — Base UI large migrations (P3)
Larger Base UI adoptions that each touch broad surfaces. Same type (migration), same verification (migration + screenshot), but separated from PR5 because each is sizable.
role="dialog"+useModalA11ythrough Base UI Dialog/Drawer.aria-activedescendantthrough Base UI Combobox/Autocomplete.Card/Tableprimitives; retire.settingsRows/.settingsMetricCard/.maka-error-card/settingsStatsTable.Badge+PrimitiveBadge+.settingsBadge/.settingsConnectionBadgeonto one.PR 8 — input canonical (P3)
Retire the native
ui.tsxInputonto the Base UIprimitives/input.tsxas the one canonicalInput. Split out of PR5 because it is not a clean look-preserving swap (see the PR5 note):primitives/input.tsxcarries a different structure +ring-[3px], andbare-field-chrome-contract.test.tspins the native'sdata-maka-field-chrome+border-input+ring-2.inputClassesstyling intoprimitives/input.tsxas the default so the 44 usages across 9 files keep their look; update InputGroup to keep its inner input bare (avoid double chrome); rewritebare-field-chrome-contract.test.tsfor the new structure; retire the nativeui.tsxInput; exportprimitives/input.tsxas the canonicalInputfrom the barrel.Out of scope
overlay-scrollbars-contract.test.tslocksprimitives/scroll-area.tsxto OverlayScrollbars (customos-theme-makatheme,autoHide: 'move') and explicitly bans@base-ui/react/scroll-area. OverlayScrollbars is a deliberate choice; "全面使用 Base UI" stops here.