Skip to content

refactor(ui): converge layout-surface & sizing tokens (#520 PR4) - #539

Merged
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing
Jul 5, 2026
Merged

refactor(ui): converge layout-surface & sizing tokens (#520 PR4)#539
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

PR4 of #520 — converges the layout-surface & sizing dimensions onto tokens + contracts, using the same converge-contract pattern as PR1 (#526) and PR3 (#527). Five items, one commit each (each independently revertible), plus one commit fixing two pre-existing test regressions from PR3, and five follow-up commits addressing review feedback (reference-chain DFS, mapped-prop completeness, triangle-caret selector allowlist, contract/comment consistency).

The user's "碍眼" — sidebar / 会话 / 设置 control-height inconsistency — is item 15 (control heights), done first.

New tokens (maka-tokens.css)

TokenValueItem
--h-control-xs/sm/md/lg/xl/2xlxs/sm/lg/2xl = var(--space-5/6/8/10), md/xl = calc(var(--spacing) * 7/9) (7 and 9 aren't in maka's discrete spacing scale) = 20/24/28/32/36/40px15 — control-height scale (on the 4px spacing ruler, shared with Tailwind h-N)
--border-width-hairline/thick/accent1px / 2px / 3px14 — border stroke weight
--maka-chat-measure680px (promoted to :root from a local .mainColumn token)16 — chat content measure

No @theme inline bridge this PR: control height is numeric Tailwind (h-N), border-width is static (border = 1px), and breakpoints can't use var() in @media (parse-time evaluation) — see item 16.

Convergence

  • item 15 — control heights: 14 CSS control selectors (sidebar nav row, session row, settings nav/back/select, model switcher, composer send, jump-bottom FAB, palette input, first-run checklist rows) snap off-ruler 22/26/30/34/38px onto --h-control-*; TSX arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] convert to the Tailwind ruler scale (min-h-8, min-h-11, max-h-55, …). The feat(ui): govern chat conversation-flow display onto @maka/ui Base UI primitives #332 chat-marker/preview contracts deliberately pin min-h-[28px] / max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to scale re-tuning) — those stay arbitrary and are whitelisted here.
  • item 14 — border width: 221 border: / border-{side}: shorthand widths across 25 CSS files → var(--border-width-*) (perl with (?<![-\w]) lookbehind so the token definitions aren't self-referenced). Border-style (solid/dashed) stays a literal keyword — a named value, not a magic number. CSS-triangle carets (border-width: 4px 0 4px 5px) are multi-value geometry, not strokes, so the contract allows them only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS) and flags any bare px — single OR multi-value — elsewhere.
  • item 13 — box-shadow color (P-SHADOW): 13 bare pure-black box-shadow usages (rgba(0,0,0,A) / oklch(0 0 0 / A)) → oklch(from var(--foreground) l c h / A), geometry preserved (only the color warms). The dark-mode recipe overrides in maka-tokens.css intentionally keep pure-black (dark canvas) — those are token defs, not box-shadow: usages, so out of scope.
  • item 16 — responsive: --maka-chat-measure promoted to :root, 6 redundant , 680px fallbacks dropped, onboarding hero adopts the token. @media breakpoints can't use var(), so a contract whitelists the 8 values in use (620/720/760/820/900/980/990/1100) and bans ad-hoc Npx.
  • item 12 — radius nesting: documents the concentric-radius rule (inner = outer − padding) on the radius tokens; pins the two calc(var(--radius-modal) - 8px) nesting sites so they don't regress to a hardcoded tier. Audit confirms settings inner cards + sub-modals already comply via the radius-converge SELECTOR_TIER.

New contracts

ContractBansPins
control-height-converge-contract.test.tsbare px height/min-height on 14 curated control selectors (each mapped prop must appear); arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] in TSX (whitelist: dots, count badge, 110px scroll cap, two #332 literals)--h-control-* exactly-once → xs/sm/lg/2xl var(--space-N), md/xl calc(var(--spacing) * N); recursive reference-chain closed (undefined ref / cycle throws)
border-width-converge-contract.test.tsbare px width in border:/border-{side}: shorthand; any bare px in border-width: longhand (single OR multi-value) unless allowlisted caret selector; non-keyword border-style:; arbitrary border-[Npx] in TSX--border-width-hairline/thick/accent exactly-once
box-shadow-converge-contract.test.tspure-black color (oklch(0 0 0/A), rgba(0,0,0,A), #000, black) in box-shadow: (multi-line values; token defs not scanned)--shadow-* / --card-shadow / --card-highlight exist
responsive-breakpoint-contract.test.ts@media (max/min-width: Npx) outside the 8-value whitelist; bare 680px in width/max-width/min-width (height caps spared); local --maka-chat-measure re-declaration--maka-chat-measure: 680px exactly-once
radius-nesting-contract.test.tsradius calc addition (breaks concentricity)the two calc(var(--radius-modal) - 8px) nesting sites

Commits

  1. test(desktop): relax exact className match for PR3 min-width additions — fixes two pre-existing test regressions on main (PR3 added min-w-[Nrem] to copy/append/save buttons but didn't update the exact-className="X" regexes). Unblocks a green baseline for PR4 verification.
  2. refactor(ui): converge control heights onto --h-control-* scale (#520 PR4 item 15)
  3. refactor(ui): converge border-width onto --border-width-* tokens (#520 PR4 item 14)
  4. refactor(ui): systematize concentric radius nesting rule (#520 PR4 item 12)
  5. refactor(ui): converge responsive breakpoints + chat content measure (#520 PR4 item 16)
  6. refactor(ui): converge box-shadow color onto foreground-derived (P-SHADOW) (#520 PR4 item 13)

Review-fix commits

  1. fix(ui): close --h-control-* reference chain + tighten mapped-selector tier check — P1: --h-control-md/xl referenced undefined --space-7/9; use calc(var(--spacing) * 7/9). Adds assertCustomPropRefsDefined helper. P3: mapped selectors must use exactly their --h-control-* tier (no var(--space-N) / chrome-token bypass).
  2. fix(ui): ban multi-value bare-px border-width, allowlist triangle carets by selector — P2: border-width: longhand now flags any bare px (single OR multi-value) unless the selector is an allowlisted caret.
  3. fix(test): require every mapped control-height prop to appear, not any one — P3: checkSelectorTier uses seenProps: Set so a multi-prop selector missing width / height / min-height is flagged.
  4. docs(test): fix stale SINGLE-only border-width comment to match selector-allowlist impl.
  5. fix(test): make assertCustomPropRefsDefined a recursive DFS with cycle detection — P3: the helper now recurses through the whole var() chain (catches undefined 2+ hops down) and detects cycles, not just the first hop.

Existing test expectations updated

PR3 min-width + this PR's token replacements broke exact-match assertions in 7 existing contracts (renderer-error-boundary, daily-review-copy-feedback, command-palette-a11y-copy, artifact-pane-layout, permissions-unified-card, startup-loading-shell, project-context-badge, settings-form-a11y). Each update preserves the test's intent (semantic class present / geometry / alpha) and only shifts the expected literal to the new token form.

Verification

  • Tests: npm run -w @maka/desktop test — 1968/1968 pass (was 1935 on main + 2 pre-existing failures; this PR fixes the 2 + adds 33 new contract assertions across 5 files, including the recursive reference-chain DFS guard).
  • Typecheck: npm run typecheck — every workspace clean.
  • Screenshots vs main (turn-narrative, settings-appearance, first-run × light/dark, compare -metric AE -fuzz 2%):
    • first-run: 0% on items 15/16, +0.00009% on item 14 (onboarding 1.5→1px hairline) — item 13 adds 0.
    • turn-narrative: 0.55–0.62% (the 2px control-height snaps in sidebar/会话/composer).
    • settings-appearance: 1.36% (settings nav rows 38→36, multiple rows; was ~1.6% before the P1 reference-chain fix restored the explicit 36px height on --h-control-xl).
    • All diffs are the deliberate 2px control-height snap or the 1.5→1px hairline; no layout-collapse-scale regression. Item 13 (P-SHADOW color) is within the 2% fuzz on light theme (near-black ≈ pure-black at low alpha) — the fix matters on warm/dark shells where pure-black reads as a smudge.

Deferred / out of scope

  • Full box-shadow recipe-converge: mapping the ~20 already-foreground-derived elevation shadows onto var(--shadow-*) recipes would add the design-system 1px border ring + standardize the blur on each — a visual-weight change to compliant surfaces that needs design review, separate from the P-SHADOW color fix in item 13.
  • index.html splash box-shadows: two pure-black rgba(0,0,0,…) shadows live in the pre-render HTML splash (no access to --foreground); left for a separate splash pass.
  • scroll-area: explicitly out of scope per refactor(ui): converge unmanaged design specs (line-height, font-weight, letter-spacing, …) #520 (overlay-scrollbars-contract.test.ts locks OverlayScrollbars).

Astro-Han added 11 commits July 5, 2026 11:40
PR3 (#527) added min-w-[Nrem] utilities to the error-boundary copy button
and the daily-review copy/append/save buttons (text-swap width lock for
复制中…/已复制 feedback). Two contract tests asserted the semantic class
with an exact className="X" regex that required the closing quote right
after the class, so the extra utility class broke the match and the suites
have been red on main since PR3 merged.
Match each semantic class as a whole word in the class list
(className="[^"]*\bX\b[^"]*"), the same form the daily-review test
already used for its negative maka-button check. This keeps the intent
(button uses the semantic class, not a legacy maka-button) while
tolerating the utility classes PR3 deliberately added.
…PR4 item 15)
The sidebar / 会话 / 设置 control heights had drifted onto off-ruler bare
px (22 / 26 / 30 / 34 / 38) while the TSX side used the 4px spacing ruler
via Tailwind h-N. The two scales never aligned, so a sidebar nav row at
34px next to a session row at 30px next to a settings nav at 38px read as
three different systems — the user's 碍眼.
Add a six-tier --h-control-* scale (xs/sm/md/lg/xl/2xl = 20/24/28/32/36/40)
on the 4px spacing ruler (var(--space-N)) so CSS var(--h-control-*) and
Tailwind h-N share one scale. Snap the off-ruler control heights to the
nearest tier and tokenize the on-ruler ones:
.maka-list-row 30 → 32 (--h-control-lg) 会话 row
.maka-search-modal-close 22 → 24 (--h-control-sm)
.maka-search-modal-clear 22 → 24 (--h-control-sm)
.settingsBackButton 38 → 36 (--h-control-xl)
.settingsNavItem 38 → 36 (--h-control-xl)
.settingsSelectTrigger 34 → 32 (--h-control-lg)
.settingsSelectMenuPopup [role=option] 32 → 32 (--h-control-lg)
.maka-model-switcher-trigger 22 → 24 (--h-control-sm)
.maka-chat-jump-bottom 26 → 28 (--h-control-md)
.maka-palette-input-wrap 32 → 32 (--h-control-lg)
.maka-first-run-checklist-error-action 24 → 24 (--h-control-sm)
.maka-first-run-checklist-row > button 38 → 36 (--h-control-xl)
.maka-composer-send-button 30 → 32 (--h-control-lg)
.maka-composer-workspace-picker 24 → 24 (--h-control-sm)
On the TSX side, replace arbitrary h-[Npx] / min-h-[Npx] / max-h-[Npx] with
the Tailwind ruler scale (min-h-8, min-h-11, h-7, max-h-55, max-h-80,
min-h-21, etc.) so TSX and CSS share the 4px ruler. The #332 chat-marker
and chat-preview cascade contracts deliberately pin min-h-[28px] and
max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to
scale re-tuning); those two stay arbitrary and are whitelisted here, with
#332 retaining governance of them.
control-height-converge-contract.test.ts pins the six tokens exactly-once
to their spacing tier, maps a curated set of control selectors to their
expected tier (the radius-contract SELECTOR_TIER pattern — height has no
single anchor the way border-radius does, so the contract scopes to
control selectors, not every height), and bans bare-numeric arbitrary
h-[Npx] / min-h-[Npx] / max-h-[Npx] in TSX with a small whitelist for the
decorator dots, the count badge, the off-ruler 110px scroll cap, and the
two #332-pinned literals. App-chrome bars (--h-titlebar/--h-toolbar/
--h-composer-min/--h-list-header) and content min/max heights stay bare —
they are structure / content, not controls.
Verification: 1941/1941 desktop tests pass. Screenshots vs main:
first-run 0% diff (untouched), turn-narrative 0.6%, settings-appearance
1.6-1.8% — all consistent with the deliberate 2px control-height snap.
…PR4 item 14)
Border COLOR was already tokenized (--border / --border-strong); the WIDTH
was bare px in every `border: 1px solid var(--border)` shorthand plus a
handful of `border-left: 3px solid …` status strips. Add three semantic
weights on a small scale:
--border-width-hairline 1px the universal divider (210+ sites)
--border-width-thick 2px a heavier divider / selected outline
--border-width-accent 3px a status / decorative strip (toast
variant color bars, avatar rings)
Snap the rare 1.5px hairlines to hairline (3 sites, -0.5px) and the one 4px
avatar ring to accent (1 site, -1px). Border-STYLE (solid / dashed) stays a
literal keyword — it is a named value, not a magic number, so tokenizing it
adds indirection with no governance benefit. CSS-triangle carets
(`border-width: 4px 0 4px 5px`) are multi-value geometry, not border
strokes, so the contract only flags a SINGLE bare-px width.
Replace 221 `border:` / `border-{side}:` shorthand widths across 25 CSS
files with `var(--border-width-*)` (perl with `(?<![-\w])` lookbehind so the
`--border-width-hairline: 1px` token definition is not turned into a
self-reference).
border-width-converge-contract.test.ts pins the three tokens exactly-once;
scans `border:` / `border-{side}:` shorthand for bare px (stripping
var()/calc()/oklch() first so color px is not mistaken for the width);
flags a SINGLE bare-px `border-width:` longhand while sparing multi-value
triangle geometry; enforces `border-style:` keyword literals; and bans
arbitrary `border-[Npx]` / `border-{side}-[Npx]` in TSX (Tailwind `border`
defaults to 1px = hairline, so TSX and CSS agree on the value).
Update four existing contracts that hardcoded `border: 1px solid …` to
match the new token form (artifact-pane, command-palette, permissions-
unified-card, startup-loading-shell).
Verification: 1950/1950 desktop tests pass. Screenshots vs main: turn-
narrative / settings-appearance 0% incremental over item 15, first-run
+375px (0.00009%, the onboarding 1.5→1 hairline). No regression.
…em 12)
Roadmap §1.3 / P-RADIUS: when a rounded surface sits inside another rounded
surface with padding between them, the inner radius = outer radius −
padding so the two curves share a center and read as one machined shell.
Document the convention on the radius tokens in maka-tokens.css with the
two forms it takes:
1. outer − padding lands on a tier → pick that tier directly (e.g. an
8px surface card inside a 12px modal with 4px padding: 12 − 4 = 8).
The radius-converge contract SELECTOR_TIER already pins this.
2. outer − padding does NOT land on a tier → use
`calc(var(--radius-*) - Npx)` (the radius-converge calc allowlist
permits only this shrink form). Two sites use it today — an input
inside a 12px modal shell with an 8px inset (12 − 8 = 4px, not a
tier): .maka-search-modal-input-row and .maka-palette-input-wrap.
Audit conclusion: the settings-modal inner cards use the surface/control
tier inside the modal shell (form 1, governed by SELECTOR_TIER), and the
seven settings inner surfaces that keep --radius-modal are peer sub-modals
(login modal, scan modal, select popup), not nested cards — so no nesting
violations and no CSS value changes are needed.
radius-nesting-contract.test.ts pins the two calc-nested input sites so a
later cleanup can't drop the calc and revert to a hardcoded --radius-control
(6px) that would read too round against the 12px shell corners, and
restates the shrink-only rule (no calc addition) for the nesting sites.
Verification: 1952/1952 desktop tests pass. No CSS values changed → no
visual regression (screenshots unchanged from item 14).
…520 PR4 item 16)
Two responsive seams had drifted:
1. @media breakpoints. CSS @media queries evaluate at parse time, before
custom properties resolve, so `@media (max-width: var(--bp))` is INVALID
— breakpoints cannot be tokenized with var(). Instead a contract
whitelists the eight max/min-width pixel values the app actually uses
(620 / 720 / 760 / 820 / 900 / 980 / 990 / 1100) and bans any other bare
`@media (max-width: Npx)`. A new breakpoint must be added to the
whitelist, which forces a conscious decision instead of a silent drift.
prefers-reduced-motion / prefers-color-scheme are not width breakpoints
and stay out of scope.
2. The chat content measure (--maka-chat-measure: 680px). This IS a regular
property value, so it can be tokenized. It was a LOCAL token on
.mainColumn with a `680px` fallback at every call site; promote it to
:root in maka-tokens.css so it is canonical, drop the redundant
`, 680px` fallbacks at six call sites, and adopt it in the onboarding
hero (was a bare `width: min(680px, 100%)`). The chat column, tool
output, composer, and onboarding hero now share one measure with no
fallback. A 680px HEIGHT cap on the settings form modal is a different
semantic and stays bare — the contract scopes the ban to width.
responsive-breakpoint-contract.test.ts whitelists the breakpoints, pins
--maka-chat-measure to 680px exactly-once in maka-tokens.css, bans a local
re-declaration in styles/, and bans a bare 680px in width / max-width /
min-width declarations.
Update project-context-badge.test.ts to match the no-fallback
composer-workspace-row width formula.
Verification: 1959/1959 desktop tests pass. No CSS values changed (the
chat-measure move is functionally a no-op — every site already resolved to
680px via the local def or the fallback) → first-run screenshot unchanged
from item 14 (375px / 0.00009%, the onboarding 1.5→1 hairline).
…ADOW) (#520 PR4 item 13)
P-SHADOW (roadmap §1.2 / maka-tokens.css): blur layers in box-shadow must
derive from --foreground, not pure black — a pure-black rgba()/oklch()
shadow on maka's warm shell reads as a dirty smudge, while a foreground-
derived shadow shares one light source with the border ring and ink.
Thirteen bare pure-black box-shadow usages (rgba(0,0,0,A) and
oklch(0 0 0 / A)) survived across settings, plan-reminders, and
reference-shell. Swap each pure-black color for
oklch(from var(--foreground) l c h / A), KEEPING the shadow geometry
(offset / blur / spread) intact so only the color warms — the safe
P-SHADOW win. The dark-mode shadow recipe overrides in maka-tokens.css
(--shadow-medium / --shadow-modal for dark mode) intentionally keep
pure-black oklch(0 0 0 / 0.5|0.6) — on a dark canvas a pure-black shadow is
correct ("dark mode shadows collapse to a single ring; modal keeps one
deep drop") — those are token definitions, not box-shadow usages, so they
are out of scope.
The broader recipe-converge (mapping the ~20 already-foreground-derived
elevation shadows onto var(--shadow-*) recipes) is deferred: the recipes
add a 1px border ring + standardize the blur, which changes the visual
weight of compliant surfaces — a design-review change, separate from the
P-SHADOW color fix here. The themed colored skill-card shadow
(oklch(0.2 0.02 224 / 0.4)) and the hard 1px blur-0 hover edge
(0 1px 0 0 ...) are not pure-black and are not flagged.
box-shadow-converge-contract.test.ts bans a pure-black color
(oklch(0 0 0 / A) / rgba(0,0,0,A) / #000 / black with non-zero alpha) in
any box-shadow declaration (multi-line values captured), and confirms the
shadow recipes are defined. It does not scan --shadow-*: token
definitions, so the intentional dark-mode pure-black recipes are not
flagged.
Update settings-form-a11y-contract.test.ts: the three connection / auth-
contract / model-table near-flat shadow assertions pinned the pure-black
rgba form as the "reference implementation" shadow; P-SHADOW supersedes
that — the geometry (0 1px 3px) and alpha (0.03) are unchanged, only the
color form moves to foreground-derived.
Verification: 1965/1965 desktop tests pass. Screenshots vs main: 0%
incremental over item 14/15/16 (the color warm-up is within the 2% fuzz on
light theme — near-black and pure-black at low alpha are visually
identical there; the fix matters on warm/dark shells where pure-black
reads as a smudge).
…r tier check (#520 PR4 review)
P1: --h-control-md / --h-control-xl referenced --space-7 / --space-9, which
are NOT in maka's discrete spacing scale (it defines --space-0/1/2/3/4/5/6/
8/10/12/16 — 7 and 9 are skipped). A var() with no fallback resolving to an
undefined custom prop is invalid at computed-value time, so the jump-bottom
FAB (md=28px) and settings nav/back rows (xl=36px) collapsed to width/height
auto / min-height 0 — masked by their padding/content, but fragile and wrong.
Fix with calc(var(--spacing) * 7) / calc(var(--spacing) * 9) (the --spacing
base step IS defined, = 4px), so 28/36px resolve explicitly without expanding
the general --space-* scale (which would invite p-7 / gap-7 drift).
Add assertCustomPropRefsDefined to css-test-helpers: walk the var(--xxx)
reference chain in a token's value and assert every referenced custom prop is
defined somewhere in the tokens file (token :root, @theme inline, or bridge
alias). A pin-only check that --h-control-md is declared with var(--space-7)
passes while the token is broken — this helper catches the bug class. Add a
contract test that runs it on every --h-control-* tier, plus a negative case
that feeds a --space-7 reference and asserts it throws.
P3: isAllowedControlHeight previously let a MAPPED selector pass with a
direct var(--space-N), calc(var(--spacing) * N), or a layout-chrome token
(--h-titlebar / --maka-sidebar-topbar-button-size / …) instead of its
expected --h-control-* tier — bypassing the semantic scale the mapping table
exists to enforce. Tighten: a mapped selector's height / min-height / width
must be exactly var(--h-control-<expected>) or a neutral literal (0 / auto /
100%). Unmapped controls are added to CONTROL_HEIGHT rather than allowed to
slip via a space token. No current mapped selector uses a bypass value, so
nothing real is flagged; the negative cases now assert the bypass forms fail.
Verification: 1967/1967 desktop tests pass (was 1965; +1 ref-chain test, +1
ref-chain negative case). Screenshots vs main: settings-appearance diff drops
0.0159 → 0.0136 (the settings nav rows restore to explicit 36px instead of
the padding-masked auto height); turn-narrative unchanged (the FAB collapse
was already masked by its icon + padding). typecheck clean.
…ets by selector (#520 PR4 review)
P2: the border-width contract only flagged a SINGLE bare-px value in a
border-width: longhand (SINGLE_BARE_PX_RE). A multi-value form like
border-width: 1px 2px (a non-uniform stroke, NOT a triangle) was spared — a
false negative a future drift could hide behind. The triangle-carets-are-
multi-value heuristic spared ALL multi-value, which is the hole.
Fix: flag ANY bare px in a border-width: longhand (single OR multi-value),
and allowlist the three known triangle/caret SELECTORS (.maka-turn-thinking
summary::before, .maka-bubble-assistant …checkbox:checked::after,
.maka-permission-raw > summary::before) whose multi-value is geometry, not a
stroke. Selector tracking walks the line before { to keep the current
selector; a new caret with a new value on a new selector is flagged until
explicitly added to the allowlist (correct — review new carets consciously).
Add negative cases: border-width: 1px 2px on a non-allowlisted selector must
fail; the three allowlisted caret selectors must pass; single bare-px still
fails; token / 0 still pass.
Verification: 1967/1967 desktop tests pass. The real renderer CSS scan stays
clean (the only three border-width: longhands are the allowlisted carets).
…y one (#520 PR4 review)
checkSelectorTier used a single checkedAny flag — if a selector was mapped
with multiple REQUIRED props (e.g. .maka-chat-jump-bottom [width, height],
.settingsNavItem [height, min-height]), the contract only checked that AT
LEAST ONE appeared. Deleting the width declaration but keeping height passed
silently, dropping the square-control width requirement.
Replace checkedAny with seenProps: Set<string>, accumulated across all
matched blocks (base + @media / :state variants), and require every prop in
check.props to appear at least once — a missing required prop is flagged as
'is missing required <prop> declaration'. Each declaration that does appear
is still value-checked against the expected tier. No current mapped selector
is missing a prop, so nothing real is flagged.
Add a negative case: a .maka-chat-jump-bottom fixture with only height must
flag the missing width; a .settingsNavItem fixture missing height OR
min-height must flag; a complete fixture passes.
Verification: 1968/1968 desktop tests pass.
…tor-allowlist impl (#520 PR4 review)
The previous review fix changed border-width longhand scanning from 'only
flag a SINGLE bare-px value' to 'flag ANY bare px (single OR multi-value)
unless the selector is an allowlisted triangle/caret', but the top summary
paragraph still described the old single-only rule — comment, impl, and
negative cases disagreed.
Update the top summary to match: triangle carets are multi-value geometry
allowed only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS), and
any bare px — single OR multi-value — is flagged elsewhere. The item-2
invariant and inline comment already said this; only the summary was stale.
Verification: 1968/1968 desktop tests pass; non-allowlisted border-width:
1px 2px continues to fail.
…e detection (#520 PR4 review)
The helper only checked the FIRST hop: it collected var(--xxx) refs in the
target prop's value and checked each was defined, but did not recurse into a
ref's own value. A chain like --h-control-xs → --space-5 → --missing
(--missing undefined two hops out) passed because --space-5 was defined and
the helper stopped there. It also had no cycle detection (--a → --b → --a
would infinite-loop).
Rewrite as a small DFS from the target prop through every var() ref,
maintaining visiting/visited sets: an undefined ref at any depth throws
'references undefined <name> (via <path>)', and a back-edge to a node on the
current path throws 'circular custom-prop reference: a → b → a'. Each node
must still be declared exactly once. Function name kept; the doc comment now
says 'recursively, with cycle detection' and means it.
Add negative cases: --h-control-xs → --space-5 → --missing must fail (the
2-hop case the old helper missed); --a → --b → --a must fail (cycle). The
direct --space-7 case and the valid closed chain still behave as before.
Verification: 1968/1968 desktop tests pass; typecheck clean. The real
--h-control-* chain (xs/sm/lg/2xl → --space-N → calc(var(--spacing) * N) →
--spacing: 4px; md/xl → calc(var(--spacing) * N) → --spacing) still passes.
@Astro-Han
Astro-Han merged commit 0954d9b into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the refactor/layout-surface-sizing branch July 5, 2026 05:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
refactor(ui): converge layout-surface & sizing tokens (#520 PR4) by Astro-Han · Pull Request #539 · apache/maka · GitHub
Skip to content

refactor(ui): converge layout-surface & sizing tokens (#520 PR4) - #539

Merged
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing
Jul 5, 2026
Merged

refactor(ui): converge layout-surface & sizing tokens (#520 PR4)#539
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

PR4 of #520 — converges the layout-surface & sizing dimensions onto tokens + contracts, using the same converge-contract pattern as PR1 (#526) and PR3 (#527). Five items, one commit each (each independently revertible), plus one commit fixing two pre-existing test regressions from PR3, and five follow-up commits addressing review feedback (reference-chain DFS, mapped-prop completeness, triangle-caret selector allowlist, contract/comment consistency).

The user's "碍眼" — sidebar / 会话 / 设置 control-height inconsistency — is item 15 (control heights), done first.

New tokens (maka-tokens.css)

TokenValueItem
--h-control-xs/sm/md/lg/xl/2xlxs/sm/lg/2xl = var(--space-5/6/8/10), md/xl = calc(var(--spacing) * 7/9) (7 and 9 aren't in maka's discrete spacing scale) = 20/24/28/32/36/40px15 — control-height scale (on the 4px spacing ruler, shared with Tailwind h-N)
--border-width-hairline/thick/accent1px / 2px / 3px14 — border stroke weight
--maka-chat-measure680px (promoted to :root from a local .mainColumn token)16 — chat content measure

No @theme inline bridge this PR: control height is numeric Tailwind (h-N), border-width is static (border = 1px), and breakpoints can't use var() in @media (parse-time evaluation) — see item 16.

Convergence

  • item 15 — control heights: 14 CSS control selectors (sidebar nav row, session row, settings nav/back/select, model switcher, composer send, jump-bottom FAB, palette input, first-run checklist rows) snap off-ruler 22/26/30/34/38px onto --h-control-*; TSX arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] convert to the Tailwind ruler scale (min-h-8, min-h-11, max-h-55, …). The feat(ui): govern chat conversation-flow display onto @maka/ui Base UI primitives #332 chat-marker/preview contracts deliberately pin min-h-[28px] / max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to scale re-tuning) — those stay arbitrary and are whitelisted here.
  • item 14 — border width: 221 border: / border-{side}: shorthand widths across 25 CSS files → var(--border-width-*) (perl with (?<![-\w]) lookbehind so the token definitions aren't self-referenced). Border-style (solid/dashed) stays a literal keyword — a named value, not a magic number. CSS-triangle carets (border-width: 4px 0 4px 5px) are multi-value geometry, not strokes, so the contract allows them only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS) and flags any bare px — single OR multi-value — elsewhere.
  • item 13 — box-shadow color (P-SHADOW): 13 bare pure-black box-shadow usages (rgba(0,0,0,A) / oklch(0 0 0 / A)) → oklch(from var(--foreground) l c h / A), geometry preserved (only the color warms). The dark-mode recipe overrides in maka-tokens.css intentionally keep pure-black (dark canvas) — those are token defs, not box-shadow: usages, so out of scope.
  • item 16 — responsive: --maka-chat-measure promoted to :root, 6 redundant , 680px fallbacks dropped, onboarding hero adopts the token. @media breakpoints can't use var(), so a contract whitelists the 8 values in use (620/720/760/820/900/980/990/1100) and bans ad-hoc Npx.
  • item 12 — radius nesting: documents the concentric-radius rule (inner = outer − padding) on the radius tokens; pins the two calc(var(--radius-modal) - 8px) nesting sites so they don't regress to a hardcoded tier. Audit confirms settings inner cards + sub-modals already comply via the radius-converge SELECTOR_TIER.

New contracts

ContractBansPins
control-height-converge-contract.test.tsbare px height/min-height on 14 curated control selectors (each mapped prop must appear); arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] in TSX (whitelist: dots, count badge, 110px scroll cap, two #332 literals)--h-control-* exactly-once → xs/sm/lg/2xl var(--space-N), md/xl calc(var(--spacing) * N); recursive reference-chain closed (undefined ref / cycle throws)
border-width-converge-contract.test.tsbare px width in border:/border-{side}: shorthand; any bare px in border-width: longhand (single OR multi-value) unless allowlisted caret selector; non-keyword border-style:; arbitrary border-[Npx] in TSX--border-width-hairline/thick/accent exactly-once
box-shadow-converge-contract.test.tspure-black color (oklch(0 0 0/A), rgba(0,0,0,A), #000, black) in box-shadow: (multi-line values; token defs not scanned)--shadow-* / --card-shadow / --card-highlight exist
responsive-breakpoint-contract.test.ts@media (max/min-width: Npx) outside the 8-value whitelist; bare 680px in width/max-width/min-width (height caps spared); local --maka-chat-measure re-declaration--maka-chat-measure: 680px exactly-once
radius-nesting-contract.test.tsradius calc addition (breaks concentricity)the two calc(var(--radius-modal) - 8px) nesting sites

Commits

  1. test(desktop): relax exact className match for PR3 min-width additions — fixes two pre-existing test regressions on main (PR3 added min-w-[Nrem] to copy/append/save buttons but didn't update the exact-className="X" regexes). Unblocks a green baseline for PR4 verification.
  2. refactor(ui): converge control heights onto --h-control-* scale (#520 PR4 item 15)
  3. refactor(ui): converge border-width onto --border-width-* tokens (#520 PR4 item 14)
  4. refactor(ui): systematize concentric radius nesting rule (#520 PR4 item 12)
  5. refactor(ui): converge responsive breakpoints + chat content measure (#520 PR4 item 16)
  6. refactor(ui): converge box-shadow color onto foreground-derived (P-SHADOW) (#520 PR4 item 13)

Review-fix commits

  1. fix(ui): close --h-control-* reference chain + tighten mapped-selector tier check — P1: --h-control-md/xl referenced undefined --space-7/9; use calc(var(--spacing) * 7/9). Adds assertCustomPropRefsDefined helper. P3: mapped selectors must use exactly their --h-control-* tier (no var(--space-N) / chrome-token bypass).
  2. fix(ui): ban multi-value bare-px border-width, allowlist triangle carets by selector — P2: border-width: longhand now flags any bare px (single OR multi-value) unless the selector is an allowlisted caret.
  3. fix(test): require every mapped control-height prop to appear, not any one — P3: checkSelectorTier uses seenProps: Set so a multi-prop selector missing width / height / min-height is flagged.
  4. docs(test): fix stale SINGLE-only border-width comment to match selector-allowlist impl.
  5. fix(test): make assertCustomPropRefsDefined a recursive DFS with cycle detection — P3: the helper now recurses through the whole var() chain (catches undefined 2+ hops down) and detects cycles, not just the first hop.

Existing test expectations updated

PR3 min-width + this PR's token replacements broke exact-match assertions in 7 existing contracts (renderer-error-boundary, daily-review-copy-feedback, command-palette-a11y-copy, artifact-pane-layout, permissions-unified-card, startup-loading-shell, project-context-badge, settings-form-a11y). Each update preserves the test's intent (semantic class present / geometry / alpha) and only shifts the expected literal to the new token form.

Verification

  • Tests: npm run -w @maka/desktop test — 1968/1968 pass (was 1935 on main + 2 pre-existing failures; this PR fixes the 2 + adds 33 new contract assertions across 5 files, including the recursive reference-chain DFS guard).
  • Typecheck: npm run typecheck — every workspace clean.
  • Screenshots vs main (turn-narrative, settings-appearance, first-run × light/dark, compare -metric AE -fuzz 2%):
    • first-run: 0% on items 15/16, +0.00009% on item 14 (onboarding 1.5→1px hairline) — item 13 adds 0.
    • turn-narrative: 0.55–0.62% (the 2px control-height snaps in sidebar/会话/composer).
    • settings-appearance: 1.36% (settings nav rows 38→36, multiple rows; was ~1.6% before the P1 reference-chain fix restored the explicit 36px height on --h-control-xl).
    • All diffs are the deliberate 2px control-height snap or the 1.5→1px hairline; no layout-collapse-scale regression. Item 13 (P-SHADOW color) is within the 2% fuzz on light theme (near-black ≈ pure-black at low alpha) — the fix matters on warm/dark shells where pure-black reads as a smudge.

Deferred / out of scope

  • Full box-shadow recipe-converge: mapping the ~20 already-foreground-derived elevation shadows onto var(--shadow-*) recipes would add the design-system 1px border ring + standardize the blur on each — a visual-weight change to compliant surfaces that needs design review, separate from the P-SHADOW color fix in item 13.
  • index.html splash box-shadows: two pure-black rgba(0,0,0,…) shadows live in the pre-render HTML splash (no access to --foreground); left for a separate splash pass.
  • scroll-area: explicitly out of scope per refactor(ui): converge unmanaged design specs (line-height, font-weight, letter-spacing, …) #520 (overlay-scrollbars-contract.test.ts locks OverlayScrollbars).

Astro-Han added 11 commits July 5, 2026 11:40
PR3 (#527) added min-w-[Nrem] utilities to the error-boundary copy button
and the daily-review copy/append/save buttons (text-swap width lock for
复制中…/已复制 feedback). Two contract tests asserted the semantic class
with an exact className="X" regex that required the closing quote right
after the class, so the extra utility class broke the match and the suites
have been red on main since PR3 merged.
Match each semantic class as a whole word in the class list
(className="[^"]*\bX\b[^"]*"), the same form the daily-review test
already used for its negative maka-button check. This keeps the intent
(button uses the semantic class, not a legacy maka-button) while
tolerating the utility classes PR3 deliberately added.
…PR4 item 15)
The sidebar / 会话 / 设置 control heights had drifted onto off-ruler bare
px (22 / 26 / 30 / 34 / 38) while the TSX side used the 4px spacing ruler
via Tailwind h-N. The two scales never aligned, so a sidebar nav row at
34px next to a session row at 30px next to a settings nav at 38px read as
three different systems — the user's 碍眼.
Add a six-tier --h-control-* scale (xs/sm/md/lg/xl/2xl = 20/24/28/32/36/40)
on the 4px spacing ruler (var(--space-N)) so CSS var(--h-control-*) and
Tailwind h-N share one scale. Snap the off-ruler control heights to the
nearest tier and tokenize the on-ruler ones:
.maka-list-row 30 → 32 (--h-control-lg) 会话 row
.maka-search-modal-close 22 → 24 (--h-control-sm)
.maka-search-modal-clear 22 → 24 (--h-control-sm)
.settingsBackButton 38 → 36 (--h-control-xl)
.settingsNavItem 38 → 36 (--h-control-xl)
.settingsSelectTrigger 34 → 32 (--h-control-lg)
.settingsSelectMenuPopup [role=option] 32 → 32 (--h-control-lg)
.maka-model-switcher-trigger 22 → 24 (--h-control-sm)
.maka-chat-jump-bottom 26 → 28 (--h-control-md)
.maka-palette-input-wrap 32 → 32 (--h-control-lg)
.maka-first-run-checklist-error-action 24 → 24 (--h-control-sm)
.maka-first-run-checklist-row > button 38 → 36 (--h-control-xl)
.maka-composer-send-button 30 → 32 (--h-control-lg)
.maka-composer-workspace-picker 24 → 24 (--h-control-sm)
On the TSX side, replace arbitrary h-[Npx] / min-h-[Npx] / max-h-[Npx] with
the Tailwind ruler scale (min-h-8, min-h-11, h-7, max-h-55, max-h-80,
min-h-21, etc.) so TSX and CSS share the 4px ruler. The #332 chat-marker
and chat-preview cascade contracts deliberately pin min-h-[28px] and
max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to
scale re-tuning); those two stay arbitrary and are whitelisted here, with
#332 retaining governance of them.
control-height-converge-contract.test.ts pins the six tokens exactly-once
to their spacing tier, maps a curated set of control selectors to their
expected tier (the radius-contract SELECTOR_TIER pattern — height has no
single anchor the way border-radius does, so the contract scopes to
control selectors, not every height), and bans bare-numeric arbitrary
h-[Npx] / min-h-[Npx] / max-h-[Npx] in TSX with a small whitelist for the
decorator dots, the count badge, the off-ruler 110px scroll cap, and the
two #332-pinned literals. App-chrome bars (--h-titlebar/--h-toolbar/
--h-composer-min/--h-list-header) and content min/max heights stay bare —
they are structure / content, not controls.
Verification: 1941/1941 desktop tests pass. Screenshots vs main:
first-run 0% diff (untouched), turn-narrative 0.6%, settings-appearance
1.6-1.8% — all consistent with the deliberate 2px control-height snap.
…PR4 item 14)
Border COLOR was already tokenized (--border / --border-strong); the WIDTH
was bare px in every `border: 1px solid var(--border)` shorthand plus a
handful of `border-left: 3px solid …` status strips. Add three semantic
weights on a small scale:
--border-width-hairline 1px the universal divider (210+ sites)
--border-width-thick 2px a heavier divider / selected outline
--border-width-accent 3px a status / decorative strip (toast
variant color bars, avatar rings)
Snap the rare 1.5px hairlines to hairline (3 sites, -0.5px) and the one 4px
avatar ring to accent (1 site, -1px). Border-STYLE (solid / dashed) stays a
literal keyword — it is a named value, not a magic number, so tokenizing it
adds indirection with no governance benefit. CSS-triangle carets
(`border-width: 4px 0 4px 5px`) are multi-value geometry, not border
strokes, so the contract only flags a SINGLE bare-px width.
Replace 221 `border:` / `border-{side}:` shorthand widths across 25 CSS
files with `var(--border-width-*)` (perl with `(?<![-\w])` lookbehind so the
`--border-width-hairline: 1px` token definition is not turned into a
self-reference).
border-width-converge-contract.test.ts pins the three tokens exactly-once;
scans `border:` / `border-{side}:` shorthand for bare px (stripping
var()/calc()/oklch() first so color px is not mistaken for the width);
flags a SINGLE bare-px `border-width:` longhand while sparing multi-value
triangle geometry; enforces `border-style:` keyword literals; and bans
arbitrary `border-[Npx]` / `border-{side}-[Npx]` in TSX (Tailwind `border`
defaults to 1px = hairline, so TSX and CSS agree on the value).
Update four existing contracts that hardcoded `border: 1px solid …` to
match the new token form (artifact-pane, command-palette, permissions-
unified-card, startup-loading-shell).
Verification: 1950/1950 desktop tests pass. Screenshots vs main: turn-
narrative / settings-appearance 0% incremental over item 15, first-run
+375px (0.00009%, the onboarding 1.5→1 hairline). No regression.
…em 12)
Roadmap §1.3 / P-RADIUS: when a rounded surface sits inside another rounded
surface with padding between them, the inner radius = outer radius −
padding so the two curves share a center and read as one machined shell.
Document the convention on the radius tokens in maka-tokens.css with the
two forms it takes:
1. outer − padding lands on a tier → pick that tier directly (e.g. an
8px surface card inside a 12px modal with 4px padding: 12 − 4 = 8).
The radius-converge contract SELECTOR_TIER already pins this.
2. outer − padding does NOT land on a tier → use
`calc(var(--radius-*) - Npx)` (the radius-converge calc allowlist
permits only this shrink form). Two sites use it today — an input
inside a 12px modal shell with an 8px inset (12 − 8 = 4px, not a
tier): .maka-search-modal-input-row and .maka-palette-input-wrap.
Audit conclusion: the settings-modal inner cards use the surface/control
tier inside the modal shell (form 1, governed by SELECTOR_TIER), and the
seven settings inner surfaces that keep --radius-modal are peer sub-modals
(login modal, scan modal, select popup), not nested cards — so no nesting
violations and no CSS value changes are needed.
radius-nesting-contract.test.ts pins the two calc-nested input sites so a
later cleanup can't drop the calc and revert to a hardcoded --radius-control
(6px) that would read too round against the 12px shell corners, and
restates the shrink-only rule (no calc addition) for the nesting sites.
Verification: 1952/1952 desktop tests pass. No CSS values changed → no
visual regression (screenshots unchanged from item 14).
…520 PR4 item 16)
Two responsive seams had drifted:
1. @media breakpoints. CSS @media queries evaluate at parse time, before
custom properties resolve, so `@media (max-width: var(--bp))` is INVALID
— breakpoints cannot be tokenized with var(). Instead a contract
whitelists the eight max/min-width pixel values the app actually uses
(620 / 720 / 760 / 820 / 900 / 980 / 990 / 1100) and bans any other bare
`@media (max-width: Npx)`. A new breakpoint must be added to the
whitelist, which forces a conscious decision instead of a silent drift.
prefers-reduced-motion / prefers-color-scheme are not width breakpoints
and stay out of scope.
2. The chat content measure (--maka-chat-measure: 680px). This IS a regular
property value, so it can be tokenized. It was a LOCAL token on
.mainColumn with a `680px` fallback at every call site; promote it to
:root in maka-tokens.css so it is canonical, drop the redundant
`, 680px` fallbacks at six call sites, and adopt it in the onboarding
hero (was a bare `width: min(680px, 100%)`). The chat column, tool
output, composer, and onboarding hero now share one measure with no
fallback. A 680px HEIGHT cap on the settings form modal is a different
semantic and stays bare — the contract scopes the ban to width.
responsive-breakpoint-contract.test.ts whitelists the breakpoints, pins
--maka-chat-measure to 680px exactly-once in maka-tokens.css, bans a local
re-declaration in styles/, and bans a bare 680px in width / max-width /
min-width declarations.
Update project-context-badge.test.ts to match the no-fallback
composer-workspace-row width formula.
Verification: 1959/1959 desktop tests pass. No CSS values changed (the
chat-measure move is functionally a no-op — every site already resolved to
680px via the local def or the fallback) → first-run screenshot unchanged
from item 14 (375px / 0.00009%, the onboarding 1.5→1 hairline).
…ADOW) (#520 PR4 item 13)
P-SHADOW (roadmap §1.2 / maka-tokens.css): blur layers in box-shadow must
derive from --foreground, not pure black — a pure-black rgba()/oklch()
shadow on maka's warm shell reads as a dirty smudge, while a foreground-
derived shadow shares one light source with the border ring and ink.
Thirteen bare pure-black box-shadow usages (rgba(0,0,0,A) and
oklch(0 0 0 / A)) survived across settings, plan-reminders, and
reference-shell. Swap each pure-black color for
oklch(from var(--foreground) l c h / A), KEEPING the shadow geometry
(offset / blur / spread) intact so only the color warms — the safe
P-SHADOW win. The dark-mode shadow recipe overrides in maka-tokens.css
(--shadow-medium / --shadow-modal for dark mode) intentionally keep
pure-black oklch(0 0 0 / 0.5|0.6) — on a dark canvas a pure-black shadow is
correct ("dark mode shadows collapse to a single ring; modal keeps one
deep drop") — those are token definitions, not box-shadow usages, so they
are out of scope.
The broader recipe-converge (mapping the ~20 already-foreground-derived
elevation shadows onto var(--shadow-*) recipes) is deferred: the recipes
add a 1px border ring + standardize the blur, which changes the visual
weight of compliant surfaces — a design-review change, separate from the
P-SHADOW color fix here. The themed colored skill-card shadow
(oklch(0.2 0.02 224 / 0.4)) and the hard 1px blur-0 hover edge
(0 1px 0 0 ...) are not pure-black and are not flagged.
box-shadow-converge-contract.test.ts bans a pure-black color
(oklch(0 0 0 / A) / rgba(0,0,0,A) / #000 / black with non-zero alpha) in
any box-shadow declaration (multi-line values captured), and confirms the
shadow recipes are defined. It does not scan --shadow-*: token
definitions, so the intentional dark-mode pure-black recipes are not
flagged.
Update settings-form-a11y-contract.test.ts: the three connection / auth-
contract / model-table near-flat shadow assertions pinned the pure-black
rgba form as the "reference implementation" shadow; P-SHADOW supersedes
that — the geometry (0 1px 3px) and alpha (0.03) are unchanged, only the
color form moves to foreground-derived.
Verification: 1965/1965 desktop tests pass. Screenshots vs main: 0%
incremental over item 14/15/16 (the color warm-up is within the 2% fuzz on
light theme — near-black and pure-black at low alpha are visually
identical there; the fix matters on warm/dark shells where pure-black
reads as a smudge).
…r tier check (#520 PR4 review)
P1: --h-control-md / --h-control-xl referenced --space-7 / --space-9, which
are NOT in maka's discrete spacing scale (it defines --space-0/1/2/3/4/5/6/
8/10/12/16 — 7 and 9 are skipped). A var() with no fallback resolving to an
undefined custom prop is invalid at computed-value time, so the jump-bottom
FAB (md=28px) and settings nav/back rows (xl=36px) collapsed to width/height
auto / min-height 0 — masked by their padding/content, but fragile and wrong.
Fix with calc(var(--spacing) * 7) / calc(var(--spacing) * 9) (the --spacing
base step IS defined, = 4px), so 28/36px resolve explicitly without expanding
the general --space-* scale (which would invite p-7 / gap-7 drift).
Add assertCustomPropRefsDefined to css-test-helpers: walk the var(--xxx)
reference chain in a token's value and assert every referenced custom prop is
defined somewhere in the tokens file (token :root, @theme inline, or bridge
alias). A pin-only check that --h-control-md is declared with var(--space-7)
passes while the token is broken — this helper catches the bug class. Add a
contract test that runs it on every --h-control-* tier, plus a negative case
that feeds a --space-7 reference and asserts it throws.
P3: isAllowedControlHeight previously let a MAPPED selector pass with a
direct var(--space-N), calc(var(--spacing) * N), or a layout-chrome token
(--h-titlebar / --maka-sidebar-topbar-button-size / …) instead of its
expected --h-control-* tier — bypassing the semantic scale the mapping table
exists to enforce. Tighten: a mapped selector's height / min-height / width
must be exactly var(--h-control-<expected>) or a neutral literal (0 / auto /
100%). Unmapped controls are added to CONTROL_HEIGHT rather than allowed to
slip via a space token. No current mapped selector uses a bypass value, so
nothing real is flagged; the negative cases now assert the bypass forms fail.
Verification: 1967/1967 desktop tests pass (was 1965; +1 ref-chain test, +1
ref-chain negative case). Screenshots vs main: settings-appearance diff drops
0.0159 → 0.0136 (the settings nav rows restore to explicit 36px instead of
the padding-masked auto height); turn-narrative unchanged (the FAB collapse
was already masked by its icon + padding). typecheck clean.
…ets by selector (#520 PR4 review)
P2: the border-width contract only flagged a SINGLE bare-px value in a
border-width: longhand (SINGLE_BARE_PX_RE). A multi-value form like
border-width: 1px 2px (a non-uniform stroke, NOT a triangle) was spared — a
false negative a future drift could hide behind. The triangle-carets-are-
multi-value heuristic spared ALL multi-value, which is the hole.
Fix: flag ANY bare px in a border-width: longhand (single OR multi-value),
and allowlist the three known triangle/caret SELECTORS (.maka-turn-thinking
summary::before, .maka-bubble-assistant …checkbox:checked::after,
.maka-permission-raw > summary::before) whose multi-value is geometry, not a
stroke. Selector tracking walks the line before { to keep the current
selector; a new caret with a new value on a new selector is flagged until
explicitly added to the allowlist (correct — review new carets consciously).
Add negative cases: border-width: 1px 2px on a non-allowlisted selector must
fail; the three allowlisted caret selectors must pass; single bare-px still
fails; token / 0 still pass.
Verification: 1967/1967 desktop tests pass. The real renderer CSS scan stays
clean (the only three border-width: longhands are the allowlisted carets).
…y one (#520 PR4 review)
checkSelectorTier used a single checkedAny flag — if a selector was mapped
with multiple REQUIRED props (e.g. .maka-chat-jump-bottom [width, height],
.settingsNavItem [height, min-height]), the contract only checked that AT
LEAST ONE appeared. Deleting the width declaration but keeping height passed
silently, dropping the square-control width requirement.
Replace checkedAny with seenProps: Set<string>, accumulated across all
matched blocks (base + @media / :state variants), and require every prop in
check.props to appear at least once — a missing required prop is flagged as
'is missing required <prop> declaration'. Each declaration that does appear
is still value-checked against the expected tier. No current mapped selector
is missing a prop, so nothing real is flagged.
Add a negative case: a .maka-chat-jump-bottom fixture with only height must
flag the missing width; a .settingsNavItem fixture missing height OR
min-height must flag; a complete fixture passes.
Verification: 1968/1968 desktop tests pass.
…tor-allowlist impl (#520 PR4 review)
The previous review fix changed border-width longhand scanning from 'only
flag a SINGLE bare-px value' to 'flag ANY bare px (single OR multi-value)
unless the selector is an allowlisted triangle/caret', but the top summary
paragraph still described the old single-only rule — comment, impl, and
negative cases disagreed.
Update the top summary to match: triangle carets are multi-value geometry
allowed only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS), and
any bare px — single OR multi-value — is flagged elsewhere. The item-2
invariant and inline comment already said this; only the summary was stale.
Verification: 1968/1968 desktop tests pass; non-allowlisted border-width:
1px 2px continues to fail.
…e detection (#520 PR4 review)
The helper only checked the FIRST hop: it collected var(--xxx) refs in the
target prop's value and checked each was defined, but did not recurse into a
ref's own value. A chain like --h-control-xs → --space-5 → --missing
(--missing undefined two hops out) passed because --space-5 was defined and
the helper stopped there. It also had no cycle detection (--a → --b → --a
would infinite-loop).
Rewrite as a small DFS from the target prop through every var() ref,
maintaining visiting/visited sets: an undefined ref at any depth throws
'references undefined <name> (via <path>)', and a back-edge to a node on the
current path throws 'circular custom-prop reference: a → b → a'. Each node
must still be declared exactly once. Function name kept; the doc comment now
says 'recursively, with cycle detection' and means it.
Add negative cases: --h-control-xs → --space-5 → --missing must fail (the
2-hop case the old helper missed); --a → --b → --a must fail (cycle). The
direct --space-7 case and the valid closed chain still behave as before.
Verification: 1968/1968 desktop tests pass; typecheck clean. The real
--h-control-* chain (xs/sm/lg/2xl → --space-N → calc(var(--spacing) * N) →
--spacing: 4px; md/xl → calc(var(--spacing) * N) → --spacing) still passes.
@Astro-Han
Astro-Han merged commit 0954d9b into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the refactor/layout-surface-sizing branch July 5, 2026 05:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(ui): converge layout-surface & sizing tokens (#520 PR4) by Astro-Han · Pull Request #539 · apache/maka · GitHub
Skip to content

refactor(ui): converge layout-surface & sizing tokens (#520 PR4) - #539

Merged
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing
Jul 5, 2026
Merged

refactor(ui): converge layout-surface & sizing tokens (#520 PR4)#539
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

PR4 of #520 — converges the layout-surface & sizing dimensions onto tokens + contracts, using the same converge-contract pattern as PR1 (#526) and PR3 (#527). Five items, one commit each (each independently revertible), plus one commit fixing two pre-existing test regressions from PR3, and five follow-up commits addressing review feedback (reference-chain DFS, mapped-prop completeness, triangle-caret selector allowlist, contract/comment consistency).

The user's "碍眼" — sidebar / 会话 / 设置 control-height inconsistency — is item 15 (control heights), done first.

New tokens (maka-tokens.css)

TokenValueItem
--h-control-xs/sm/md/lg/xl/2xlxs/sm/lg/2xl = var(--space-5/6/8/10), md/xl = calc(var(--spacing) * 7/9) (7 and 9 aren't in maka's discrete spacing scale) = 20/24/28/32/36/40px15 — control-height scale (on the 4px spacing ruler, shared with Tailwind h-N)
--border-width-hairline/thick/accent1px / 2px / 3px14 — border stroke weight
--maka-chat-measure680px (promoted to :root from a local .mainColumn token)16 — chat content measure

No @theme inline bridge this PR: control height is numeric Tailwind (h-N), border-width is static (border = 1px), and breakpoints can't use var() in @media (parse-time evaluation) — see item 16.

Convergence

  • item 15 — control heights: 14 CSS control selectors (sidebar nav row, session row, settings nav/back/select, model switcher, composer send, jump-bottom FAB, palette input, first-run checklist rows) snap off-ruler 22/26/30/34/38px onto --h-control-*; TSX arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] convert to the Tailwind ruler scale (min-h-8, min-h-11, max-h-55, …). The feat(ui): govern chat conversation-flow display onto @maka/ui Base UI primitives #332 chat-marker/preview contracts deliberately pin min-h-[28px] / max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to scale re-tuning) — those stay arbitrary and are whitelisted here.
  • item 14 — border width: 221 border: / border-{side}: shorthand widths across 25 CSS files → var(--border-width-*) (perl with (?<![-\w]) lookbehind so the token definitions aren't self-referenced). Border-style (solid/dashed) stays a literal keyword — a named value, not a magic number. CSS-triangle carets (border-width: 4px 0 4px 5px) are multi-value geometry, not strokes, so the contract allows them only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS) and flags any bare px — single OR multi-value — elsewhere.
  • item 13 — box-shadow color (P-SHADOW): 13 bare pure-black box-shadow usages (rgba(0,0,0,A) / oklch(0 0 0 / A)) → oklch(from var(--foreground) l c h / A), geometry preserved (only the color warms). The dark-mode recipe overrides in maka-tokens.css intentionally keep pure-black (dark canvas) — those are token defs, not box-shadow: usages, so out of scope.
  • item 16 — responsive: --maka-chat-measure promoted to :root, 6 redundant , 680px fallbacks dropped, onboarding hero adopts the token. @media breakpoints can't use var(), so a contract whitelists the 8 values in use (620/720/760/820/900/980/990/1100) and bans ad-hoc Npx.
  • item 12 — radius nesting: documents the concentric-radius rule (inner = outer − padding) on the radius tokens; pins the two calc(var(--radius-modal) - 8px) nesting sites so they don't regress to a hardcoded tier. Audit confirms settings inner cards + sub-modals already comply via the radius-converge SELECTOR_TIER.

New contracts

ContractBansPins
control-height-converge-contract.test.tsbare px height/min-height on 14 curated control selectors (each mapped prop must appear); arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] in TSX (whitelist: dots, count badge, 110px scroll cap, two #332 literals)--h-control-* exactly-once → xs/sm/lg/2xl var(--space-N), md/xl calc(var(--spacing) * N); recursive reference-chain closed (undefined ref / cycle throws)
border-width-converge-contract.test.tsbare px width in border:/border-{side}: shorthand; any bare px in border-width: longhand (single OR multi-value) unless allowlisted caret selector; non-keyword border-style:; arbitrary border-[Npx] in TSX--border-width-hairline/thick/accent exactly-once
box-shadow-converge-contract.test.tspure-black color (oklch(0 0 0/A), rgba(0,0,0,A), #000, black) in box-shadow: (multi-line values; token defs not scanned)--shadow-* / --card-shadow / --card-highlight exist
responsive-breakpoint-contract.test.ts@media (max/min-width: Npx) outside the 8-value whitelist; bare 680px in width/max-width/min-width (height caps spared); local --maka-chat-measure re-declaration--maka-chat-measure: 680px exactly-once
radius-nesting-contract.test.tsradius calc addition (breaks concentricity)the two calc(var(--radius-modal) - 8px) nesting sites

Commits

  1. test(desktop): relax exact className match for PR3 min-width additions — fixes two pre-existing test regressions on main (PR3 added min-w-[Nrem] to copy/append/save buttons but didn't update the exact-className="X" regexes). Unblocks a green baseline for PR4 verification.
  2. refactor(ui): converge control heights onto --h-control-* scale (#520 PR4 item 15)
  3. refactor(ui): converge border-width onto --border-width-* tokens (#520 PR4 item 14)
  4. refactor(ui): systematize concentric radius nesting rule (#520 PR4 item 12)
  5. refactor(ui): converge responsive breakpoints + chat content measure (#520 PR4 item 16)
  6. refactor(ui): converge box-shadow color onto foreground-derived (P-SHADOW) (#520 PR4 item 13)

Review-fix commits

  1. fix(ui): close --h-control-* reference chain + tighten mapped-selector tier check — P1: --h-control-md/xl referenced undefined --space-7/9; use calc(var(--spacing) * 7/9). Adds assertCustomPropRefsDefined helper. P3: mapped selectors must use exactly their --h-control-* tier (no var(--space-N) / chrome-token bypass).
  2. fix(ui): ban multi-value bare-px border-width, allowlist triangle carets by selector — P2: border-width: longhand now flags any bare px (single OR multi-value) unless the selector is an allowlisted caret.
  3. fix(test): require every mapped control-height prop to appear, not any one — P3: checkSelectorTier uses seenProps: Set so a multi-prop selector missing width / height / min-height is flagged.
  4. docs(test): fix stale SINGLE-only border-width comment to match selector-allowlist impl.
  5. fix(test): make assertCustomPropRefsDefined a recursive DFS with cycle detection — P3: the helper now recurses through the whole var() chain (catches undefined 2+ hops down) and detects cycles, not just the first hop.

Existing test expectations updated

PR3 min-width + this PR's token replacements broke exact-match assertions in 7 existing contracts (renderer-error-boundary, daily-review-copy-feedback, command-palette-a11y-copy, artifact-pane-layout, permissions-unified-card, startup-loading-shell, project-context-badge, settings-form-a11y). Each update preserves the test's intent (semantic class present / geometry / alpha) and only shifts the expected literal to the new token form.

Verification

  • Tests: npm run -w @maka/desktop test — 1968/1968 pass (was 1935 on main + 2 pre-existing failures; this PR fixes the 2 + adds 33 new contract assertions across 5 files, including the recursive reference-chain DFS guard).
  • Typecheck: npm run typecheck — every workspace clean.
  • Screenshots vs main (turn-narrative, settings-appearance, first-run × light/dark, compare -metric AE -fuzz 2%):
    • first-run: 0% on items 15/16, +0.00009% on item 14 (onboarding 1.5→1px hairline) — item 13 adds 0.
    • turn-narrative: 0.55–0.62% (the 2px control-height snaps in sidebar/会话/composer).
    • settings-appearance: 1.36% (settings nav rows 38→36, multiple rows; was ~1.6% before the P1 reference-chain fix restored the explicit 36px height on --h-control-xl).
    • All diffs are the deliberate 2px control-height snap or the 1.5→1px hairline; no layout-collapse-scale regression. Item 13 (P-SHADOW color) is within the 2% fuzz on light theme (near-black ≈ pure-black at low alpha) — the fix matters on warm/dark shells where pure-black reads as a smudge.

Deferred / out of scope

  • Full box-shadow recipe-converge: mapping the ~20 already-foreground-derived elevation shadows onto var(--shadow-*) recipes would add the design-system 1px border ring + standardize the blur on each — a visual-weight change to compliant surfaces that needs design review, separate from the P-SHADOW color fix in item 13.
  • index.html splash box-shadows: two pure-black rgba(0,0,0,…) shadows live in the pre-render HTML splash (no access to --foreground); left for a separate splash pass.
  • scroll-area: explicitly out of scope per refactor(ui): converge unmanaged design specs (line-height, font-weight, letter-spacing, …) #520 (overlay-scrollbars-contract.test.ts locks OverlayScrollbars).

Astro-Han added 11 commits July 5, 2026 11:40
PR3 (#527) added min-w-[Nrem] utilities to the error-boundary copy button
and the daily-review copy/append/save buttons (text-swap width lock for
复制中…/已复制 feedback). Two contract tests asserted the semantic class
with an exact className="X" regex that required the closing quote right
after the class, so the extra utility class broke the match and the suites
have been red on main since PR3 merged.
Match each semantic class as a whole word in the class list
(className="[^"]*\bX\b[^"]*"), the same form the daily-review test
already used for its negative maka-button check. This keeps the intent
(button uses the semantic class, not a legacy maka-button) while
tolerating the utility classes PR3 deliberately added.
…PR4 item 15)
The sidebar / 会话 / 设置 control heights had drifted onto off-ruler bare
px (22 / 26 / 30 / 34 / 38) while the TSX side used the 4px spacing ruler
via Tailwind h-N. The two scales never aligned, so a sidebar nav row at
34px next to a session row at 30px next to a settings nav at 38px read as
three different systems — the user's 碍眼.
Add a six-tier --h-control-* scale (xs/sm/md/lg/xl/2xl = 20/24/28/32/36/40)
on the 4px spacing ruler (var(--space-N)) so CSS var(--h-control-*) and
Tailwind h-N share one scale. Snap the off-ruler control heights to the
nearest tier and tokenize the on-ruler ones:
.maka-list-row 30 → 32 (--h-control-lg) 会话 row
.maka-search-modal-close 22 → 24 (--h-control-sm)
.maka-search-modal-clear 22 → 24 (--h-control-sm)
.settingsBackButton 38 → 36 (--h-control-xl)
.settingsNavItem 38 → 36 (--h-control-xl)
.settingsSelectTrigger 34 → 32 (--h-control-lg)
.settingsSelectMenuPopup [role=option] 32 → 32 (--h-control-lg)
.maka-model-switcher-trigger 22 → 24 (--h-control-sm)
.maka-chat-jump-bottom 26 → 28 (--h-control-md)
.maka-palette-input-wrap 32 → 32 (--h-control-lg)
.maka-first-run-checklist-error-action 24 → 24 (--h-control-sm)
.maka-first-run-checklist-row > button 38 → 36 (--h-control-xl)
.maka-composer-send-button 30 → 32 (--h-control-lg)
.maka-composer-workspace-picker 24 → 24 (--h-control-sm)
On the TSX side, replace arbitrary h-[Npx] / min-h-[Npx] / max-h-[Npx] with
the Tailwind ruler scale (min-h-8, min-h-11, h-7, max-h-55, max-h-80,
min-h-21, etc.) so TSX and CSS share the 4px ruler. The #332 chat-marker
and chat-preview cascade contracts deliberately pin min-h-[28px] and
max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to
scale re-tuning); those two stay arbitrary and are whitelisted here, with
#332 retaining governance of them.
control-height-converge-contract.test.ts pins the six tokens exactly-once
to their spacing tier, maps a curated set of control selectors to their
expected tier (the radius-contract SELECTOR_TIER pattern — height has no
single anchor the way border-radius does, so the contract scopes to
control selectors, not every height), and bans bare-numeric arbitrary
h-[Npx] / min-h-[Npx] / max-h-[Npx] in TSX with a small whitelist for the
decorator dots, the count badge, the off-ruler 110px scroll cap, and the
two #332-pinned literals. App-chrome bars (--h-titlebar/--h-toolbar/
--h-composer-min/--h-list-header) and content min/max heights stay bare —
they are structure / content, not controls.
Verification: 1941/1941 desktop tests pass. Screenshots vs main:
first-run 0% diff (untouched), turn-narrative 0.6%, settings-appearance
1.6-1.8% — all consistent with the deliberate 2px control-height snap.
…PR4 item 14)
Border COLOR was already tokenized (--border / --border-strong); the WIDTH
was bare px in every `border: 1px solid var(--border)` shorthand plus a
handful of `border-left: 3px solid …` status strips. Add three semantic
weights on a small scale:
--border-width-hairline 1px the universal divider (210+ sites)
--border-width-thick 2px a heavier divider / selected outline
--border-width-accent 3px a status / decorative strip (toast
variant color bars, avatar rings)
Snap the rare 1.5px hairlines to hairline (3 sites, -0.5px) and the one 4px
avatar ring to accent (1 site, -1px). Border-STYLE (solid / dashed) stays a
literal keyword — it is a named value, not a magic number, so tokenizing it
adds indirection with no governance benefit. CSS-triangle carets
(`border-width: 4px 0 4px 5px`) are multi-value geometry, not border
strokes, so the contract only flags a SINGLE bare-px width.
Replace 221 `border:` / `border-{side}:` shorthand widths across 25 CSS
files with `var(--border-width-*)` (perl with `(?<![-\w])` lookbehind so the
`--border-width-hairline: 1px` token definition is not turned into a
self-reference).
border-width-converge-contract.test.ts pins the three tokens exactly-once;
scans `border:` / `border-{side}:` shorthand for bare px (stripping
var()/calc()/oklch() first so color px is not mistaken for the width);
flags a SINGLE bare-px `border-width:` longhand while sparing multi-value
triangle geometry; enforces `border-style:` keyword literals; and bans
arbitrary `border-[Npx]` / `border-{side}-[Npx]` in TSX (Tailwind `border`
defaults to 1px = hairline, so TSX and CSS agree on the value).
Update four existing contracts that hardcoded `border: 1px solid …` to
match the new token form (artifact-pane, command-palette, permissions-
unified-card, startup-loading-shell).
Verification: 1950/1950 desktop tests pass. Screenshots vs main: turn-
narrative / settings-appearance 0% incremental over item 15, first-run
+375px (0.00009%, the onboarding 1.5→1 hairline). No regression.
…em 12)
Roadmap §1.3 / P-RADIUS: when a rounded surface sits inside another rounded
surface with padding between them, the inner radius = outer radius −
padding so the two curves share a center and read as one machined shell.
Document the convention on the radius tokens in maka-tokens.css with the
two forms it takes:
1. outer − padding lands on a tier → pick that tier directly (e.g. an
8px surface card inside a 12px modal with 4px padding: 12 − 4 = 8).
The radius-converge contract SELECTOR_TIER already pins this.
2. outer − padding does NOT land on a tier → use
`calc(var(--radius-*) - Npx)` (the radius-converge calc allowlist
permits only this shrink form). Two sites use it today — an input
inside a 12px modal shell with an 8px inset (12 − 8 = 4px, not a
tier): .maka-search-modal-input-row and .maka-palette-input-wrap.
Audit conclusion: the settings-modal inner cards use the surface/control
tier inside the modal shell (form 1, governed by SELECTOR_TIER), and the
seven settings inner surfaces that keep --radius-modal are peer sub-modals
(login modal, scan modal, select popup), not nested cards — so no nesting
violations and no CSS value changes are needed.
radius-nesting-contract.test.ts pins the two calc-nested input sites so a
later cleanup can't drop the calc and revert to a hardcoded --radius-control
(6px) that would read too round against the 12px shell corners, and
restates the shrink-only rule (no calc addition) for the nesting sites.
Verification: 1952/1952 desktop tests pass. No CSS values changed → no
visual regression (screenshots unchanged from item 14).
…520 PR4 item 16)
Two responsive seams had drifted:
1. @media breakpoints. CSS @media queries evaluate at parse time, before
custom properties resolve, so `@media (max-width: var(--bp))` is INVALID
— breakpoints cannot be tokenized with var(). Instead a contract
whitelists the eight max/min-width pixel values the app actually uses
(620 / 720 / 760 / 820 / 900 / 980 / 990 / 1100) and bans any other bare
`@media (max-width: Npx)`. A new breakpoint must be added to the
whitelist, which forces a conscious decision instead of a silent drift.
prefers-reduced-motion / prefers-color-scheme are not width breakpoints
and stay out of scope.
2. The chat content measure (--maka-chat-measure: 680px). This IS a regular
property value, so it can be tokenized. It was a LOCAL token on
.mainColumn with a `680px` fallback at every call site; promote it to
:root in maka-tokens.css so it is canonical, drop the redundant
`, 680px` fallbacks at six call sites, and adopt it in the onboarding
hero (was a bare `width: min(680px, 100%)`). The chat column, tool
output, composer, and onboarding hero now share one measure with no
fallback. A 680px HEIGHT cap on the settings form modal is a different
semantic and stays bare — the contract scopes the ban to width.
responsive-breakpoint-contract.test.ts whitelists the breakpoints, pins
--maka-chat-measure to 680px exactly-once in maka-tokens.css, bans a local
re-declaration in styles/, and bans a bare 680px in width / max-width /
min-width declarations.
Update project-context-badge.test.ts to match the no-fallback
composer-workspace-row width formula.
Verification: 1959/1959 desktop tests pass. No CSS values changed (the
chat-measure move is functionally a no-op — every site already resolved to
680px via the local def or the fallback) → first-run screenshot unchanged
from item 14 (375px / 0.00009%, the onboarding 1.5→1 hairline).
…ADOW) (#520 PR4 item 13)
P-SHADOW (roadmap §1.2 / maka-tokens.css): blur layers in box-shadow must
derive from --foreground, not pure black — a pure-black rgba()/oklch()
shadow on maka's warm shell reads as a dirty smudge, while a foreground-
derived shadow shares one light source with the border ring and ink.
Thirteen bare pure-black box-shadow usages (rgba(0,0,0,A) and
oklch(0 0 0 / A)) survived across settings, plan-reminders, and
reference-shell. Swap each pure-black color for
oklch(from var(--foreground) l c h / A), KEEPING the shadow geometry
(offset / blur / spread) intact so only the color warms — the safe
P-SHADOW win. The dark-mode shadow recipe overrides in maka-tokens.css
(--shadow-medium / --shadow-modal for dark mode) intentionally keep
pure-black oklch(0 0 0 / 0.5|0.6) — on a dark canvas a pure-black shadow is
correct ("dark mode shadows collapse to a single ring; modal keeps one
deep drop") — those are token definitions, not box-shadow usages, so they
are out of scope.
The broader recipe-converge (mapping the ~20 already-foreground-derived
elevation shadows onto var(--shadow-*) recipes) is deferred: the recipes
add a 1px border ring + standardize the blur, which changes the visual
weight of compliant surfaces — a design-review change, separate from the
P-SHADOW color fix here. The themed colored skill-card shadow
(oklch(0.2 0.02 224 / 0.4)) and the hard 1px blur-0 hover edge
(0 1px 0 0 ...) are not pure-black and are not flagged.
box-shadow-converge-contract.test.ts bans a pure-black color
(oklch(0 0 0 / A) / rgba(0,0,0,A) / #000 / black with non-zero alpha) in
any box-shadow declaration (multi-line values captured), and confirms the
shadow recipes are defined. It does not scan --shadow-*: token
definitions, so the intentional dark-mode pure-black recipes are not
flagged.
Update settings-form-a11y-contract.test.ts: the three connection / auth-
contract / model-table near-flat shadow assertions pinned the pure-black
rgba form as the "reference implementation" shadow; P-SHADOW supersedes
that — the geometry (0 1px 3px) and alpha (0.03) are unchanged, only the
color form moves to foreground-derived.
Verification: 1965/1965 desktop tests pass. Screenshots vs main: 0%
incremental over item 14/15/16 (the color warm-up is within the 2% fuzz on
light theme — near-black and pure-black at low alpha are visually
identical there; the fix matters on warm/dark shells where pure-black
reads as a smudge).
…r tier check (#520 PR4 review)
P1: --h-control-md / --h-control-xl referenced --space-7 / --space-9, which
are NOT in maka's discrete spacing scale (it defines --space-0/1/2/3/4/5/6/
8/10/12/16 — 7 and 9 are skipped). A var() with no fallback resolving to an
undefined custom prop is invalid at computed-value time, so the jump-bottom
FAB (md=28px) and settings nav/back rows (xl=36px) collapsed to width/height
auto / min-height 0 — masked by their padding/content, but fragile and wrong.
Fix with calc(var(--spacing) * 7) / calc(var(--spacing) * 9) (the --spacing
base step IS defined, = 4px), so 28/36px resolve explicitly without expanding
the general --space-* scale (which would invite p-7 / gap-7 drift).
Add assertCustomPropRefsDefined to css-test-helpers: walk the var(--xxx)
reference chain in a token's value and assert every referenced custom prop is
defined somewhere in the tokens file (token :root, @theme inline, or bridge
alias). A pin-only check that --h-control-md is declared with var(--space-7)
passes while the token is broken — this helper catches the bug class. Add a
contract test that runs it on every --h-control-* tier, plus a negative case
that feeds a --space-7 reference and asserts it throws.
P3: isAllowedControlHeight previously let a MAPPED selector pass with a
direct var(--space-N), calc(var(--spacing) * N), or a layout-chrome token
(--h-titlebar / --maka-sidebar-topbar-button-size / …) instead of its
expected --h-control-* tier — bypassing the semantic scale the mapping table
exists to enforce. Tighten: a mapped selector's height / min-height / width
must be exactly var(--h-control-<expected>) or a neutral literal (0 / auto /
100%). Unmapped controls are added to CONTROL_HEIGHT rather than allowed to
slip via a space token. No current mapped selector uses a bypass value, so
nothing real is flagged; the negative cases now assert the bypass forms fail.
Verification: 1967/1967 desktop tests pass (was 1965; +1 ref-chain test, +1
ref-chain negative case). Screenshots vs main: settings-appearance diff drops
0.0159 → 0.0136 (the settings nav rows restore to explicit 36px instead of
the padding-masked auto height); turn-narrative unchanged (the FAB collapse
was already masked by its icon + padding). typecheck clean.
…ets by selector (#520 PR4 review)
P2: the border-width contract only flagged a SINGLE bare-px value in a
border-width: longhand (SINGLE_BARE_PX_RE). A multi-value form like
border-width: 1px 2px (a non-uniform stroke, NOT a triangle) was spared — a
false negative a future drift could hide behind. The triangle-carets-are-
multi-value heuristic spared ALL multi-value, which is the hole.
Fix: flag ANY bare px in a border-width: longhand (single OR multi-value),
and allowlist the three known triangle/caret SELECTORS (.maka-turn-thinking
summary::before, .maka-bubble-assistant …checkbox:checked::after,
.maka-permission-raw > summary::before) whose multi-value is geometry, not a
stroke. Selector tracking walks the line before { to keep the current
selector; a new caret with a new value on a new selector is flagged until
explicitly added to the allowlist (correct — review new carets consciously).
Add negative cases: border-width: 1px 2px on a non-allowlisted selector must
fail; the three allowlisted caret selectors must pass; single bare-px still
fails; token / 0 still pass.
Verification: 1967/1967 desktop tests pass. The real renderer CSS scan stays
clean (the only three border-width: longhands are the allowlisted carets).
…y one (#520 PR4 review)
checkSelectorTier used a single checkedAny flag — if a selector was mapped
with multiple REQUIRED props (e.g. .maka-chat-jump-bottom [width, height],
.settingsNavItem [height, min-height]), the contract only checked that AT
LEAST ONE appeared. Deleting the width declaration but keeping height passed
silently, dropping the square-control width requirement.
Replace checkedAny with seenProps: Set<string>, accumulated across all
matched blocks (base + @media / :state variants), and require every prop in
check.props to appear at least once — a missing required prop is flagged as
'is missing required <prop> declaration'. Each declaration that does appear
is still value-checked against the expected tier. No current mapped selector
is missing a prop, so nothing real is flagged.
Add a negative case: a .maka-chat-jump-bottom fixture with only height must
flag the missing width; a .settingsNavItem fixture missing height OR
min-height must flag; a complete fixture passes.
Verification: 1968/1968 desktop tests pass.
…tor-allowlist impl (#520 PR4 review)
The previous review fix changed border-width longhand scanning from 'only
flag a SINGLE bare-px value' to 'flag ANY bare px (single OR multi-value)
unless the selector is an allowlisted triangle/caret', but the top summary
paragraph still described the old single-only rule — comment, impl, and
negative cases disagreed.
Update the top summary to match: triangle carets are multi-value geometry
allowed only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS), and
any bare px — single OR multi-value — is flagged elsewhere. The item-2
invariant and inline comment already said this; only the summary was stale.
Verification: 1968/1968 desktop tests pass; non-allowlisted border-width:
1px 2px continues to fail.
…e detection (#520 PR4 review)
The helper only checked the FIRST hop: it collected var(--xxx) refs in the
target prop's value and checked each was defined, but did not recurse into a
ref's own value. A chain like --h-control-xs → --space-5 → --missing
(--missing undefined two hops out) passed because --space-5 was defined and
the helper stopped there. It also had no cycle detection (--a → --b → --a
would infinite-loop).
Rewrite as a small DFS from the target prop through every var() ref,
maintaining visiting/visited sets: an undefined ref at any depth throws
'references undefined <name> (via <path>)', and a back-edge to a node on the
current path throws 'circular custom-prop reference: a → b → a'. Each node
must still be declared exactly once. Function name kept; the doc comment now
says 'recursively, with cycle detection' and means it.
Add negative cases: --h-control-xs → --space-5 → --missing must fail (the
2-hop case the old helper missed); --a → --b → --a must fail (cycle). The
direct --space-7 case and the valid closed chain still behave as before.
Verification: 1968/1968 desktop tests pass; typecheck clean. The real
--h-control-* chain (xs/sm/lg/2xl → --space-N → calc(var(--spacing) * N) →
--spacing: 4px; md/xl → calc(var(--spacing) * N) → --spacing) still passes.
@Astro-Han
Astro-Han merged commit 0954d9b into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the refactor/layout-surface-sizing branch July 5, 2026 05:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(ui): converge layout-surface & sizing tokens (#520 PR4) by Astro-Han · Pull Request #539 · apache/maka · GitHub
Skip to content

refactor(ui): converge layout-surface & sizing tokens (#520 PR4) - #539

Merged
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing
Jul 5, 2026
Merged

refactor(ui): converge layout-surface & sizing tokens (#520 PR4)#539
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

PR4 of #520 — converges the layout-surface & sizing dimensions onto tokens + contracts, using the same converge-contract pattern as PR1 (#526) and PR3 (#527). Five items, one commit each (each independently revertible), plus one commit fixing two pre-existing test regressions from PR3, and five follow-up commits addressing review feedback (reference-chain DFS, mapped-prop completeness, triangle-caret selector allowlist, contract/comment consistency).

The user's "碍眼" — sidebar / 会话 / 设置 control-height inconsistency — is item 15 (control heights), done first.

New tokens (maka-tokens.css)

TokenValueItem
--h-control-xs/sm/md/lg/xl/2xlxs/sm/lg/2xl = var(--space-5/6/8/10), md/xl = calc(var(--spacing) * 7/9) (7 and 9 aren't in maka's discrete spacing scale) = 20/24/28/32/36/40px15 — control-height scale (on the 4px spacing ruler, shared with Tailwind h-N)
--border-width-hairline/thick/accent1px / 2px / 3px14 — border stroke weight
--maka-chat-measure680px (promoted to :root from a local .mainColumn token)16 — chat content measure

No @theme inline bridge this PR: control height is numeric Tailwind (h-N), border-width is static (border = 1px), and breakpoints can't use var() in @media (parse-time evaluation) — see item 16.

Convergence

  • item 15 — control heights: 14 CSS control selectors (sidebar nav row, session row, settings nav/back/select, model switcher, composer send, jump-bottom FAB, palette input, first-run checklist rows) snap off-ruler 22/26/30/34/38px onto --h-control-*; TSX arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] convert to the Tailwind ruler scale (min-h-8, min-h-11, max-h-55, …). The feat(ui): govern chat conversation-flow display onto @maka/ui Base UI primitives #332 chat-marker/preview contracts deliberately pin min-h-[28px] / max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to scale re-tuning) — those stay arbitrary and are whitelisted here.
  • item 14 — border width: 221 border: / border-{side}: shorthand widths across 25 CSS files → var(--border-width-*) (perl with (?<![-\w]) lookbehind so the token definitions aren't self-referenced). Border-style (solid/dashed) stays a literal keyword — a named value, not a magic number. CSS-triangle carets (border-width: 4px 0 4px 5px) are multi-value geometry, not strokes, so the contract allows them only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS) and flags any bare px — single OR multi-value — elsewhere.
  • item 13 — box-shadow color (P-SHADOW): 13 bare pure-black box-shadow usages (rgba(0,0,0,A) / oklch(0 0 0 / A)) → oklch(from var(--foreground) l c h / A), geometry preserved (only the color warms). The dark-mode recipe overrides in maka-tokens.css intentionally keep pure-black (dark canvas) — those are token defs, not box-shadow: usages, so out of scope.
  • item 16 — responsive: --maka-chat-measure promoted to :root, 6 redundant , 680px fallbacks dropped, onboarding hero adopts the token. @media breakpoints can't use var(), so a contract whitelists the 8 values in use (620/720/760/820/900/980/990/1100) and bans ad-hoc Npx.
  • item 12 — radius nesting: documents the concentric-radius rule (inner = outer − padding) on the radius tokens; pins the two calc(var(--radius-modal) - 8px) nesting sites so they don't regress to a hardcoded tier. Audit confirms settings inner cards + sub-modals already comply via the radius-converge SELECTOR_TIER.

New contracts

ContractBansPins
control-height-converge-contract.test.tsbare px height/min-height on 14 curated control selectors (each mapped prop must appear); arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] in TSX (whitelist: dots, count badge, 110px scroll cap, two #332 literals)--h-control-* exactly-once → xs/sm/lg/2xl var(--space-N), md/xl calc(var(--spacing) * N); recursive reference-chain closed (undefined ref / cycle throws)
border-width-converge-contract.test.tsbare px width in border:/border-{side}: shorthand; any bare px in border-width: longhand (single OR multi-value) unless allowlisted caret selector; non-keyword border-style:; arbitrary border-[Npx] in TSX--border-width-hairline/thick/accent exactly-once
box-shadow-converge-contract.test.tspure-black color (oklch(0 0 0/A), rgba(0,0,0,A), #000, black) in box-shadow: (multi-line values; token defs not scanned)--shadow-* / --card-shadow / --card-highlight exist
responsive-breakpoint-contract.test.ts@media (max/min-width: Npx) outside the 8-value whitelist; bare 680px in width/max-width/min-width (height caps spared); local --maka-chat-measure re-declaration--maka-chat-measure: 680px exactly-once
radius-nesting-contract.test.tsradius calc addition (breaks concentricity)the two calc(var(--radius-modal) - 8px) nesting sites

Commits

  1. test(desktop): relax exact className match for PR3 min-width additions — fixes two pre-existing test regressions on main (PR3 added min-w-[Nrem] to copy/append/save buttons but didn't update the exact-className="X" regexes). Unblocks a green baseline for PR4 verification.
  2. refactor(ui): converge control heights onto --h-control-* scale (#520 PR4 item 15)
  3. refactor(ui): converge border-width onto --border-width-* tokens (#520 PR4 item 14)
  4. refactor(ui): systematize concentric radius nesting rule (#520 PR4 item 12)
  5. refactor(ui): converge responsive breakpoints + chat content measure (#520 PR4 item 16)
  6. refactor(ui): converge box-shadow color onto foreground-derived (P-SHADOW) (#520 PR4 item 13)

Review-fix commits

  1. fix(ui): close --h-control-* reference chain + tighten mapped-selector tier check — P1: --h-control-md/xl referenced undefined --space-7/9; use calc(var(--spacing) * 7/9). Adds assertCustomPropRefsDefined helper. P3: mapped selectors must use exactly their --h-control-* tier (no var(--space-N) / chrome-token bypass).
  2. fix(ui): ban multi-value bare-px border-width, allowlist triangle carets by selector — P2: border-width: longhand now flags any bare px (single OR multi-value) unless the selector is an allowlisted caret.
  3. fix(test): require every mapped control-height prop to appear, not any one — P3: checkSelectorTier uses seenProps: Set so a multi-prop selector missing width / height / min-height is flagged.
  4. docs(test): fix stale SINGLE-only border-width comment to match selector-allowlist impl.
  5. fix(test): make assertCustomPropRefsDefined a recursive DFS with cycle detection — P3: the helper now recurses through the whole var() chain (catches undefined 2+ hops down) and detects cycles, not just the first hop.

Existing test expectations updated

PR3 min-width + this PR's token replacements broke exact-match assertions in 7 existing contracts (renderer-error-boundary, daily-review-copy-feedback, command-palette-a11y-copy, artifact-pane-layout, permissions-unified-card, startup-loading-shell, project-context-badge, settings-form-a11y). Each update preserves the test's intent (semantic class present / geometry / alpha) and only shifts the expected literal to the new token form.

Verification

  • Tests: npm run -w @maka/desktop test — 1968/1968 pass (was 1935 on main + 2 pre-existing failures; this PR fixes the 2 + adds 33 new contract assertions across 5 files, including the recursive reference-chain DFS guard).
  • Typecheck: npm run typecheck — every workspace clean.
  • Screenshots vs main (turn-narrative, settings-appearance, first-run × light/dark, compare -metric AE -fuzz 2%):
    • first-run: 0% on items 15/16, +0.00009% on item 14 (onboarding 1.5→1px hairline) — item 13 adds 0.
    • turn-narrative: 0.55–0.62% (the 2px control-height snaps in sidebar/会话/composer).
    • settings-appearance: 1.36% (settings nav rows 38→36, multiple rows; was ~1.6% before the P1 reference-chain fix restored the explicit 36px height on --h-control-xl).
    • All diffs are the deliberate 2px control-height snap or the 1.5→1px hairline; no layout-collapse-scale regression. Item 13 (P-SHADOW color) is within the 2% fuzz on light theme (near-black ≈ pure-black at low alpha) — the fix matters on warm/dark shells where pure-black reads as a smudge.

Deferred / out of scope

  • Full box-shadow recipe-converge: mapping the ~20 already-foreground-derived elevation shadows onto var(--shadow-*) recipes would add the design-system 1px border ring + standardize the blur on each — a visual-weight change to compliant surfaces that needs design review, separate from the P-SHADOW color fix in item 13.
  • index.html splash box-shadows: two pure-black rgba(0,0,0,…) shadows live in the pre-render HTML splash (no access to --foreground); left for a separate splash pass.
  • scroll-area: explicitly out of scope per refactor(ui): converge unmanaged design specs (line-height, font-weight, letter-spacing, …) #520 (overlay-scrollbars-contract.test.ts locks OverlayScrollbars).

Astro-Han added 11 commits July 5, 2026 11:40
PR3 (#527) added min-w-[Nrem] utilities to the error-boundary copy button
and the daily-review copy/append/save buttons (text-swap width lock for
复制中…/已复制 feedback). Two contract tests asserted the semantic class
with an exact className="X" regex that required the closing quote right
after the class, so the extra utility class broke the match and the suites
have been red on main since PR3 merged.
Match each semantic class as a whole word in the class list
(className="[^"]*\bX\b[^"]*"), the same form the daily-review test
already used for its negative maka-button check. This keeps the intent
(button uses the semantic class, not a legacy maka-button) while
tolerating the utility classes PR3 deliberately added.
…PR4 item 15)
The sidebar / 会话 / 设置 control heights had drifted onto off-ruler bare
px (22 / 26 / 30 / 34 / 38) while the TSX side used the 4px spacing ruler
via Tailwind h-N. The two scales never aligned, so a sidebar nav row at
34px next to a session row at 30px next to a settings nav at 38px read as
three different systems — the user's 碍眼.
Add a six-tier --h-control-* scale (xs/sm/md/lg/xl/2xl = 20/24/28/32/36/40)
on the 4px spacing ruler (var(--space-N)) so CSS var(--h-control-*) and
Tailwind h-N share one scale. Snap the off-ruler control heights to the
nearest tier and tokenize the on-ruler ones:
.maka-list-row 30 → 32 (--h-control-lg) 会话 row
.maka-search-modal-close 22 → 24 (--h-control-sm)
.maka-search-modal-clear 22 → 24 (--h-control-sm)
.settingsBackButton 38 → 36 (--h-control-xl)
.settingsNavItem 38 → 36 (--h-control-xl)
.settingsSelectTrigger 34 → 32 (--h-control-lg)
.settingsSelectMenuPopup [role=option] 32 → 32 (--h-control-lg)
.maka-model-switcher-trigger 22 → 24 (--h-control-sm)
.maka-chat-jump-bottom 26 → 28 (--h-control-md)
.maka-palette-input-wrap 32 → 32 (--h-control-lg)
.maka-first-run-checklist-error-action 24 → 24 (--h-control-sm)
.maka-first-run-checklist-row > button 38 → 36 (--h-control-xl)
.maka-composer-send-button 30 → 32 (--h-control-lg)
.maka-composer-workspace-picker 24 → 24 (--h-control-sm)
On the TSX side, replace arbitrary h-[Npx] / min-h-[Npx] / max-h-[Npx] with
the Tailwind ruler scale (min-h-8, min-h-11, h-7, max-h-55, max-h-80,
min-h-21, etc.) so TSX and CSS share the 4px ruler. The #332 chat-marker
and chat-preview cascade contracts deliberately pin min-h-[28px] and
max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to
scale re-tuning); those two stay arbitrary and are whitelisted here, with
#332 retaining governance of them.
control-height-converge-contract.test.ts pins the six tokens exactly-once
to their spacing tier, maps a curated set of control selectors to their
expected tier (the radius-contract SELECTOR_TIER pattern — height has no
single anchor the way border-radius does, so the contract scopes to
control selectors, not every height), and bans bare-numeric arbitrary
h-[Npx] / min-h-[Npx] / max-h-[Npx] in TSX with a small whitelist for the
decorator dots, the count badge, the off-ruler 110px scroll cap, and the
two #332-pinned literals. App-chrome bars (--h-titlebar/--h-toolbar/
--h-composer-min/--h-list-header) and content min/max heights stay bare —
they are structure / content, not controls.
Verification: 1941/1941 desktop tests pass. Screenshots vs main:
first-run 0% diff (untouched), turn-narrative 0.6%, settings-appearance
1.6-1.8% — all consistent with the deliberate 2px control-height snap.
…PR4 item 14)
Border COLOR was already tokenized (--border / --border-strong); the WIDTH
was bare px in every `border: 1px solid var(--border)` shorthand plus a
handful of `border-left: 3px solid …` status strips. Add three semantic
weights on a small scale:
--border-width-hairline 1px the universal divider (210+ sites)
--border-width-thick 2px a heavier divider / selected outline
--border-width-accent 3px a status / decorative strip (toast
variant color bars, avatar rings)
Snap the rare 1.5px hairlines to hairline (3 sites, -0.5px) and the one 4px
avatar ring to accent (1 site, -1px). Border-STYLE (solid / dashed) stays a
literal keyword — it is a named value, not a magic number, so tokenizing it
adds indirection with no governance benefit. CSS-triangle carets
(`border-width: 4px 0 4px 5px`) are multi-value geometry, not border
strokes, so the contract only flags a SINGLE bare-px width.
Replace 221 `border:` / `border-{side}:` shorthand widths across 25 CSS
files with `var(--border-width-*)` (perl with `(?<![-\w])` lookbehind so the
`--border-width-hairline: 1px` token definition is not turned into a
self-reference).
border-width-converge-contract.test.ts pins the three tokens exactly-once;
scans `border:` / `border-{side}:` shorthand for bare px (stripping
var()/calc()/oklch() first so color px is not mistaken for the width);
flags a SINGLE bare-px `border-width:` longhand while sparing multi-value
triangle geometry; enforces `border-style:` keyword literals; and bans
arbitrary `border-[Npx]` / `border-{side}-[Npx]` in TSX (Tailwind `border`
defaults to 1px = hairline, so TSX and CSS agree on the value).
Update four existing contracts that hardcoded `border: 1px solid …` to
match the new token form (artifact-pane, command-palette, permissions-
unified-card, startup-loading-shell).
Verification: 1950/1950 desktop tests pass. Screenshots vs main: turn-
narrative / settings-appearance 0% incremental over item 15, first-run
+375px (0.00009%, the onboarding 1.5→1 hairline). No regression.
…em 12)
Roadmap §1.3 / P-RADIUS: when a rounded surface sits inside another rounded
surface with padding between them, the inner radius = outer radius −
padding so the two curves share a center and read as one machined shell.
Document the convention on the radius tokens in maka-tokens.css with the
two forms it takes:
1. outer − padding lands on a tier → pick that tier directly (e.g. an
8px surface card inside a 12px modal with 4px padding: 12 − 4 = 8).
The radius-converge contract SELECTOR_TIER already pins this.
2. outer − padding does NOT land on a tier → use
`calc(var(--radius-*) - Npx)` (the radius-converge calc allowlist
permits only this shrink form). Two sites use it today — an input
inside a 12px modal shell with an 8px inset (12 − 8 = 4px, not a
tier): .maka-search-modal-input-row and .maka-palette-input-wrap.
Audit conclusion: the settings-modal inner cards use the surface/control
tier inside the modal shell (form 1, governed by SELECTOR_TIER), and the
seven settings inner surfaces that keep --radius-modal are peer sub-modals
(login modal, scan modal, select popup), not nested cards — so no nesting
violations and no CSS value changes are needed.
radius-nesting-contract.test.ts pins the two calc-nested input sites so a
later cleanup can't drop the calc and revert to a hardcoded --radius-control
(6px) that would read too round against the 12px shell corners, and
restates the shrink-only rule (no calc addition) for the nesting sites.
Verification: 1952/1952 desktop tests pass. No CSS values changed → no
visual regression (screenshots unchanged from item 14).
…520 PR4 item 16)
Two responsive seams had drifted:
1. @media breakpoints. CSS @media queries evaluate at parse time, before
custom properties resolve, so `@media (max-width: var(--bp))` is INVALID
— breakpoints cannot be tokenized with var(). Instead a contract
whitelists the eight max/min-width pixel values the app actually uses
(620 / 720 / 760 / 820 / 900 / 980 / 990 / 1100) and bans any other bare
`@media (max-width: Npx)`. A new breakpoint must be added to the
whitelist, which forces a conscious decision instead of a silent drift.
prefers-reduced-motion / prefers-color-scheme are not width breakpoints
and stay out of scope.
2. The chat content measure (--maka-chat-measure: 680px). This IS a regular
property value, so it can be tokenized. It was a LOCAL token on
.mainColumn with a `680px` fallback at every call site; promote it to
:root in maka-tokens.css so it is canonical, drop the redundant
`, 680px` fallbacks at six call sites, and adopt it in the onboarding
hero (was a bare `width: min(680px, 100%)`). The chat column, tool
output, composer, and onboarding hero now share one measure with no
fallback. A 680px HEIGHT cap on the settings form modal is a different
semantic and stays bare — the contract scopes the ban to width.
responsive-breakpoint-contract.test.ts whitelists the breakpoints, pins
--maka-chat-measure to 680px exactly-once in maka-tokens.css, bans a local
re-declaration in styles/, and bans a bare 680px in width / max-width /
min-width declarations.
Update project-context-badge.test.ts to match the no-fallback
composer-workspace-row width formula.
Verification: 1959/1959 desktop tests pass. No CSS values changed (the
chat-measure move is functionally a no-op — every site already resolved to
680px via the local def or the fallback) → first-run screenshot unchanged
from item 14 (375px / 0.00009%, the onboarding 1.5→1 hairline).
…ADOW) (#520 PR4 item 13)
P-SHADOW (roadmap §1.2 / maka-tokens.css): blur layers in box-shadow must
derive from --foreground, not pure black — a pure-black rgba()/oklch()
shadow on maka's warm shell reads as a dirty smudge, while a foreground-
derived shadow shares one light source with the border ring and ink.
Thirteen bare pure-black box-shadow usages (rgba(0,0,0,A) and
oklch(0 0 0 / A)) survived across settings, plan-reminders, and
reference-shell. Swap each pure-black color for
oklch(from var(--foreground) l c h / A), KEEPING the shadow geometry
(offset / blur / spread) intact so only the color warms — the safe
P-SHADOW win. The dark-mode shadow recipe overrides in maka-tokens.css
(--shadow-medium / --shadow-modal for dark mode) intentionally keep
pure-black oklch(0 0 0 / 0.5|0.6) — on a dark canvas a pure-black shadow is
correct ("dark mode shadows collapse to a single ring; modal keeps one
deep drop") — those are token definitions, not box-shadow usages, so they
are out of scope.
The broader recipe-converge (mapping the ~20 already-foreground-derived
elevation shadows onto var(--shadow-*) recipes) is deferred: the recipes
add a 1px border ring + standardize the blur, which changes the visual
weight of compliant surfaces — a design-review change, separate from the
P-SHADOW color fix here. The themed colored skill-card shadow
(oklch(0.2 0.02 224 / 0.4)) and the hard 1px blur-0 hover edge
(0 1px 0 0 ...) are not pure-black and are not flagged.
box-shadow-converge-contract.test.ts bans a pure-black color
(oklch(0 0 0 / A) / rgba(0,0,0,A) / #000 / black with non-zero alpha) in
any box-shadow declaration (multi-line values captured), and confirms the
shadow recipes are defined. It does not scan --shadow-*: token
definitions, so the intentional dark-mode pure-black recipes are not
flagged.
Update settings-form-a11y-contract.test.ts: the three connection / auth-
contract / model-table near-flat shadow assertions pinned the pure-black
rgba form as the "reference implementation" shadow; P-SHADOW supersedes
that — the geometry (0 1px 3px) and alpha (0.03) are unchanged, only the
color form moves to foreground-derived.
Verification: 1965/1965 desktop tests pass. Screenshots vs main: 0%
incremental over item 14/15/16 (the color warm-up is within the 2% fuzz on
light theme — near-black and pure-black at low alpha are visually
identical there; the fix matters on warm/dark shells where pure-black
reads as a smudge).
…r tier check (#520 PR4 review)
P1: --h-control-md / --h-control-xl referenced --space-7 / --space-9, which
are NOT in maka's discrete spacing scale (it defines --space-0/1/2/3/4/5/6/
8/10/12/16 — 7 and 9 are skipped). A var() with no fallback resolving to an
undefined custom prop is invalid at computed-value time, so the jump-bottom
FAB (md=28px) and settings nav/back rows (xl=36px) collapsed to width/height
auto / min-height 0 — masked by their padding/content, but fragile and wrong.
Fix with calc(var(--spacing) * 7) / calc(var(--spacing) * 9) (the --spacing
base step IS defined, = 4px), so 28/36px resolve explicitly without expanding
the general --space-* scale (which would invite p-7 / gap-7 drift).
Add assertCustomPropRefsDefined to css-test-helpers: walk the var(--xxx)
reference chain in a token's value and assert every referenced custom prop is
defined somewhere in the tokens file (token :root, @theme inline, or bridge
alias). A pin-only check that --h-control-md is declared with var(--space-7)
passes while the token is broken — this helper catches the bug class. Add a
contract test that runs it on every --h-control-* tier, plus a negative case
that feeds a --space-7 reference and asserts it throws.
P3: isAllowedControlHeight previously let a MAPPED selector pass with a
direct var(--space-N), calc(var(--spacing) * N), or a layout-chrome token
(--h-titlebar / --maka-sidebar-topbar-button-size / …) instead of its
expected --h-control-* tier — bypassing the semantic scale the mapping table
exists to enforce. Tighten: a mapped selector's height / min-height / width
must be exactly var(--h-control-<expected>) or a neutral literal (0 / auto /
100%). Unmapped controls are added to CONTROL_HEIGHT rather than allowed to
slip via a space token. No current mapped selector uses a bypass value, so
nothing real is flagged; the negative cases now assert the bypass forms fail.
Verification: 1967/1967 desktop tests pass (was 1965; +1 ref-chain test, +1
ref-chain negative case). Screenshots vs main: settings-appearance diff drops
0.0159 → 0.0136 (the settings nav rows restore to explicit 36px instead of
the padding-masked auto height); turn-narrative unchanged (the FAB collapse
was already masked by its icon + padding). typecheck clean.
…ets by selector (#520 PR4 review)
P2: the border-width contract only flagged a SINGLE bare-px value in a
border-width: longhand (SINGLE_BARE_PX_RE). A multi-value form like
border-width: 1px 2px (a non-uniform stroke, NOT a triangle) was spared — a
false negative a future drift could hide behind. The triangle-carets-are-
multi-value heuristic spared ALL multi-value, which is the hole.
Fix: flag ANY bare px in a border-width: longhand (single OR multi-value),
and allowlist the three known triangle/caret SELECTORS (.maka-turn-thinking
summary::before, .maka-bubble-assistant …checkbox:checked::after,
.maka-permission-raw > summary::before) whose multi-value is geometry, not a
stroke. Selector tracking walks the line before { to keep the current
selector; a new caret with a new value on a new selector is flagged until
explicitly added to the allowlist (correct — review new carets consciously).
Add negative cases: border-width: 1px 2px on a non-allowlisted selector must
fail; the three allowlisted caret selectors must pass; single bare-px still
fails; token / 0 still pass.
Verification: 1967/1967 desktop tests pass. The real renderer CSS scan stays
clean (the only three border-width: longhands are the allowlisted carets).
…y one (#520 PR4 review)
checkSelectorTier used a single checkedAny flag — if a selector was mapped
with multiple REQUIRED props (e.g. .maka-chat-jump-bottom [width, height],
.settingsNavItem [height, min-height]), the contract only checked that AT
LEAST ONE appeared. Deleting the width declaration but keeping height passed
silently, dropping the square-control width requirement.
Replace checkedAny with seenProps: Set<string>, accumulated across all
matched blocks (base + @media / :state variants), and require every prop in
check.props to appear at least once — a missing required prop is flagged as
'is missing required <prop> declaration'. Each declaration that does appear
is still value-checked against the expected tier. No current mapped selector
is missing a prop, so nothing real is flagged.
Add a negative case: a .maka-chat-jump-bottom fixture with only height must
flag the missing width; a .settingsNavItem fixture missing height OR
min-height must flag; a complete fixture passes.
Verification: 1968/1968 desktop tests pass.
…tor-allowlist impl (#520 PR4 review)
The previous review fix changed border-width longhand scanning from 'only
flag a SINGLE bare-px value' to 'flag ANY bare px (single OR multi-value)
unless the selector is an allowlisted triangle/caret', but the top summary
paragraph still described the old single-only rule — comment, impl, and
negative cases disagreed.
Update the top summary to match: triangle carets are multi-value geometry
allowed only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS), and
any bare px — single OR multi-value — is flagged elsewhere. The item-2
invariant and inline comment already said this; only the summary was stale.
Verification: 1968/1968 desktop tests pass; non-allowlisted border-width:
1px 2px continues to fail.
…e detection (#520 PR4 review)
The helper only checked the FIRST hop: it collected var(--xxx) refs in the
target prop's value and checked each was defined, but did not recurse into a
ref's own value. A chain like --h-control-xs → --space-5 → --missing
(--missing undefined two hops out) passed because --space-5 was defined and
the helper stopped there. It also had no cycle detection (--a → --b → --a
would infinite-loop).
Rewrite as a small DFS from the target prop through every var() ref,
maintaining visiting/visited sets: an undefined ref at any depth throws
'references undefined <name> (via <path>)', and a back-edge to a node on the
current path throws 'circular custom-prop reference: a → b → a'. Each node
must still be declared exactly once. Function name kept; the doc comment now
says 'recursively, with cycle detection' and means it.
Add negative cases: --h-control-xs → --space-5 → --missing must fail (the
2-hop case the old helper missed); --a → --b → --a must fail (cycle). The
direct --space-7 case and the valid closed chain still behave as before.
Verification: 1968/1968 desktop tests pass; typecheck clean. The real
--h-control-* chain (xs/sm/lg/2xl → --space-N → calc(var(--spacing) * N) →
--spacing: 4px; md/xl → calc(var(--spacing) * N) → --spacing) still passes.
@Astro-Han
Astro-Han merged commit 0954d9b into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the refactor/layout-surface-sizing branch July 5, 2026 05:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' refactor(ui): converge layout-surface & sizing tokens (#520 PR4) by Astro-Han · Pull Request #539 · apache/maka · GitHub
Skip to content

refactor(ui): converge layout-surface & sizing tokens (#520 PR4) - #539

Merged
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing
Jul 5, 2026
Merged

refactor(ui): converge layout-surface & sizing tokens (#520 PR4)#539
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

PR4 of #520 — converges the layout-surface & sizing dimensions onto tokens + contracts, using the same converge-contract pattern as PR1 (#526) and PR3 (#527). Five items, one commit each (each independently revertible), plus one commit fixing two pre-existing test regressions from PR3, and five follow-up commits addressing review feedback (reference-chain DFS, mapped-prop completeness, triangle-caret selector allowlist, contract/comment consistency).

The user's "碍眼" — sidebar / 会话 / 设置 control-height inconsistency — is item 15 (control heights), done first.

New tokens (maka-tokens.css)

TokenValueItem
--h-control-xs/sm/md/lg/xl/2xlxs/sm/lg/2xl = var(--space-5/6/8/10), md/xl = calc(var(--spacing) * 7/9) (7 and 9 aren't in maka's discrete spacing scale) = 20/24/28/32/36/40px15 — control-height scale (on the 4px spacing ruler, shared with Tailwind h-N)
--border-width-hairline/thick/accent1px / 2px / 3px14 — border stroke weight
--maka-chat-measure680px (promoted to :root from a local .mainColumn token)16 — chat content measure

No @theme inline bridge this PR: control height is numeric Tailwind (h-N), border-width is static (border = 1px), and breakpoints can't use var() in @media (parse-time evaluation) — see item 16.

Convergence

  • item 15 — control heights: 14 CSS control selectors (sidebar nav row, session row, settings nav/back/select, model switcher, composer send, jump-bottom FAB, palette input, first-run checklist rows) snap off-ruler 22/26/30/34/38px onto --h-control-*; TSX arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] convert to the Tailwind ruler scale (min-h-8, min-h-11, max-h-55, …). The feat(ui): govern chat conversation-flow display onto @maka/ui Base UI primitives #332 chat-marker/preview contracts deliberately pin min-h-[28px] / max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to scale re-tuning) — those stay arbitrary and are whitelisted here.
  • item 14 — border width: 221 border: / border-{side}: shorthand widths across 25 CSS files → var(--border-width-*) (perl with (?<![-\w]) lookbehind so the token definitions aren't self-referenced). Border-style (solid/dashed) stays a literal keyword — a named value, not a magic number. CSS-triangle carets (border-width: 4px 0 4px 5px) are multi-value geometry, not strokes, so the contract allows them only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS) and flags any bare px — single OR multi-value — elsewhere.
  • item 13 — box-shadow color (P-SHADOW): 13 bare pure-black box-shadow usages (rgba(0,0,0,A) / oklch(0 0 0 / A)) → oklch(from var(--foreground) l c h / A), geometry preserved (only the color warms). The dark-mode recipe overrides in maka-tokens.css intentionally keep pure-black (dark canvas) — those are token defs, not box-shadow: usages, so out of scope.
  • item 16 — responsive: --maka-chat-measure promoted to :root, 6 redundant , 680px fallbacks dropped, onboarding hero adopts the token. @media breakpoints can't use var(), so a contract whitelists the 8 values in use (620/720/760/820/900/980/990/1100) and bans ad-hoc Npx.
  • item 12 — radius nesting: documents the concentric-radius rule (inner = outer − padding) on the radius tokens; pins the two calc(var(--radius-modal) - 8px) nesting sites so they don't regress to a hardcoded tier. Audit confirms settings inner cards + sub-modals already comply via the radius-converge SELECTOR_TIER.

New contracts

ContractBansPins
control-height-converge-contract.test.tsbare px height/min-height on 14 curated control selectors (each mapped prop must appear); arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] in TSX (whitelist: dots, count badge, 110px scroll cap, two #332 literals)--h-control-* exactly-once → xs/sm/lg/2xl var(--space-N), md/xl calc(var(--spacing) * N); recursive reference-chain closed (undefined ref / cycle throws)
border-width-converge-contract.test.tsbare px width in border:/border-{side}: shorthand; any bare px in border-width: longhand (single OR multi-value) unless allowlisted caret selector; non-keyword border-style:; arbitrary border-[Npx] in TSX--border-width-hairline/thick/accent exactly-once
box-shadow-converge-contract.test.tspure-black color (oklch(0 0 0/A), rgba(0,0,0,A), #000, black) in box-shadow: (multi-line values; token defs not scanned)--shadow-* / --card-shadow / --card-highlight exist
responsive-breakpoint-contract.test.ts@media (max/min-width: Npx) outside the 8-value whitelist; bare 680px in width/max-width/min-width (height caps spared); local --maka-chat-measure re-declaration--maka-chat-measure: 680px exactly-once
radius-nesting-contract.test.tsradius calc addition (breaks concentricity)the two calc(var(--radius-modal) - 8px) nesting sites

Commits

  1. test(desktop): relax exact className match for PR3 min-width additions — fixes two pre-existing test regressions on main (PR3 added min-w-[Nrem] to copy/append/save buttons but didn't update the exact-className="X" regexes). Unblocks a green baseline for PR4 verification.
  2. refactor(ui): converge control heights onto --h-control-* scale (#520 PR4 item 15)
  3. refactor(ui): converge border-width onto --border-width-* tokens (#520 PR4 item 14)
  4. refactor(ui): systematize concentric radius nesting rule (#520 PR4 item 12)
  5. refactor(ui): converge responsive breakpoints + chat content measure (#520 PR4 item 16)
  6. refactor(ui): converge box-shadow color onto foreground-derived (P-SHADOW) (#520 PR4 item 13)

Review-fix commits

  1. fix(ui): close --h-control-* reference chain + tighten mapped-selector tier check — P1: --h-control-md/xl referenced undefined --space-7/9; use calc(var(--spacing) * 7/9). Adds assertCustomPropRefsDefined helper. P3: mapped selectors must use exactly their --h-control-* tier (no var(--space-N) / chrome-token bypass).
  2. fix(ui): ban multi-value bare-px border-width, allowlist triangle carets by selector — P2: border-width: longhand now flags any bare px (single OR multi-value) unless the selector is an allowlisted caret.
  3. fix(test): require every mapped control-height prop to appear, not any one — P3: checkSelectorTier uses seenProps: Set so a multi-prop selector missing width / height / min-height is flagged.
  4. docs(test): fix stale SINGLE-only border-width comment to match selector-allowlist impl.
  5. fix(test): make assertCustomPropRefsDefined a recursive DFS with cycle detection — P3: the helper now recurses through the whole var() chain (catches undefined 2+ hops down) and detects cycles, not just the first hop.

Existing test expectations updated

PR3 min-width + this PR's token replacements broke exact-match assertions in 7 existing contracts (renderer-error-boundary, daily-review-copy-feedback, command-palette-a11y-copy, artifact-pane-layout, permissions-unified-card, startup-loading-shell, project-context-badge, settings-form-a11y). Each update preserves the test's intent (semantic class present / geometry / alpha) and only shifts the expected literal to the new token form.

Verification

  • Tests: npm run -w @maka/desktop test — 1968/1968 pass (was 1935 on main + 2 pre-existing failures; this PR fixes the 2 + adds 33 new contract assertions across 5 files, including the recursive reference-chain DFS guard).
  • Typecheck: npm run typecheck — every workspace clean.
  • Screenshots vs main (turn-narrative, settings-appearance, first-run × light/dark, compare -metric AE -fuzz 2%):
    • first-run: 0% on items 15/16, +0.00009% on item 14 (onboarding 1.5→1px hairline) — item 13 adds 0.
    • turn-narrative: 0.55–0.62% (the 2px control-height snaps in sidebar/会话/composer).
    • settings-appearance: 1.36% (settings nav rows 38→36, multiple rows; was ~1.6% before the P1 reference-chain fix restored the explicit 36px height on --h-control-xl).
    • All diffs are the deliberate 2px control-height snap or the 1.5→1px hairline; no layout-collapse-scale regression. Item 13 (P-SHADOW color) is within the 2% fuzz on light theme (near-black ≈ pure-black at low alpha) — the fix matters on warm/dark shells where pure-black reads as a smudge.

Deferred / out of scope

  • Full box-shadow recipe-converge: mapping the ~20 already-foreground-derived elevation shadows onto var(--shadow-*) recipes would add the design-system 1px border ring + standardize the blur on each — a visual-weight change to compliant surfaces that needs design review, separate from the P-SHADOW color fix in item 13.
  • index.html splash box-shadows: two pure-black rgba(0,0,0,…) shadows live in the pre-render HTML splash (no access to --foreground); left for a separate splash pass.
  • scroll-area: explicitly out of scope per refactor(ui): converge unmanaged design specs (line-height, font-weight, letter-spacing, …) #520 (overlay-scrollbars-contract.test.ts locks OverlayScrollbars).

Astro-Han added 11 commits July 5, 2026 11:40
PR3 (#527) added min-w-[Nrem] utilities to the error-boundary copy button
and the daily-review copy/append/save buttons (text-swap width lock for
复制中…/已复制 feedback). Two contract tests asserted the semantic class
with an exact className="X" regex that required the closing quote right
after the class, so the extra utility class broke the match and the suites
have been red on main since PR3 merged.
Match each semantic class as a whole word in the class list
(className="[^"]*\bX\b[^"]*"), the same form the daily-review test
already used for its negative maka-button check. This keeps the intent
(button uses the semantic class, not a legacy maka-button) while
tolerating the utility classes PR3 deliberately added.
…PR4 item 15)
The sidebar / 会话 / 设置 control heights had drifted onto off-ruler bare
px (22 / 26 / 30 / 34 / 38) while the TSX side used the 4px spacing ruler
via Tailwind h-N. The two scales never aligned, so a sidebar nav row at
34px next to a session row at 30px next to a settings nav at 38px read as
three different systems — the user's 碍眼.
Add a six-tier --h-control-* scale (xs/sm/md/lg/xl/2xl = 20/24/28/32/36/40)
on the 4px spacing ruler (var(--space-N)) so CSS var(--h-control-*) and
Tailwind h-N share one scale. Snap the off-ruler control heights to the
nearest tier and tokenize the on-ruler ones:
.maka-list-row 30 → 32 (--h-control-lg) 会话 row
.maka-search-modal-close 22 → 24 (--h-control-sm)
.maka-search-modal-clear 22 → 24 (--h-control-sm)
.settingsBackButton 38 → 36 (--h-control-xl)
.settingsNavItem 38 → 36 (--h-control-xl)
.settingsSelectTrigger 34 → 32 (--h-control-lg)
.settingsSelectMenuPopup [role=option] 32 → 32 (--h-control-lg)
.maka-model-switcher-trigger 22 → 24 (--h-control-sm)
.maka-chat-jump-bottom 26 → 28 (--h-control-md)
.maka-palette-input-wrap 32 → 32 (--h-control-lg)
.maka-first-run-checklist-error-action 24 → 24 (--h-control-sm)
.maka-first-run-checklist-row > button 38 → 36 (--h-control-xl)
.maka-composer-send-button 30 → 32 (--h-control-lg)
.maka-composer-workspace-picker 24 → 24 (--h-control-sm)
On the TSX side, replace arbitrary h-[Npx] / min-h-[Npx] / max-h-[Npx] with
the Tailwind ruler scale (min-h-8, min-h-11, h-7, max-h-55, max-h-80,
min-h-21, etc.) so TSX and CSS share the 4px ruler. The #332 chat-marker
and chat-preview cascade contracts deliberately pin min-h-[28px] and
max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to
scale re-tuning); those two stay arbitrary and are whitelisted here, with
#332 retaining governance of them.
control-height-converge-contract.test.ts pins the six tokens exactly-once
to their spacing tier, maps a curated set of control selectors to their
expected tier (the radius-contract SELECTOR_TIER pattern — height has no
single anchor the way border-radius does, so the contract scopes to
control selectors, not every height), and bans bare-numeric arbitrary
h-[Npx] / min-h-[Npx] / max-h-[Npx] in TSX with a small whitelist for the
decorator dots, the count badge, the off-ruler 110px scroll cap, and the
two #332-pinned literals. App-chrome bars (--h-titlebar/--h-toolbar/
--h-composer-min/--h-list-header) and content min/max heights stay bare —
they are structure / content, not controls.
Verification: 1941/1941 desktop tests pass. Screenshots vs main:
first-run 0% diff (untouched), turn-narrative 0.6%, settings-appearance
1.6-1.8% — all consistent with the deliberate 2px control-height snap.
…PR4 item 14)
Border COLOR was already tokenized (--border / --border-strong); the WIDTH
was bare px in every `border: 1px solid var(--border)` shorthand plus a
handful of `border-left: 3px solid …` status strips. Add three semantic
weights on a small scale:
--border-width-hairline 1px the universal divider (210+ sites)
--border-width-thick 2px a heavier divider / selected outline
--border-width-accent 3px a status / decorative strip (toast
variant color bars, avatar rings)
Snap the rare 1.5px hairlines to hairline (3 sites, -0.5px) and the one 4px
avatar ring to accent (1 site, -1px). Border-STYLE (solid / dashed) stays a
literal keyword — it is a named value, not a magic number, so tokenizing it
adds indirection with no governance benefit. CSS-triangle carets
(`border-width: 4px 0 4px 5px`) are multi-value geometry, not border
strokes, so the contract only flags a SINGLE bare-px width.
Replace 221 `border:` / `border-{side}:` shorthand widths across 25 CSS
files with `var(--border-width-*)` (perl with `(?<![-\w])` lookbehind so the
`--border-width-hairline: 1px` token definition is not turned into a
self-reference).
border-width-converge-contract.test.ts pins the three tokens exactly-once;
scans `border:` / `border-{side}:` shorthand for bare px (stripping
var()/calc()/oklch() first so color px is not mistaken for the width);
flags a SINGLE bare-px `border-width:` longhand while sparing multi-value
triangle geometry; enforces `border-style:` keyword literals; and bans
arbitrary `border-[Npx]` / `border-{side}-[Npx]` in TSX (Tailwind `border`
defaults to 1px = hairline, so TSX and CSS agree on the value).
Update four existing contracts that hardcoded `border: 1px solid …` to
match the new token form (artifact-pane, command-palette, permissions-
unified-card, startup-loading-shell).
Verification: 1950/1950 desktop tests pass. Screenshots vs main: turn-
narrative / settings-appearance 0% incremental over item 15, first-run
+375px (0.00009%, the onboarding 1.5→1 hairline). No regression.
…em 12)
Roadmap §1.3 / P-RADIUS: when a rounded surface sits inside another rounded
surface with padding between them, the inner radius = outer radius −
padding so the two curves share a center and read as one machined shell.
Document the convention on the radius tokens in maka-tokens.css with the
two forms it takes:
1. outer − padding lands on a tier → pick that tier directly (e.g. an
8px surface card inside a 12px modal with 4px padding: 12 − 4 = 8).
The radius-converge contract SELECTOR_TIER already pins this.
2. outer − padding does NOT land on a tier → use
`calc(var(--radius-*) - Npx)` (the radius-converge calc allowlist
permits only this shrink form). Two sites use it today — an input
inside a 12px modal shell with an 8px inset (12 − 8 = 4px, not a
tier): .maka-search-modal-input-row and .maka-palette-input-wrap.
Audit conclusion: the settings-modal inner cards use the surface/control
tier inside the modal shell (form 1, governed by SELECTOR_TIER), and the
seven settings inner surfaces that keep --radius-modal are peer sub-modals
(login modal, scan modal, select popup), not nested cards — so no nesting
violations and no CSS value changes are needed.
radius-nesting-contract.test.ts pins the two calc-nested input sites so a
later cleanup can't drop the calc and revert to a hardcoded --radius-control
(6px) that would read too round against the 12px shell corners, and
restates the shrink-only rule (no calc addition) for the nesting sites.
Verification: 1952/1952 desktop tests pass. No CSS values changed → no
visual regression (screenshots unchanged from item 14).
…520 PR4 item 16)
Two responsive seams had drifted:
1. @media breakpoints. CSS @media queries evaluate at parse time, before
custom properties resolve, so `@media (max-width: var(--bp))` is INVALID
— breakpoints cannot be tokenized with var(). Instead a contract
whitelists the eight max/min-width pixel values the app actually uses
(620 / 720 / 760 / 820 / 900 / 980 / 990 / 1100) and bans any other bare
`@media (max-width: Npx)`. A new breakpoint must be added to the
whitelist, which forces a conscious decision instead of a silent drift.
prefers-reduced-motion / prefers-color-scheme are not width breakpoints
and stay out of scope.
2. The chat content measure (--maka-chat-measure: 680px). This IS a regular
property value, so it can be tokenized. It was a LOCAL token on
.mainColumn with a `680px` fallback at every call site; promote it to
:root in maka-tokens.css so it is canonical, drop the redundant
`, 680px` fallbacks at six call sites, and adopt it in the onboarding
hero (was a bare `width: min(680px, 100%)`). The chat column, tool
output, composer, and onboarding hero now share one measure with no
fallback. A 680px HEIGHT cap on the settings form modal is a different
semantic and stays bare — the contract scopes the ban to width.
responsive-breakpoint-contract.test.ts whitelists the breakpoints, pins
--maka-chat-measure to 680px exactly-once in maka-tokens.css, bans a local
re-declaration in styles/, and bans a bare 680px in width / max-width /
min-width declarations.
Update project-context-badge.test.ts to match the no-fallback
composer-workspace-row width formula.
Verification: 1959/1959 desktop tests pass. No CSS values changed (the
chat-measure move is functionally a no-op — every site already resolved to
680px via the local def or the fallback) → first-run screenshot unchanged
from item 14 (375px / 0.00009%, the onboarding 1.5→1 hairline).
…ADOW) (#520 PR4 item 13)
P-SHADOW (roadmap §1.2 / maka-tokens.css): blur layers in box-shadow must
derive from --foreground, not pure black — a pure-black rgba()/oklch()
shadow on maka's warm shell reads as a dirty smudge, while a foreground-
derived shadow shares one light source with the border ring and ink.
Thirteen bare pure-black box-shadow usages (rgba(0,0,0,A) and
oklch(0 0 0 / A)) survived across settings, plan-reminders, and
reference-shell. Swap each pure-black color for
oklch(from var(--foreground) l c h / A), KEEPING the shadow geometry
(offset / blur / spread) intact so only the color warms — the safe
P-SHADOW win. The dark-mode shadow recipe overrides in maka-tokens.css
(--shadow-medium / --shadow-modal for dark mode) intentionally keep
pure-black oklch(0 0 0 / 0.5|0.6) — on a dark canvas a pure-black shadow is
correct ("dark mode shadows collapse to a single ring; modal keeps one
deep drop") — those are token definitions, not box-shadow usages, so they
are out of scope.
The broader recipe-converge (mapping the ~20 already-foreground-derived
elevation shadows onto var(--shadow-*) recipes) is deferred: the recipes
add a 1px border ring + standardize the blur, which changes the visual
weight of compliant surfaces — a design-review change, separate from the
P-SHADOW color fix here. The themed colored skill-card shadow
(oklch(0.2 0.02 224 / 0.4)) and the hard 1px blur-0 hover edge
(0 1px 0 0 ...) are not pure-black and are not flagged.
box-shadow-converge-contract.test.ts bans a pure-black color
(oklch(0 0 0 / A) / rgba(0,0,0,A) / #000 / black with non-zero alpha) in
any box-shadow declaration (multi-line values captured), and confirms the
shadow recipes are defined. It does not scan --shadow-*: token
definitions, so the intentional dark-mode pure-black recipes are not
flagged.
Update settings-form-a11y-contract.test.ts: the three connection / auth-
contract / model-table near-flat shadow assertions pinned the pure-black
rgba form as the "reference implementation" shadow; P-SHADOW supersedes
that — the geometry (0 1px 3px) and alpha (0.03) are unchanged, only the
color form moves to foreground-derived.
Verification: 1965/1965 desktop tests pass. Screenshots vs main: 0%
incremental over item 14/15/16 (the color warm-up is within the 2% fuzz on
light theme — near-black and pure-black at low alpha are visually
identical there; the fix matters on warm/dark shells where pure-black
reads as a smudge).
…r tier check (#520 PR4 review)
P1: --h-control-md / --h-control-xl referenced --space-7 / --space-9, which
are NOT in maka's discrete spacing scale (it defines --space-0/1/2/3/4/5/6/
8/10/12/16 — 7 and 9 are skipped). A var() with no fallback resolving to an
undefined custom prop is invalid at computed-value time, so the jump-bottom
FAB (md=28px) and settings nav/back rows (xl=36px) collapsed to width/height
auto / min-height 0 — masked by their padding/content, but fragile and wrong.
Fix with calc(var(--spacing) * 7) / calc(var(--spacing) * 9) (the --spacing
base step IS defined, = 4px), so 28/36px resolve explicitly without expanding
the general --space-* scale (which would invite p-7 / gap-7 drift).
Add assertCustomPropRefsDefined to css-test-helpers: walk the var(--xxx)
reference chain in a token's value and assert every referenced custom prop is
defined somewhere in the tokens file (token :root, @theme inline, or bridge
alias). A pin-only check that --h-control-md is declared with var(--space-7)
passes while the token is broken — this helper catches the bug class. Add a
contract test that runs it on every --h-control-* tier, plus a negative case
that feeds a --space-7 reference and asserts it throws.
P3: isAllowedControlHeight previously let a MAPPED selector pass with a
direct var(--space-N), calc(var(--spacing) * N), or a layout-chrome token
(--h-titlebar / --maka-sidebar-topbar-button-size / …) instead of its
expected --h-control-* tier — bypassing the semantic scale the mapping table
exists to enforce. Tighten: a mapped selector's height / min-height / width
must be exactly var(--h-control-<expected>) or a neutral literal (0 / auto /
100%). Unmapped controls are added to CONTROL_HEIGHT rather than allowed to
slip via a space token. No current mapped selector uses a bypass value, so
nothing real is flagged; the negative cases now assert the bypass forms fail.
Verification: 1967/1967 desktop tests pass (was 1965; +1 ref-chain test, +1
ref-chain negative case). Screenshots vs main: settings-appearance diff drops
0.0159 → 0.0136 (the settings nav rows restore to explicit 36px instead of
the padding-masked auto height); turn-narrative unchanged (the FAB collapse
was already masked by its icon + padding). typecheck clean.
…ets by selector (#520 PR4 review)
P2: the border-width contract only flagged a SINGLE bare-px value in a
border-width: longhand (SINGLE_BARE_PX_RE). A multi-value form like
border-width: 1px 2px (a non-uniform stroke, NOT a triangle) was spared — a
false negative a future drift could hide behind. The triangle-carets-are-
multi-value heuristic spared ALL multi-value, which is the hole.
Fix: flag ANY bare px in a border-width: longhand (single OR multi-value),
and allowlist the three known triangle/caret SELECTORS (.maka-turn-thinking
summary::before, .maka-bubble-assistant …checkbox:checked::after,
.maka-permission-raw > summary::before) whose multi-value is geometry, not a
stroke. Selector tracking walks the line before { to keep the current
selector; a new caret with a new value on a new selector is flagged until
explicitly added to the allowlist (correct — review new carets consciously).
Add negative cases: border-width: 1px 2px on a non-allowlisted selector must
fail; the three allowlisted caret selectors must pass; single bare-px still
fails; token / 0 still pass.
Verification: 1967/1967 desktop tests pass. The real renderer CSS scan stays
clean (the only three border-width: longhands are the allowlisted carets).
…y one (#520 PR4 review)
checkSelectorTier used a single checkedAny flag — if a selector was mapped
with multiple REQUIRED props (e.g. .maka-chat-jump-bottom [width, height],
.settingsNavItem [height, min-height]), the contract only checked that AT
LEAST ONE appeared. Deleting the width declaration but keeping height passed
silently, dropping the square-control width requirement.
Replace checkedAny with seenProps: Set<string>, accumulated across all
matched blocks (base + @media / :state variants), and require every prop in
check.props to appear at least once — a missing required prop is flagged as
'is missing required <prop> declaration'. Each declaration that does appear
is still value-checked against the expected tier. No current mapped selector
is missing a prop, so nothing real is flagged.
Add a negative case: a .maka-chat-jump-bottom fixture with only height must
flag the missing width; a .settingsNavItem fixture missing height OR
min-height must flag; a complete fixture passes.
Verification: 1968/1968 desktop tests pass.
…tor-allowlist impl (#520 PR4 review)
The previous review fix changed border-width longhand scanning from 'only
flag a SINGLE bare-px value' to 'flag ANY bare px (single OR multi-value)
unless the selector is an allowlisted triangle/caret', but the top summary
paragraph still described the old single-only rule — comment, impl, and
negative cases disagreed.
Update the top summary to match: triangle carets are multi-value geometry
allowed only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS), and
any bare px — single OR multi-value — is flagged elsewhere. The item-2
invariant and inline comment already said this; only the summary was stale.
Verification: 1968/1968 desktop tests pass; non-allowlisted border-width:
1px 2px continues to fail.
…e detection (#520 PR4 review)
The helper only checked the FIRST hop: it collected var(--xxx) refs in the
target prop's value and checked each was defined, but did not recurse into a
ref's own value. A chain like --h-control-xs → --space-5 → --missing
(--missing undefined two hops out) passed because --space-5 was defined and
the helper stopped there. It also had no cycle detection (--a → --b → --a
would infinite-loop).
Rewrite as a small DFS from the target prop through every var() ref,
maintaining visiting/visited sets: an undefined ref at any depth throws
'references undefined <name> (via <path>)', and a back-edge to a node on the
current path throws 'circular custom-prop reference: a → b → a'. Each node
must still be declared exactly once. Function name kept; the doc comment now
says 'recursively, with cycle detection' and means it.
Add negative cases: --h-control-xs → --space-5 → --missing must fail (the
2-hop case the old helper missed); --a → --b → --a must fail (cycle). The
direct --space-7 case and the valid closed chain still behave as before.
Verification: 1968/1968 desktop tests pass; typecheck clean. The real
--h-control-* chain (xs/sm/lg/2xl → --space-N → calc(var(--spacing) * N) →
--spacing: 4px; md/xl → calc(var(--spacing) * N) → --spacing) still passes.
@Astro-Han
Astro-Han merged commit 0954d9b into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the refactor/layout-surface-sizing branch July 5, 2026 05:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(ui): converge layout-surface & sizing tokens (#520 PR4) by Astro-Han · Pull Request #539 · apache/maka · GitHub
Skip to content

refactor(ui): converge layout-surface & sizing tokens (#520 PR4) - #539

Merged
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing
Jul 5, 2026
Merged

refactor(ui): converge layout-surface & sizing tokens (#520 PR4)#539
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

PR4 of #520 — converges the layout-surface & sizing dimensions onto tokens + contracts, using the same converge-contract pattern as PR1 (#526) and PR3 (#527). Five items, one commit each (each independently revertible), plus one commit fixing two pre-existing test regressions from PR3, and five follow-up commits addressing review feedback (reference-chain DFS, mapped-prop completeness, triangle-caret selector allowlist, contract/comment consistency).

The user's "碍眼" — sidebar / 会话 / 设置 control-height inconsistency — is item 15 (control heights), done first.

New tokens (maka-tokens.css)

TokenValueItem
--h-control-xs/sm/md/lg/xl/2xlxs/sm/lg/2xl = var(--space-5/6/8/10), md/xl = calc(var(--spacing) * 7/9) (7 and 9 aren't in maka's discrete spacing scale) = 20/24/28/32/36/40px15 — control-height scale (on the 4px spacing ruler, shared with Tailwind h-N)
--border-width-hairline/thick/accent1px / 2px / 3px14 — border stroke weight
--maka-chat-measure680px (promoted to :root from a local .mainColumn token)16 — chat content measure

No @theme inline bridge this PR: control height is numeric Tailwind (h-N), border-width is static (border = 1px), and breakpoints can't use var() in @media (parse-time evaluation) — see item 16.

Convergence

  • item 15 — control heights: 14 CSS control selectors (sidebar nav row, session row, settings nav/back/select, model switcher, composer send, jump-bottom FAB, palette input, first-run checklist rows) snap off-ruler 22/26/30/34/38px onto --h-control-*; TSX arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] convert to the Tailwind ruler scale (min-h-8, min-h-11, max-h-55, …). The feat(ui): govern chat conversation-flow display onto @maka/ui Base UI primitives #332 chat-marker/preview contracts deliberately pin min-h-[28px] / max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to scale re-tuning) — those stay arbitrary and are whitelisted here.
  • item 14 — border width: 221 border: / border-{side}: shorthand widths across 25 CSS files → var(--border-width-*) (perl with (?<![-\w]) lookbehind so the token definitions aren't self-referenced). Border-style (solid/dashed) stays a literal keyword — a named value, not a magic number. CSS-triangle carets (border-width: 4px 0 4px 5px) are multi-value geometry, not strokes, so the contract allows them only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS) and flags any bare px — single OR multi-value — elsewhere.
  • item 13 — box-shadow color (P-SHADOW): 13 bare pure-black box-shadow usages (rgba(0,0,0,A) / oklch(0 0 0 / A)) → oklch(from var(--foreground) l c h / A), geometry preserved (only the color warms). The dark-mode recipe overrides in maka-tokens.css intentionally keep pure-black (dark canvas) — those are token defs, not box-shadow: usages, so out of scope.
  • item 16 — responsive: --maka-chat-measure promoted to :root, 6 redundant , 680px fallbacks dropped, onboarding hero adopts the token. @media breakpoints can't use var(), so a contract whitelists the 8 values in use (620/720/760/820/900/980/990/1100) and bans ad-hoc Npx.
  • item 12 — radius nesting: documents the concentric-radius rule (inner = outer − padding) on the radius tokens; pins the two calc(var(--radius-modal) - 8px) nesting sites so they don't regress to a hardcoded tier. Audit confirms settings inner cards + sub-modals already comply via the radius-converge SELECTOR_TIER.

New contracts

ContractBansPins
control-height-converge-contract.test.tsbare px height/min-height on 14 curated control selectors (each mapped prop must appear); arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] in TSX (whitelist: dots, count badge, 110px scroll cap, two #332 literals)--h-control-* exactly-once → xs/sm/lg/2xl var(--space-N), md/xl calc(var(--spacing) * N); recursive reference-chain closed (undefined ref / cycle throws)
border-width-converge-contract.test.tsbare px width in border:/border-{side}: shorthand; any bare px in border-width: longhand (single OR multi-value) unless allowlisted caret selector; non-keyword border-style:; arbitrary border-[Npx] in TSX--border-width-hairline/thick/accent exactly-once
box-shadow-converge-contract.test.tspure-black color (oklch(0 0 0/A), rgba(0,0,0,A), #000, black) in box-shadow: (multi-line values; token defs not scanned)--shadow-* / --card-shadow / --card-highlight exist
responsive-breakpoint-contract.test.ts@media (max/min-width: Npx) outside the 8-value whitelist; bare 680px in width/max-width/min-width (height caps spared); local --maka-chat-measure re-declaration--maka-chat-measure: 680px exactly-once
radius-nesting-contract.test.tsradius calc addition (breaks concentricity)the two calc(var(--radius-modal) - 8px) nesting sites

Commits

  1. test(desktop): relax exact className match for PR3 min-width additions — fixes two pre-existing test regressions on main (PR3 added min-w-[Nrem] to copy/append/save buttons but didn't update the exact-className="X" regexes). Unblocks a green baseline for PR4 verification.
  2. refactor(ui): converge control heights onto --h-control-* scale (#520 PR4 item 15)
  3. refactor(ui): converge border-width onto --border-width-* tokens (#520 PR4 item 14)
  4. refactor(ui): systematize concentric radius nesting rule (#520 PR4 item 12)
  5. refactor(ui): converge responsive breakpoints + chat content measure (#520 PR4 item 16)
  6. refactor(ui): converge box-shadow color onto foreground-derived (P-SHADOW) (#520 PR4 item 13)

Review-fix commits

  1. fix(ui): close --h-control-* reference chain + tighten mapped-selector tier check — P1: --h-control-md/xl referenced undefined --space-7/9; use calc(var(--spacing) * 7/9). Adds assertCustomPropRefsDefined helper. P3: mapped selectors must use exactly their --h-control-* tier (no var(--space-N) / chrome-token bypass).
  2. fix(ui): ban multi-value bare-px border-width, allowlist triangle carets by selector — P2: border-width: longhand now flags any bare px (single OR multi-value) unless the selector is an allowlisted caret.
  3. fix(test): require every mapped control-height prop to appear, not any one — P3: checkSelectorTier uses seenProps: Set so a multi-prop selector missing width / height / min-height is flagged.
  4. docs(test): fix stale SINGLE-only border-width comment to match selector-allowlist impl.
  5. fix(test): make assertCustomPropRefsDefined a recursive DFS with cycle detection — P3: the helper now recurses through the whole var() chain (catches undefined 2+ hops down) and detects cycles, not just the first hop.

Existing test expectations updated

PR3 min-width + this PR's token replacements broke exact-match assertions in 7 existing contracts (renderer-error-boundary, daily-review-copy-feedback, command-palette-a11y-copy, artifact-pane-layout, permissions-unified-card, startup-loading-shell, project-context-badge, settings-form-a11y). Each update preserves the test's intent (semantic class present / geometry / alpha) and only shifts the expected literal to the new token form.

Verification

  • Tests: npm run -w @maka/desktop test — 1968/1968 pass (was 1935 on main + 2 pre-existing failures; this PR fixes the 2 + adds 33 new contract assertions across 5 files, including the recursive reference-chain DFS guard).
  • Typecheck: npm run typecheck — every workspace clean.
  • Screenshots vs main (turn-narrative, settings-appearance, first-run × light/dark, compare -metric AE -fuzz 2%):
    • first-run: 0% on items 15/16, +0.00009% on item 14 (onboarding 1.5→1px hairline) — item 13 adds 0.
    • turn-narrative: 0.55–0.62% (the 2px control-height snaps in sidebar/会话/composer).
    • settings-appearance: 1.36% (settings nav rows 38→36, multiple rows; was ~1.6% before the P1 reference-chain fix restored the explicit 36px height on --h-control-xl).
    • All diffs are the deliberate 2px control-height snap or the 1.5→1px hairline; no layout-collapse-scale regression. Item 13 (P-SHADOW color) is within the 2% fuzz on light theme (near-black ≈ pure-black at low alpha) — the fix matters on warm/dark shells where pure-black reads as a smudge.

Deferred / out of scope

  • Full box-shadow recipe-converge: mapping the ~20 already-foreground-derived elevation shadows onto var(--shadow-*) recipes would add the design-system 1px border ring + standardize the blur on each — a visual-weight change to compliant surfaces that needs design review, separate from the P-SHADOW color fix in item 13.
  • index.html splash box-shadows: two pure-black rgba(0,0,0,…) shadows live in the pre-render HTML splash (no access to --foreground); left for a separate splash pass.
  • scroll-area: explicitly out of scope per refactor(ui): converge unmanaged design specs (line-height, font-weight, letter-spacing, …) #520 (overlay-scrollbars-contract.test.ts locks OverlayScrollbars).

Astro-Han added 11 commits July 5, 2026 11:40
PR3 (#527) added min-w-[Nrem] utilities to the error-boundary copy button
and the daily-review copy/append/save buttons (text-swap width lock for
复制中…/已复制 feedback). Two contract tests asserted the semantic class
with an exact className="X" regex that required the closing quote right
after the class, so the extra utility class broke the match and the suites
have been red on main since PR3 merged.
Match each semantic class as a whole word in the class list
(className="[^"]*\bX\b[^"]*"), the same form the daily-review test
already used for its negative maka-button check. This keeps the intent
(button uses the semantic class, not a legacy maka-button) while
tolerating the utility classes PR3 deliberately added.
…PR4 item 15)
The sidebar / 会话 / 设置 control heights had drifted onto off-ruler bare
px (22 / 26 / 30 / 34 / 38) while the TSX side used the 4px spacing ruler
via Tailwind h-N. The two scales never aligned, so a sidebar nav row at
34px next to a session row at 30px next to a settings nav at 38px read as
three different systems — the user's 碍眼.
Add a six-tier --h-control-* scale (xs/sm/md/lg/xl/2xl = 20/24/28/32/36/40)
on the 4px spacing ruler (var(--space-N)) so CSS var(--h-control-*) and
Tailwind h-N share one scale. Snap the off-ruler control heights to the
nearest tier and tokenize the on-ruler ones:
.maka-list-row 30 → 32 (--h-control-lg) 会话 row
.maka-search-modal-close 22 → 24 (--h-control-sm)
.maka-search-modal-clear 22 → 24 (--h-control-sm)
.settingsBackButton 38 → 36 (--h-control-xl)
.settingsNavItem 38 → 36 (--h-control-xl)
.settingsSelectTrigger 34 → 32 (--h-control-lg)
.settingsSelectMenuPopup [role=option] 32 → 32 (--h-control-lg)
.maka-model-switcher-trigger 22 → 24 (--h-control-sm)
.maka-chat-jump-bottom 26 → 28 (--h-control-md)
.maka-palette-input-wrap 32 → 32 (--h-control-lg)
.maka-first-run-checklist-error-action 24 → 24 (--h-control-sm)
.maka-first-run-checklist-row > button 38 → 36 (--h-control-xl)
.maka-composer-send-button 30 → 32 (--h-control-lg)
.maka-composer-workspace-picker 24 → 24 (--h-control-sm)
On the TSX side, replace arbitrary h-[Npx] / min-h-[Npx] / max-h-[Npx] with
the Tailwind ruler scale (min-h-8, min-h-11, h-7, max-h-55, max-h-80,
min-h-21, etc.) so TSX and CSS share the 4px ruler. The #332 chat-marker
and chat-preview cascade contracts deliberately pin min-h-[28px] and
max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to
scale re-tuning); those two stay arbitrary and are whitelisted here, with
#332 retaining governance of them.
control-height-converge-contract.test.ts pins the six tokens exactly-once
to their spacing tier, maps a curated set of control selectors to their
expected tier (the radius-contract SELECTOR_TIER pattern — height has no
single anchor the way border-radius does, so the contract scopes to
control selectors, not every height), and bans bare-numeric arbitrary
h-[Npx] / min-h-[Npx] / max-h-[Npx] in TSX with a small whitelist for the
decorator dots, the count badge, the off-ruler 110px scroll cap, and the
two #332-pinned literals. App-chrome bars (--h-titlebar/--h-toolbar/
--h-composer-min/--h-list-header) and content min/max heights stay bare —
they are structure / content, not controls.
Verification: 1941/1941 desktop tests pass. Screenshots vs main:
first-run 0% diff (untouched), turn-narrative 0.6%, settings-appearance
1.6-1.8% — all consistent with the deliberate 2px control-height snap.
…PR4 item 14)
Border COLOR was already tokenized (--border / --border-strong); the WIDTH
was bare px in every `border: 1px solid var(--border)` shorthand plus a
handful of `border-left: 3px solid …` status strips. Add three semantic
weights on a small scale:
--border-width-hairline 1px the universal divider (210+ sites)
--border-width-thick 2px a heavier divider / selected outline
--border-width-accent 3px a status / decorative strip (toast
variant color bars, avatar rings)
Snap the rare 1.5px hairlines to hairline (3 sites, -0.5px) and the one 4px
avatar ring to accent (1 site, -1px). Border-STYLE (solid / dashed) stays a
literal keyword — it is a named value, not a magic number, so tokenizing it
adds indirection with no governance benefit. CSS-triangle carets
(`border-width: 4px 0 4px 5px`) are multi-value geometry, not border
strokes, so the contract only flags a SINGLE bare-px width.
Replace 221 `border:` / `border-{side}:` shorthand widths across 25 CSS
files with `var(--border-width-*)` (perl with `(?<![-\w])` lookbehind so the
`--border-width-hairline: 1px` token definition is not turned into a
self-reference).
border-width-converge-contract.test.ts pins the three tokens exactly-once;
scans `border:` / `border-{side}:` shorthand for bare px (stripping
var()/calc()/oklch() first so color px is not mistaken for the width);
flags a SINGLE bare-px `border-width:` longhand while sparing multi-value
triangle geometry; enforces `border-style:` keyword literals; and bans
arbitrary `border-[Npx]` / `border-{side}-[Npx]` in TSX (Tailwind `border`
defaults to 1px = hairline, so TSX and CSS agree on the value).
Update four existing contracts that hardcoded `border: 1px solid …` to
match the new token form (artifact-pane, command-palette, permissions-
unified-card, startup-loading-shell).
Verification: 1950/1950 desktop tests pass. Screenshots vs main: turn-
narrative / settings-appearance 0% incremental over item 15, first-run
+375px (0.00009%, the onboarding 1.5→1 hairline). No regression.
…em 12)
Roadmap §1.3 / P-RADIUS: when a rounded surface sits inside another rounded
surface with padding between them, the inner radius = outer radius −
padding so the two curves share a center and read as one machined shell.
Document the convention on the radius tokens in maka-tokens.css with the
two forms it takes:
1. outer − padding lands on a tier → pick that tier directly (e.g. an
8px surface card inside a 12px modal with 4px padding: 12 − 4 = 8).
The radius-converge contract SELECTOR_TIER already pins this.
2. outer − padding does NOT land on a tier → use
`calc(var(--radius-*) - Npx)` (the radius-converge calc allowlist
permits only this shrink form). Two sites use it today — an input
inside a 12px modal shell with an 8px inset (12 − 8 = 4px, not a
tier): .maka-search-modal-input-row and .maka-palette-input-wrap.
Audit conclusion: the settings-modal inner cards use the surface/control
tier inside the modal shell (form 1, governed by SELECTOR_TIER), and the
seven settings inner surfaces that keep --radius-modal are peer sub-modals
(login modal, scan modal, select popup), not nested cards — so no nesting
violations and no CSS value changes are needed.
radius-nesting-contract.test.ts pins the two calc-nested input sites so a
later cleanup can't drop the calc and revert to a hardcoded --radius-control
(6px) that would read too round against the 12px shell corners, and
restates the shrink-only rule (no calc addition) for the nesting sites.
Verification: 1952/1952 desktop tests pass. No CSS values changed → no
visual regression (screenshots unchanged from item 14).
…520 PR4 item 16)
Two responsive seams had drifted:
1. @media breakpoints. CSS @media queries evaluate at parse time, before
custom properties resolve, so `@media (max-width: var(--bp))` is INVALID
— breakpoints cannot be tokenized with var(). Instead a contract
whitelists the eight max/min-width pixel values the app actually uses
(620 / 720 / 760 / 820 / 900 / 980 / 990 / 1100) and bans any other bare
`@media (max-width: Npx)`. A new breakpoint must be added to the
whitelist, which forces a conscious decision instead of a silent drift.
prefers-reduced-motion / prefers-color-scheme are not width breakpoints
and stay out of scope.
2. The chat content measure (--maka-chat-measure: 680px). This IS a regular
property value, so it can be tokenized. It was a LOCAL token on
.mainColumn with a `680px` fallback at every call site; promote it to
:root in maka-tokens.css so it is canonical, drop the redundant
`, 680px` fallbacks at six call sites, and adopt it in the onboarding
hero (was a bare `width: min(680px, 100%)`). The chat column, tool
output, composer, and onboarding hero now share one measure with no
fallback. A 680px HEIGHT cap on the settings form modal is a different
semantic and stays bare — the contract scopes the ban to width.
responsive-breakpoint-contract.test.ts whitelists the breakpoints, pins
--maka-chat-measure to 680px exactly-once in maka-tokens.css, bans a local
re-declaration in styles/, and bans a bare 680px in width / max-width /
min-width declarations.
Update project-context-badge.test.ts to match the no-fallback
composer-workspace-row width formula.
Verification: 1959/1959 desktop tests pass. No CSS values changed (the
chat-measure move is functionally a no-op — every site already resolved to
680px via the local def or the fallback) → first-run screenshot unchanged
from item 14 (375px / 0.00009%, the onboarding 1.5→1 hairline).
…ADOW) (#520 PR4 item 13)
P-SHADOW (roadmap §1.2 / maka-tokens.css): blur layers in box-shadow must
derive from --foreground, not pure black — a pure-black rgba()/oklch()
shadow on maka's warm shell reads as a dirty smudge, while a foreground-
derived shadow shares one light source with the border ring and ink.
Thirteen bare pure-black box-shadow usages (rgba(0,0,0,A) and
oklch(0 0 0 / A)) survived across settings, plan-reminders, and
reference-shell. Swap each pure-black color for
oklch(from var(--foreground) l c h / A), KEEPING the shadow geometry
(offset / blur / spread) intact so only the color warms — the safe
P-SHADOW win. The dark-mode shadow recipe overrides in maka-tokens.css
(--shadow-medium / --shadow-modal for dark mode) intentionally keep
pure-black oklch(0 0 0 / 0.5|0.6) — on a dark canvas a pure-black shadow is
correct ("dark mode shadows collapse to a single ring; modal keeps one
deep drop") — those are token definitions, not box-shadow usages, so they
are out of scope.
The broader recipe-converge (mapping the ~20 already-foreground-derived
elevation shadows onto var(--shadow-*) recipes) is deferred: the recipes
add a 1px border ring + standardize the blur, which changes the visual
weight of compliant surfaces — a design-review change, separate from the
P-SHADOW color fix here. The themed colored skill-card shadow
(oklch(0.2 0.02 224 / 0.4)) and the hard 1px blur-0 hover edge
(0 1px 0 0 ...) are not pure-black and are not flagged.
box-shadow-converge-contract.test.ts bans a pure-black color
(oklch(0 0 0 / A) / rgba(0,0,0,A) / #000 / black with non-zero alpha) in
any box-shadow declaration (multi-line values captured), and confirms the
shadow recipes are defined. It does not scan --shadow-*: token
definitions, so the intentional dark-mode pure-black recipes are not
flagged.
Update settings-form-a11y-contract.test.ts: the three connection / auth-
contract / model-table near-flat shadow assertions pinned the pure-black
rgba form as the "reference implementation" shadow; P-SHADOW supersedes
that — the geometry (0 1px 3px) and alpha (0.03) are unchanged, only the
color form moves to foreground-derived.
Verification: 1965/1965 desktop tests pass. Screenshots vs main: 0%
incremental over item 14/15/16 (the color warm-up is within the 2% fuzz on
light theme — near-black and pure-black at low alpha are visually
identical there; the fix matters on warm/dark shells where pure-black
reads as a smudge).
…r tier check (#520 PR4 review)
P1: --h-control-md / --h-control-xl referenced --space-7 / --space-9, which
are NOT in maka's discrete spacing scale (it defines --space-0/1/2/3/4/5/6/
8/10/12/16 — 7 and 9 are skipped). A var() with no fallback resolving to an
undefined custom prop is invalid at computed-value time, so the jump-bottom
FAB (md=28px) and settings nav/back rows (xl=36px) collapsed to width/height
auto / min-height 0 — masked by their padding/content, but fragile and wrong.
Fix with calc(var(--spacing) * 7) / calc(var(--spacing) * 9) (the --spacing
base step IS defined, = 4px), so 28/36px resolve explicitly without expanding
the general --space-* scale (which would invite p-7 / gap-7 drift).
Add assertCustomPropRefsDefined to css-test-helpers: walk the var(--xxx)
reference chain in a token's value and assert every referenced custom prop is
defined somewhere in the tokens file (token :root, @theme inline, or bridge
alias). A pin-only check that --h-control-md is declared with var(--space-7)
passes while the token is broken — this helper catches the bug class. Add a
contract test that runs it on every --h-control-* tier, plus a negative case
that feeds a --space-7 reference and asserts it throws.
P3: isAllowedControlHeight previously let a MAPPED selector pass with a
direct var(--space-N), calc(var(--spacing) * N), or a layout-chrome token
(--h-titlebar / --maka-sidebar-topbar-button-size / …) instead of its
expected --h-control-* tier — bypassing the semantic scale the mapping table
exists to enforce. Tighten: a mapped selector's height / min-height / width
must be exactly var(--h-control-<expected>) or a neutral literal (0 / auto /
100%). Unmapped controls are added to CONTROL_HEIGHT rather than allowed to
slip via a space token. No current mapped selector uses a bypass value, so
nothing real is flagged; the negative cases now assert the bypass forms fail.
Verification: 1967/1967 desktop tests pass (was 1965; +1 ref-chain test, +1
ref-chain negative case). Screenshots vs main: settings-appearance diff drops
0.0159 → 0.0136 (the settings nav rows restore to explicit 36px instead of
the padding-masked auto height); turn-narrative unchanged (the FAB collapse
was already masked by its icon + padding). typecheck clean.
…ets by selector (#520 PR4 review)
P2: the border-width contract only flagged a SINGLE bare-px value in a
border-width: longhand (SINGLE_BARE_PX_RE). A multi-value form like
border-width: 1px 2px (a non-uniform stroke, NOT a triangle) was spared — a
false negative a future drift could hide behind. The triangle-carets-are-
multi-value heuristic spared ALL multi-value, which is the hole.
Fix: flag ANY bare px in a border-width: longhand (single OR multi-value),
and allowlist the three known triangle/caret SELECTORS (.maka-turn-thinking
summary::before, .maka-bubble-assistant …checkbox:checked::after,
.maka-permission-raw > summary::before) whose multi-value is geometry, not a
stroke. Selector tracking walks the line before { to keep the current
selector; a new caret with a new value on a new selector is flagged until
explicitly added to the allowlist (correct — review new carets consciously).
Add negative cases: border-width: 1px 2px on a non-allowlisted selector must
fail; the three allowlisted caret selectors must pass; single bare-px still
fails; token / 0 still pass.
Verification: 1967/1967 desktop tests pass. The real renderer CSS scan stays
clean (the only three border-width: longhands are the allowlisted carets).
…y one (#520 PR4 review)
checkSelectorTier used a single checkedAny flag — if a selector was mapped
with multiple REQUIRED props (e.g. .maka-chat-jump-bottom [width, height],
.settingsNavItem [height, min-height]), the contract only checked that AT
LEAST ONE appeared. Deleting the width declaration but keeping height passed
silently, dropping the square-control width requirement.
Replace checkedAny with seenProps: Set<string>, accumulated across all
matched blocks (base + @media / :state variants), and require every prop in
check.props to appear at least once — a missing required prop is flagged as
'is missing required <prop> declaration'. Each declaration that does appear
is still value-checked against the expected tier. No current mapped selector
is missing a prop, so nothing real is flagged.
Add a negative case: a .maka-chat-jump-bottom fixture with only height must
flag the missing width; a .settingsNavItem fixture missing height OR
min-height must flag; a complete fixture passes.
Verification: 1968/1968 desktop tests pass.
…tor-allowlist impl (#520 PR4 review)
The previous review fix changed border-width longhand scanning from 'only
flag a SINGLE bare-px value' to 'flag ANY bare px (single OR multi-value)
unless the selector is an allowlisted triangle/caret', but the top summary
paragraph still described the old single-only rule — comment, impl, and
negative cases disagreed.
Update the top summary to match: triangle carets are multi-value geometry
allowed only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS), and
any bare px — single OR multi-value — is flagged elsewhere. The item-2
invariant and inline comment already said this; only the summary was stale.
Verification: 1968/1968 desktop tests pass; non-allowlisted border-width:
1px 2px continues to fail.
…e detection (#520 PR4 review)
The helper only checked the FIRST hop: it collected var(--xxx) refs in the
target prop's value and checked each was defined, but did not recurse into a
ref's own value. A chain like --h-control-xs → --space-5 → --missing
(--missing undefined two hops out) passed because --space-5 was defined and
the helper stopped there. It also had no cycle detection (--a → --b → --a
would infinite-loop).
Rewrite as a small DFS from the target prop through every var() ref,
maintaining visiting/visited sets: an undefined ref at any depth throws
'references undefined <name> (via <path>)', and a back-edge to a node on the
current path throws 'circular custom-prop reference: a → b → a'. Each node
must still be declared exactly once. Function name kept; the doc comment now
says 'recursively, with cycle detection' and means it.
Add negative cases: --h-control-xs → --space-5 → --missing must fail (the
2-hop case the old helper missed); --a → --b → --a must fail (cycle). The
direct --space-7 case and the valid closed chain still behave as before.
Verification: 1968/1968 desktop tests pass; typecheck clean. The real
--h-control-* chain (xs/sm/lg/2xl → --space-N → calc(var(--spacing) * N) →
--spacing: 4px; md/xl → calc(var(--spacing) * N) → --spacing) still passes.
@Astro-Han
Astro-Han merged commit 0954d9b into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the refactor/layout-surface-sizing branch July 5, 2026 05:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(ui): converge layout-surface & sizing tokens (#520 PR4) by Astro-Han · Pull Request #539 · apache/maka · GitHub
Skip to content

refactor(ui): converge layout-surface & sizing tokens (#520 PR4) - #539

Merged
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing
Jul 5, 2026
Merged

refactor(ui): converge layout-surface & sizing tokens (#520 PR4)#539
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

PR4 of #520 — converges the layout-surface & sizing dimensions onto tokens + contracts, using the same converge-contract pattern as PR1 (#526) and PR3 (#527). Five items, one commit each (each independently revertible), plus one commit fixing two pre-existing test regressions from PR3, and five follow-up commits addressing review feedback (reference-chain DFS, mapped-prop completeness, triangle-caret selector allowlist, contract/comment consistency).

The user's "碍眼" — sidebar / 会话 / 设置 control-height inconsistency — is item 15 (control heights), done first.

New tokens (maka-tokens.css)

TokenValueItem
--h-control-xs/sm/md/lg/xl/2xlxs/sm/lg/2xl = var(--space-5/6/8/10), md/xl = calc(var(--spacing) * 7/9) (7 and 9 aren't in maka's discrete spacing scale) = 20/24/28/32/36/40px15 — control-height scale (on the 4px spacing ruler, shared with Tailwind h-N)
--border-width-hairline/thick/accent1px / 2px / 3px14 — border stroke weight
--maka-chat-measure680px (promoted to :root from a local .mainColumn token)16 — chat content measure

No @theme inline bridge this PR: control height is numeric Tailwind (h-N), border-width is static (border = 1px), and breakpoints can't use var() in @media (parse-time evaluation) — see item 16.

Convergence

  • item 15 — control heights: 14 CSS control selectors (sidebar nav row, session row, settings nav/back/select, model switcher, composer send, jump-bottom FAB, palette input, first-run checklist rows) snap off-ruler 22/26/30/34/38px onto --h-control-*; TSX arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] convert to the Tailwind ruler scale (min-h-8, min-h-11, max-h-55, …). The feat(ui): govern chat conversation-flow display onto @maka/ui Base UI primitives #332 chat-marker/preview contracts deliberately pin min-h-[28px] / max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to scale re-tuning) — those stay arbitrary and are whitelisted here.
  • item 14 — border width: 221 border: / border-{side}: shorthand widths across 25 CSS files → var(--border-width-*) (perl with (?<![-\w]) lookbehind so the token definitions aren't self-referenced). Border-style (solid/dashed) stays a literal keyword — a named value, not a magic number. CSS-triangle carets (border-width: 4px 0 4px 5px) are multi-value geometry, not strokes, so the contract allows them only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS) and flags any bare px — single OR multi-value — elsewhere.
  • item 13 — box-shadow color (P-SHADOW): 13 bare pure-black box-shadow usages (rgba(0,0,0,A) / oklch(0 0 0 / A)) → oklch(from var(--foreground) l c h / A), geometry preserved (only the color warms). The dark-mode recipe overrides in maka-tokens.css intentionally keep pure-black (dark canvas) — those are token defs, not box-shadow: usages, so out of scope.
  • item 16 — responsive: --maka-chat-measure promoted to :root, 6 redundant , 680px fallbacks dropped, onboarding hero adopts the token. @media breakpoints can't use var(), so a contract whitelists the 8 values in use (620/720/760/820/900/980/990/1100) and bans ad-hoc Npx.
  • item 12 — radius nesting: documents the concentric-radius rule (inner = outer − padding) on the radius tokens; pins the two calc(var(--radius-modal) - 8px) nesting sites so they don't regress to a hardcoded tier. Audit confirms settings inner cards + sub-modals already comply via the radius-converge SELECTOR_TIER.

New contracts

ContractBansPins
control-height-converge-contract.test.tsbare px height/min-height on 14 curated control selectors (each mapped prop must appear); arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] in TSX (whitelist: dots, count badge, 110px scroll cap, two #332 literals)--h-control-* exactly-once → xs/sm/lg/2xl var(--space-N), md/xl calc(var(--spacing) * N); recursive reference-chain closed (undefined ref / cycle throws)
border-width-converge-contract.test.tsbare px width in border:/border-{side}: shorthand; any bare px in border-width: longhand (single OR multi-value) unless allowlisted caret selector; non-keyword border-style:; arbitrary border-[Npx] in TSX--border-width-hairline/thick/accent exactly-once
box-shadow-converge-contract.test.tspure-black color (oklch(0 0 0/A), rgba(0,0,0,A), #000, black) in box-shadow: (multi-line values; token defs not scanned)--shadow-* / --card-shadow / --card-highlight exist
responsive-breakpoint-contract.test.ts@media (max/min-width: Npx) outside the 8-value whitelist; bare 680px in width/max-width/min-width (height caps spared); local --maka-chat-measure re-declaration--maka-chat-measure: 680px exactly-once
radius-nesting-contract.test.tsradius calc addition (breaks concentricity)the two calc(var(--radius-modal) - 8px) nesting sites

Commits

  1. test(desktop): relax exact className match for PR3 min-width additions — fixes two pre-existing test regressions on main (PR3 added min-w-[Nrem] to copy/append/save buttons but didn't update the exact-className="X" regexes). Unblocks a green baseline for PR4 verification.
  2. refactor(ui): converge control heights onto --h-control-* scale (#520 PR4 item 15)
  3. refactor(ui): converge border-width onto --border-width-* tokens (#520 PR4 item 14)
  4. refactor(ui): systematize concentric radius nesting rule (#520 PR4 item 12)
  5. refactor(ui): converge responsive breakpoints + chat content measure (#520 PR4 item 16)
  6. refactor(ui): converge box-shadow color onto foreground-derived (P-SHADOW) (#520 PR4 item 13)

Review-fix commits

  1. fix(ui): close --h-control-* reference chain + tighten mapped-selector tier check — P1: --h-control-md/xl referenced undefined --space-7/9; use calc(var(--spacing) * 7/9). Adds assertCustomPropRefsDefined helper. P3: mapped selectors must use exactly their --h-control-* tier (no var(--space-N) / chrome-token bypass).
  2. fix(ui): ban multi-value bare-px border-width, allowlist triangle carets by selector — P2: border-width: longhand now flags any bare px (single OR multi-value) unless the selector is an allowlisted caret.
  3. fix(test): require every mapped control-height prop to appear, not any one — P3: checkSelectorTier uses seenProps: Set so a multi-prop selector missing width / height / min-height is flagged.
  4. docs(test): fix stale SINGLE-only border-width comment to match selector-allowlist impl.
  5. fix(test): make assertCustomPropRefsDefined a recursive DFS with cycle detection — P3: the helper now recurses through the whole var() chain (catches undefined 2+ hops down) and detects cycles, not just the first hop.

Existing test expectations updated

PR3 min-width + this PR's token replacements broke exact-match assertions in 7 existing contracts (renderer-error-boundary, daily-review-copy-feedback, command-palette-a11y-copy, artifact-pane-layout, permissions-unified-card, startup-loading-shell, project-context-badge, settings-form-a11y). Each update preserves the test's intent (semantic class present / geometry / alpha) and only shifts the expected literal to the new token form.

Verification

  • Tests: npm run -w @maka/desktop test — 1968/1968 pass (was 1935 on main + 2 pre-existing failures; this PR fixes the 2 + adds 33 new contract assertions across 5 files, including the recursive reference-chain DFS guard).
  • Typecheck: npm run typecheck — every workspace clean.
  • Screenshots vs main (turn-narrative, settings-appearance, first-run × light/dark, compare -metric AE -fuzz 2%):
    • first-run: 0% on items 15/16, +0.00009% on item 14 (onboarding 1.5→1px hairline) — item 13 adds 0.
    • turn-narrative: 0.55–0.62% (the 2px control-height snaps in sidebar/会话/composer).
    • settings-appearance: 1.36% (settings nav rows 38→36, multiple rows; was ~1.6% before the P1 reference-chain fix restored the explicit 36px height on --h-control-xl).
    • All diffs are the deliberate 2px control-height snap or the 1.5→1px hairline; no layout-collapse-scale regression. Item 13 (P-SHADOW color) is within the 2% fuzz on light theme (near-black ≈ pure-black at low alpha) — the fix matters on warm/dark shells where pure-black reads as a smudge.

Deferred / out of scope

  • Full box-shadow recipe-converge: mapping the ~20 already-foreground-derived elevation shadows onto var(--shadow-*) recipes would add the design-system 1px border ring + standardize the blur on each — a visual-weight change to compliant surfaces that needs design review, separate from the P-SHADOW color fix in item 13.
  • index.html splash box-shadows: two pure-black rgba(0,0,0,…) shadows live in the pre-render HTML splash (no access to --foreground); left for a separate splash pass.
  • scroll-area: explicitly out of scope per refactor(ui): converge unmanaged design specs (line-height, font-weight, letter-spacing, …) #520 (overlay-scrollbars-contract.test.ts locks OverlayScrollbars).

Astro-Han added 11 commits July 5, 2026 11:40
PR3 (#527) added min-w-[Nrem] utilities to the error-boundary copy button
and the daily-review copy/append/save buttons (text-swap width lock for
复制中…/已复制 feedback). Two contract tests asserted the semantic class
with an exact className="X" regex that required the closing quote right
after the class, so the extra utility class broke the match and the suites
have been red on main since PR3 merged.
Match each semantic class as a whole word in the class list
(className="[^"]*\bX\b[^"]*"), the same form the daily-review test
already used for its negative maka-button check. This keeps the intent
(button uses the semantic class, not a legacy maka-button) while
tolerating the utility classes PR3 deliberately added.
…PR4 item 15)
The sidebar / 会话 / 设置 control heights had drifted onto off-ruler bare
px (22 / 26 / 30 / 34 / 38) while the TSX side used the 4px spacing ruler
via Tailwind h-N. The two scales never aligned, so a sidebar nav row at
34px next to a session row at 30px next to a settings nav at 38px read as
three different systems — the user's 碍眼.
Add a six-tier --h-control-* scale (xs/sm/md/lg/xl/2xl = 20/24/28/32/36/40)
on the 4px spacing ruler (var(--space-N)) so CSS var(--h-control-*) and
Tailwind h-N share one scale. Snap the off-ruler control heights to the
nearest tier and tokenize the on-ruler ones:
.maka-list-row 30 → 32 (--h-control-lg) 会话 row
.maka-search-modal-close 22 → 24 (--h-control-sm)
.maka-search-modal-clear 22 → 24 (--h-control-sm)
.settingsBackButton 38 → 36 (--h-control-xl)
.settingsNavItem 38 → 36 (--h-control-xl)
.settingsSelectTrigger 34 → 32 (--h-control-lg)
.settingsSelectMenuPopup [role=option] 32 → 32 (--h-control-lg)
.maka-model-switcher-trigger 22 → 24 (--h-control-sm)
.maka-chat-jump-bottom 26 → 28 (--h-control-md)
.maka-palette-input-wrap 32 → 32 (--h-control-lg)
.maka-first-run-checklist-error-action 24 → 24 (--h-control-sm)
.maka-first-run-checklist-row > button 38 → 36 (--h-control-xl)
.maka-composer-send-button 30 → 32 (--h-control-lg)
.maka-composer-workspace-picker 24 → 24 (--h-control-sm)
On the TSX side, replace arbitrary h-[Npx] / min-h-[Npx] / max-h-[Npx] with
the Tailwind ruler scale (min-h-8, min-h-11, h-7, max-h-55, max-h-80,
min-h-21, etc.) so TSX and CSS share the 4px ruler. The #332 chat-marker
and chat-preview cascade contracts deliberately pin min-h-[28px] and
max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to
scale re-tuning); those two stay arbitrary and are whitelisted here, with
#332 retaining governance of them.
control-height-converge-contract.test.ts pins the six tokens exactly-once
to their spacing tier, maps a curated set of control selectors to their
expected tier (the radius-contract SELECTOR_TIER pattern — height has no
single anchor the way border-radius does, so the contract scopes to
control selectors, not every height), and bans bare-numeric arbitrary
h-[Npx] / min-h-[Npx] / max-h-[Npx] in TSX with a small whitelist for the
decorator dots, the count badge, the off-ruler 110px scroll cap, and the
two #332-pinned literals. App-chrome bars (--h-titlebar/--h-toolbar/
--h-composer-min/--h-list-header) and content min/max heights stay bare —
they are structure / content, not controls.
Verification: 1941/1941 desktop tests pass. Screenshots vs main:
first-run 0% diff (untouched), turn-narrative 0.6%, settings-appearance
1.6-1.8% — all consistent with the deliberate 2px control-height snap.
…PR4 item 14)
Border COLOR was already tokenized (--border / --border-strong); the WIDTH
was bare px in every `border: 1px solid var(--border)` shorthand plus a
handful of `border-left: 3px solid …` status strips. Add three semantic
weights on a small scale:
--border-width-hairline 1px the universal divider (210+ sites)
--border-width-thick 2px a heavier divider / selected outline
--border-width-accent 3px a status / decorative strip (toast
variant color bars, avatar rings)
Snap the rare 1.5px hairlines to hairline (3 sites, -0.5px) and the one 4px
avatar ring to accent (1 site, -1px). Border-STYLE (solid / dashed) stays a
literal keyword — it is a named value, not a magic number, so tokenizing it
adds indirection with no governance benefit. CSS-triangle carets
(`border-width: 4px 0 4px 5px`) are multi-value geometry, not border
strokes, so the contract only flags a SINGLE bare-px width.
Replace 221 `border:` / `border-{side}:` shorthand widths across 25 CSS
files with `var(--border-width-*)` (perl with `(?<![-\w])` lookbehind so the
`--border-width-hairline: 1px` token definition is not turned into a
self-reference).
border-width-converge-contract.test.ts pins the three tokens exactly-once;
scans `border:` / `border-{side}:` shorthand for bare px (stripping
var()/calc()/oklch() first so color px is not mistaken for the width);
flags a SINGLE bare-px `border-width:` longhand while sparing multi-value
triangle geometry; enforces `border-style:` keyword literals; and bans
arbitrary `border-[Npx]` / `border-{side}-[Npx]` in TSX (Tailwind `border`
defaults to 1px = hairline, so TSX and CSS agree on the value).
Update four existing contracts that hardcoded `border: 1px solid …` to
match the new token form (artifact-pane, command-palette, permissions-
unified-card, startup-loading-shell).
Verification: 1950/1950 desktop tests pass. Screenshots vs main: turn-
narrative / settings-appearance 0% incremental over item 15, first-run
+375px (0.00009%, the onboarding 1.5→1 hairline). No regression.
…em 12)
Roadmap §1.3 / P-RADIUS: when a rounded surface sits inside another rounded
surface with padding between them, the inner radius = outer radius −
padding so the two curves share a center and read as one machined shell.
Document the convention on the radius tokens in maka-tokens.css with the
two forms it takes:
1. outer − padding lands on a tier → pick that tier directly (e.g. an
8px surface card inside a 12px modal with 4px padding: 12 − 4 = 8).
The radius-converge contract SELECTOR_TIER already pins this.
2. outer − padding does NOT land on a tier → use
`calc(var(--radius-*) - Npx)` (the radius-converge calc allowlist
permits only this shrink form). Two sites use it today — an input
inside a 12px modal shell with an 8px inset (12 − 8 = 4px, not a
tier): .maka-search-modal-input-row and .maka-palette-input-wrap.
Audit conclusion: the settings-modal inner cards use the surface/control
tier inside the modal shell (form 1, governed by SELECTOR_TIER), and the
seven settings inner surfaces that keep --radius-modal are peer sub-modals
(login modal, scan modal, select popup), not nested cards — so no nesting
violations and no CSS value changes are needed.
radius-nesting-contract.test.ts pins the two calc-nested input sites so a
later cleanup can't drop the calc and revert to a hardcoded --radius-control
(6px) that would read too round against the 12px shell corners, and
restates the shrink-only rule (no calc addition) for the nesting sites.
Verification: 1952/1952 desktop tests pass. No CSS values changed → no
visual regression (screenshots unchanged from item 14).
…520 PR4 item 16)
Two responsive seams had drifted:
1. @media breakpoints. CSS @media queries evaluate at parse time, before
custom properties resolve, so `@media (max-width: var(--bp))` is INVALID
— breakpoints cannot be tokenized with var(). Instead a contract
whitelists the eight max/min-width pixel values the app actually uses
(620 / 720 / 760 / 820 / 900 / 980 / 990 / 1100) and bans any other bare
`@media (max-width: Npx)`. A new breakpoint must be added to the
whitelist, which forces a conscious decision instead of a silent drift.
prefers-reduced-motion / prefers-color-scheme are not width breakpoints
and stay out of scope.
2. The chat content measure (--maka-chat-measure: 680px). This IS a regular
property value, so it can be tokenized. It was a LOCAL token on
.mainColumn with a `680px` fallback at every call site; promote it to
:root in maka-tokens.css so it is canonical, drop the redundant
`, 680px` fallbacks at six call sites, and adopt it in the onboarding
hero (was a bare `width: min(680px, 100%)`). The chat column, tool
output, composer, and onboarding hero now share one measure with no
fallback. A 680px HEIGHT cap on the settings form modal is a different
semantic and stays bare — the contract scopes the ban to width.
responsive-breakpoint-contract.test.ts whitelists the breakpoints, pins
--maka-chat-measure to 680px exactly-once in maka-tokens.css, bans a local
re-declaration in styles/, and bans a bare 680px in width / max-width /
min-width declarations.
Update project-context-badge.test.ts to match the no-fallback
composer-workspace-row width formula.
Verification: 1959/1959 desktop tests pass. No CSS values changed (the
chat-measure move is functionally a no-op — every site already resolved to
680px via the local def or the fallback) → first-run screenshot unchanged
from item 14 (375px / 0.00009%, the onboarding 1.5→1 hairline).
…ADOW) (#520 PR4 item 13)
P-SHADOW (roadmap §1.2 / maka-tokens.css): blur layers in box-shadow must
derive from --foreground, not pure black — a pure-black rgba()/oklch()
shadow on maka's warm shell reads as a dirty smudge, while a foreground-
derived shadow shares one light source with the border ring and ink.
Thirteen bare pure-black box-shadow usages (rgba(0,0,0,A) and
oklch(0 0 0 / A)) survived across settings, plan-reminders, and
reference-shell. Swap each pure-black color for
oklch(from var(--foreground) l c h / A), KEEPING the shadow geometry
(offset / blur / spread) intact so only the color warms — the safe
P-SHADOW win. The dark-mode shadow recipe overrides in maka-tokens.css
(--shadow-medium / --shadow-modal for dark mode) intentionally keep
pure-black oklch(0 0 0 / 0.5|0.6) — on a dark canvas a pure-black shadow is
correct ("dark mode shadows collapse to a single ring; modal keeps one
deep drop") — those are token definitions, not box-shadow usages, so they
are out of scope.
The broader recipe-converge (mapping the ~20 already-foreground-derived
elevation shadows onto var(--shadow-*) recipes) is deferred: the recipes
add a 1px border ring + standardize the blur, which changes the visual
weight of compliant surfaces — a design-review change, separate from the
P-SHADOW color fix here. The themed colored skill-card shadow
(oklch(0.2 0.02 224 / 0.4)) and the hard 1px blur-0 hover edge
(0 1px 0 0 ...) are not pure-black and are not flagged.
box-shadow-converge-contract.test.ts bans a pure-black color
(oklch(0 0 0 / A) / rgba(0,0,0,A) / #000 / black with non-zero alpha) in
any box-shadow declaration (multi-line values captured), and confirms the
shadow recipes are defined. It does not scan --shadow-*: token
definitions, so the intentional dark-mode pure-black recipes are not
flagged.
Update settings-form-a11y-contract.test.ts: the three connection / auth-
contract / model-table near-flat shadow assertions pinned the pure-black
rgba form as the "reference implementation" shadow; P-SHADOW supersedes
that — the geometry (0 1px 3px) and alpha (0.03) are unchanged, only the
color form moves to foreground-derived.
Verification: 1965/1965 desktop tests pass. Screenshots vs main: 0%
incremental over item 14/15/16 (the color warm-up is within the 2% fuzz on
light theme — near-black and pure-black at low alpha are visually
identical there; the fix matters on warm/dark shells where pure-black
reads as a smudge).
…r tier check (#520 PR4 review)
P1: --h-control-md / --h-control-xl referenced --space-7 / --space-9, which
are NOT in maka's discrete spacing scale (it defines --space-0/1/2/3/4/5/6/
8/10/12/16 — 7 and 9 are skipped). A var() with no fallback resolving to an
undefined custom prop is invalid at computed-value time, so the jump-bottom
FAB (md=28px) and settings nav/back rows (xl=36px) collapsed to width/height
auto / min-height 0 — masked by their padding/content, but fragile and wrong.
Fix with calc(var(--spacing) * 7) / calc(var(--spacing) * 9) (the --spacing
base step IS defined, = 4px), so 28/36px resolve explicitly without expanding
the general --space-* scale (which would invite p-7 / gap-7 drift).
Add assertCustomPropRefsDefined to css-test-helpers: walk the var(--xxx)
reference chain in a token's value and assert every referenced custom prop is
defined somewhere in the tokens file (token :root, @theme inline, or bridge
alias). A pin-only check that --h-control-md is declared with var(--space-7)
passes while the token is broken — this helper catches the bug class. Add a
contract test that runs it on every --h-control-* tier, plus a negative case
that feeds a --space-7 reference and asserts it throws.
P3: isAllowedControlHeight previously let a MAPPED selector pass with a
direct var(--space-N), calc(var(--spacing) * N), or a layout-chrome token
(--h-titlebar / --maka-sidebar-topbar-button-size / …) instead of its
expected --h-control-* tier — bypassing the semantic scale the mapping table
exists to enforce. Tighten: a mapped selector's height / min-height / width
must be exactly var(--h-control-<expected>) or a neutral literal (0 / auto /
100%). Unmapped controls are added to CONTROL_HEIGHT rather than allowed to
slip via a space token. No current mapped selector uses a bypass value, so
nothing real is flagged; the negative cases now assert the bypass forms fail.
Verification: 1967/1967 desktop tests pass (was 1965; +1 ref-chain test, +1
ref-chain negative case). Screenshots vs main: settings-appearance diff drops
0.0159 → 0.0136 (the settings nav rows restore to explicit 36px instead of
the padding-masked auto height); turn-narrative unchanged (the FAB collapse
was already masked by its icon + padding). typecheck clean.
…ets by selector (#520 PR4 review)
P2: the border-width contract only flagged a SINGLE bare-px value in a
border-width: longhand (SINGLE_BARE_PX_RE). A multi-value form like
border-width: 1px 2px (a non-uniform stroke, NOT a triangle) was spared — a
false negative a future drift could hide behind. The triangle-carets-are-
multi-value heuristic spared ALL multi-value, which is the hole.
Fix: flag ANY bare px in a border-width: longhand (single OR multi-value),
and allowlist the three known triangle/caret SELECTORS (.maka-turn-thinking
summary::before, .maka-bubble-assistant …checkbox:checked::after,
.maka-permission-raw > summary::before) whose multi-value is geometry, not a
stroke. Selector tracking walks the line before { to keep the current
selector; a new caret with a new value on a new selector is flagged until
explicitly added to the allowlist (correct — review new carets consciously).
Add negative cases: border-width: 1px 2px on a non-allowlisted selector must
fail; the three allowlisted caret selectors must pass; single bare-px still
fails; token / 0 still pass.
Verification: 1967/1967 desktop tests pass. The real renderer CSS scan stays
clean (the only three border-width: longhands are the allowlisted carets).
…y one (#520 PR4 review)
checkSelectorTier used a single checkedAny flag — if a selector was mapped
with multiple REQUIRED props (e.g. .maka-chat-jump-bottom [width, height],
.settingsNavItem [height, min-height]), the contract only checked that AT
LEAST ONE appeared. Deleting the width declaration but keeping height passed
silently, dropping the square-control width requirement.
Replace checkedAny with seenProps: Set<string>, accumulated across all
matched blocks (base + @media / :state variants), and require every prop in
check.props to appear at least once — a missing required prop is flagged as
'is missing required <prop> declaration'. Each declaration that does appear
is still value-checked against the expected tier. No current mapped selector
is missing a prop, so nothing real is flagged.
Add a negative case: a .maka-chat-jump-bottom fixture with only height must
flag the missing width; a .settingsNavItem fixture missing height OR
min-height must flag; a complete fixture passes.
Verification: 1968/1968 desktop tests pass.
…tor-allowlist impl (#520 PR4 review)
The previous review fix changed border-width longhand scanning from 'only
flag a SINGLE bare-px value' to 'flag ANY bare px (single OR multi-value)
unless the selector is an allowlisted triangle/caret', but the top summary
paragraph still described the old single-only rule — comment, impl, and
negative cases disagreed.
Update the top summary to match: triangle carets are multi-value geometry
allowed only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS), and
any bare px — single OR multi-value — is flagged elsewhere. The item-2
invariant and inline comment already said this; only the summary was stale.
Verification: 1968/1968 desktop tests pass; non-allowlisted border-width:
1px 2px continues to fail.
…e detection (#520 PR4 review)
The helper only checked the FIRST hop: it collected var(--xxx) refs in the
target prop's value and checked each was defined, but did not recurse into a
ref's own value. A chain like --h-control-xs → --space-5 → --missing
(--missing undefined two hops out) passed because --space-5 was defined and
the helper stopped there. It also had no cycle detection (--a → --b → --a
would infinite-loop).
Rewrite as a small DFS from the target prop through every var() ref,
maintaining visiting/visited sets: an undefined ref at any depth throws
'references undefined <name> (via <path>)', and a back-edge to a node on the
current path throws 'circular custom-prop reference: a → b → a'. Each node
must still be declared exactly once. Function name kept; the doc comment now
says 'recursively, with cycle detection' and means it.
Add negative cases: --h-control-xs → --space-5 → --missing must fail (the
2-hop case the old helper missed); --a → --b → --a must fail (cycle). The
direct --space-7 case and the valid closed chain still behave as before.
Verification: 1968/1968 desktop tests pass; typecheck clean. The real
--h-control-* chain (xs/sm/lg/2xl → --space-N → calc(var(--spacing) * N) →
--spacing: 4px; md/xl → calc(var(--spacing) * N) → --spacing) still passes.
@Astro-Han
Astro-Han merged commit 0954d9b into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the refactor/layout-surface-sizing branch July 5, 2026 05:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); refactor(ui): converge layout-surface & sizing tokens (#520 PR4) by Astro-Han · Pull Request #539 · apache/maka · GitHub
Skip to content

refactor(ui): converge layout-surface & sizing tokens (#520 PR4) - #539

Merged
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing
Jul 5, 2026
Merged

refactor(ui): converge layout-surface & sizing tokens (#520 PR4)#539
Astro-Han merged 11 commits into
mainfrom
refactor/layout-surface-sizing

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

PR4 of #520 — converges the layout-surface & sizing dimensions onto tokens + contracts, using the same converge-contract pattern as PR1 (#526) and PR3 (#527). Five items, one commit each (each independently revertible), plus one commit fixing two pre-existing test regressions from PR3, and five follow-up commits addressing review feedback (reference-chain DFS, mapped-prop completeness, triangle-caret selector allowlist, contract/comment consistency).

The user's "碍眼" — sidebar / 会话 / 设置 control-height inconsistency — is item 15 (control heights), done first.

New tokens (maka-tokens.css)

TokenValueItem
--h-control-xs/sm/md/lg/xl/2xlxs/sm/lg/2xl = var(--space-5/6/8/10), md/xl = calc(var(--spacing) * 7/9) (7 and 9 aren't in maka's discrete spacing scale) = 20/24/28/32/36/40px15 — control-height scale (on the 4px spacing ruler, shared with Tailwind h-N)
--border-width-hairline/thick/accent1px / 2px / 3px14 — border stroke weight
--maka-chat-measure680px (promoted to :root from a local .mainColumn token)16 — chat content measure

No @theme inline bridge this PR: control height is numeric Tailwind (h-N), border-width is static (border = 1px), and breakpoints can't use var() in @media (parse-time evaluation) — see item 16.

Convergence

  • item 15 — control heights: 14 CSS control selectors (sidebar nav row, session row, settings nav/back/select, model switcher, composer send, jump-bottom FAB, palette input, first-run checklist rows) snap off-ruler 22/26/30/34/38px onto --h-control-*; TSX arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] convert to the Tailwind ruler scale (min-h-8, min-h-11, max-h-55, …). The feat(ui): govern chat conversation-flow display onto @maka/ui Base UI primitives #332 chat-marker/preview contracts deliberately pin min-h-[28px] / max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to scale re-tuning) — those stay arbitrary and are whitelisted here.
  • item 14 — border width: 221 border: / border-{side}: shorthand widths across 25 CSS files → var(--border-width-*) (perl with (?<![-\w]) lookbehind so the token definitions aren't self-referenced). Border-style (solid/dashed) stays a literal keyword — a named value, not a magic number. CSS-triangle carets (border-width: 4px 0 4px 5px) are multi-value geometry, not strokes, so the contract allows them only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS) and flags any bare px — single OR multi-value — elsewhere.
  • item 13 — box-shadow color (P-SHADOW): 13 bare pure-black box-shadow usages (rgba(0,0,0,A) / oklch(0 0 0 / A)) → oklch(from var(--foreground) l c h / A), geometry preserved (only the color warms). The dark-mode recipe overrides in maka-tokens.css intentionally keep pure-black (dark canvas) — those are token defs, not box-shadow: usages, so out of scope.
  • item 16 — responsive: --maka-chat-measure promoted to :root, 6 redundant , 680px fallbacks dropped, onboarding hero adopts the token. @media breakpoints can't use var(), so a contract whitelists the 8 values in use (620/720/760/820/900/980/990/1100) and bans ad-hoc Npx.
  • item 12 — radius nesting: documents the concentric-radius rule (inner = outer − padding) on the radius tokens; pins the two calc(var(--radius-modal) - 8px) nesting sites so they don't regress to a hardcoded tier. Audit confirms settings inner cards + sub-modals already comply via the radius-converge SELECTOR_TIER.

New contracts

ContractBansPins
control-height-converge-contract.test.tsbare px height/min-height on 14 curated control selectors (each mapped prop must appear); arbitrary h-[Npx]/min-h-[Npx]/max-h-[Npx] in TSX (whitelist: dots, count badge, 110px scroll cap, two #332 literals)--h-control-* exactly-once → xs/sm/lg/2xl var(--space-N), md/xl calc(var(--spacing) * N); recursive reference-chain closed (undefined ref / cycle throws)
border-width-converge-contract.test.tsbare px width in border:/border-{side}: shorthand; any bare px in border-width: longhand (single OR multi-value) unless allowlisted caret selector; non-keyword border-style:; arbitrary border-[Npx] in TSX--border-width-hairline/thick/accent exactly-once
box-shadow-converge-contract.test.tspure-black color (oklch(0 0 0/A), rgba(0,0,0,A), #000, black) in box-shadow: (multi-line values; token defs not scanned)--shadow-* / --card-shadow / --card-highlight exist
responsive-breakpoint-contract.test.ts@media (max/min-width: Npx) outside the 8-value whitelist; bare 680px in width/max-width/min-width (height caps spared); local --maka-chat-measure re-declaration--maka-chat-measure: 680px exactly-once
radius-nesting-contract.test.tsradius calc addition (breaks concentricity)the two calc(var(--radius-modal) - 8px) nesting sites

Commits

  1. test(desktop): relax exact className match for PR3 min-width additions — fixes two pre-existing test regressions on main (PR3 added min-w-[Nrem] to copy/append/save buttons but didn't update the exact-className="X" regexes). Unblocks a green baseline for PR4 verification.
  2. refactor(ui): converge control heights onto --h-control-* scale (#520 PR4 item 15)
  3. refactor(ui): converge border-width onto --border-width-* tokens (#520 PR4 item 14)
  4. refactor(ui): systematize concentric radius nesting rule (#520 PR4 item 12)
  5. refactor(ui): converge responsive breakpoints + chat content measure (#520 PR4 item 16)
  6. refactor(ui): converge box-shadow color onto foreground-derived (P-SHADOW) (#520 PR4 item 13)

Review-fix commits

  1. fix(ui): close --h-control-* reference chain + tighten mapped-selector tier check — P1: --h-control-md/xl referenced undefined --space-7/9; use calc(var(--spacing) * 7/9). Adds assertCustomPropRefsDefined helper. P3: mapped selectors must use exactly their --h-control-* tier (no var(--space-N) / chrome-token bypass).
  2. fix(ui): ban multi-value bare-px border-width, allowlist triangle carets by selector — P2: border-width: longhand now flags any bare px (single OR multi-value) unless the selector is an allowlisted caret.
  3. fix(test): require every mapped control-height prop to appear, not any one — P3: checkSelectorTier uses seenProps: Set so a multi-prop selector missing width / height / min-height is flagged.
  4. docs(test): fix stale SINGLE-only border-width comment to match selector-allowlist impl.
  5. fix(test): make assertCustomPropRefsDefined a recursive DFS with cycle detection — P3: the helper now recurses through the whole var() chain (catches undefined 2+ hops down) and detects cycles, not just the first hop.

Existing test expectations updated

PR3 min-width + this PR's token replacements broke exact-match assertions in 7 existing contracts (renderer-error-boundary, daily-review-copy-feedback, command-palette-a11y-copy, artifact-pane-layout, permissions-unified-card, startup-loading-shell, project-context-badge, settings-form-a11y). Each update preserves the test's intent (semantic class present / geometry / alpha) and only shifts the expected literal to the new token form.

Verification

  • Tests: npm run -w @maka/desktop test — 1968/1968 pass (was 1935 on main + 2 pre-existing failures; this PR fixes the 2 + adds 33 new contract assertions across 5 files, including the recursive reference-chain DFS guard).
  • Typecheck: npm run typecheck — every workspace clean.
  • Screenshots vs main (turn-narrative, settings-appearance, first-run × light/dark, compare -metric AE -fuzz 2%):
    • first-run: 0% on items 15/16, +0.00009% on item 14 (onboarding 1.5→1px hairline) — item 13 adds 0.
    • turn-narrative: 0.55–0.62% (the 2px control-height snaps in sidebar/会话/composer).
    • settings-appearance: 1.36% (settings nav rows 38→36, multiple rows; was ~1.6% before the P1 reference-chain fix restored the explicit 36px height on --h-control-xl).
    • All diffs are the deliberate 2px control-height snap or the 1.5→1px hairline; no layout-collapse-scale regression. Item 13 (P-SHADOW color) is within the 2% fuzz on light theme (near-black ≈ pure-black at low alpha) — the fix matters on warm/dark shells where pure-black reads as a smudge.

Deferred / out of scope

  • Full box-shadow recipe-converge: mapping the ~20 already-foreground-derived elevation shadows onto var(--shadow-*) recipes would add the design-system 1px border ring + standardize the blur on each — a visual-weight change to compliant surfaces that needs design review, separate from the P-SHADOW color fix in item 13.
  • index.html splash box-shadows: two pure-black rgba(0,0,0,…) shadows live in the pre-render HTML splash (no access to --foreground); left for a separate splash pass.
  • scroll-area: explicitly out of scope per refactor(ui): converge unmanaged design specs (line-height, font-weight, letter-spacing, …) #520 (overlay-scrollbars-contract.test.ts locks OverlayScrollbars).

Astro-Han added 11 commits July 5, 2026 11:40
PR3 (#527) added min-w-[Nrem] utilities to the error-boundary copy button
and the daily-review copy/append/save buttons (text-swap width lock for
复制中…/已复制 feedback). Two contract tests asserted the semantic class
with an exact className="X" regex that required the closing quote right
after the class, so the extra utility class broke the match and the suites
have been red on main since PR3 merged.
Match each semantic class as a whole word in the class list
(className="[^"]*\bX\b[^"]*"), the same form the daily-review test
already used for its negative maka-button check. This keeps the intent
(button uses the semantic class, not a legacy maka-button) while
tolerating the utility classes PR3 deliberately added.
…PR4 item 15)
The sidebar / 会话 / 设置 control heights had drifted onto off-ruler bare
px (22 / 26 / 30 / 34 / 38) while the TSX side used the 4px spacing ruler
via Tailwind h-N. The two scales never aligned, so a sidebar nav row at
34px next to a session row at 30px next to a settings nav at 38px read as
three different systems — the user's 碍眼.
Add a six-tier --h-control-* scale (xs/sm/md/lg/xl/2xl = 20/24/28/32/36/40)
on the 4px spacing ruler (var(--space-N)) so CSS var(--h-control-*) and
Tailwind h-N share one scale. Snap the off-ruler control heights to the
nearest tier and tokenize the on-ruler ones:
.maka-list-row 30 → 32 (--h-control-lg) 会话 row
.maka-search-modal-close 22 → 24 (--h-control-sm)
.maka-search-modal-clear 22 → 24 (--h-control-sm)
.settingsBackButton 38 → 36 (--h-control-xl)
.settingsNavItem 38 → 36 (--h-control-xl)
.settingsSelectTrigger 34 → 32 (--h-control-lg)
.settingsSelectMenuPopup [role=option] 32 → 32 (--h-control-lg)
.maka-model-switcher-trigger 22 → 24 (--h-control-sm)
.maka-chat-jump-bottom 26 → 28 (--h-control-md)
.maka-palette-input-wrap 32 → 32 (--h-control-lg)
.maka-first-run-checklist-error-action 24 → 24 (--h-control-sm)
.maka-first-run-checklist-row > button 38 → 36 (--h-control-xl)
.maka-composer-send-button 30 → 32 (--h-control-lg)
.maka-composer-workspace-picker 24 → 24 (--h-control-sm)
On the TSX side, replace arbitrary h-[Npx] / min-h-[Npx] / max-h-[Npx] with
the Tailwind ruler scale (min-h-8, min-h-11, h-7, max-h-55, max-h-80,
min-h-21, etc.) so TSX and CSS share the 4px ruler. The #332 chat-marker
and chat-preview cascade contracts deliberately pin min-h-[28px] and
max-h-[180px] as arbitrary literals (a "literalize vehicle" immune to
scale re-tuning); those two stay arbitrary and are whitelisted here, with
#332 retaining governance of them.
control-height-converge-contract.test.ts pins the six tokens exactly-once
to their spacing tier, maps a curated set of control selectors to their
expected tier (the radius-contract SELECTOR_TIER pattern — height has no
single anchor the way border-radius does, so the contract scopes to
control selectors, not every height), and bans bare-numeric arbitrary
h-[Npx] / min-h-[Npx] / max-h-[Npx] in TSX with a small whitelist for the
decorator dots, the count badge, the off-ruler 110px scroll cap, and the
two #332-pinned literals. App-chrome bars (--h-titlebar/--h-toolbar/
--h-composer-min/--h-list-header) and content min/max heights stay bare —
they are structure / content, not controls.
Verification: 1941/1941 desktop tests pass. Screenshots vs main:
first-run 0% diff (untouched), turn-narrative 0.6%, settings-appearance
1.6-1.8% — all consistent with the deliberate 2px control-height snap.
…PR4 item 14)
Border COLOR was already tokenized (--border / --border-strong); the WIDTH
was bare px in every `border: 1px solid var(--border)` shorthand plus a
handful of `border-left: 3px solid …` status strips. Add three semantic
weights on a small scale:
--border-width-hairline 1px the universal divider (210+ sites)
--border-width-thick 2px a heavier divider / selected outline
--border-width-accent 3px a status / decorative strip (toast
variant color bars, avatar rings)
Snap the rare 1.5px hairlines to hairline (3 sites, -0.5px) and the one 4px
avatar ring to accent (1 site, -1px). Border-STYLE (solid / dashed) stays a
literal keyword — it is a named value, not a magic number, so tokenizing it
adds indirection with no governance benefit. CSS-triangle carets
(`border-width: 4px 0 4px 5px`) are multi-value geometry, not border
strokes, so the contract only flags a SINGLE bare-px width.
Replace 221 `border:` / `border-{side}:` shorthand widths across 25 CSS
files with `var(--border-width-*)` (perl with `(?<![-\w])` lookbehind so the
`--border-width-hairline: 1px` token definition is not turned into a
self-reference).
border-width-converge-contract.test.ts pins the three tokens exactly-once;
scans `border:` / `border-{side}:` shorthand for bare px (stripping
var()/calc()/oklch() first so color px is not mistaken for the width);
flags a SINGLE bare-px `border-width:` longhand while sparing multi-value
triangle geometry; enforces `border-style:` keyword literals; and bans
arbitrary `border-[Npx]` / `border-{side}-[Npx]` in TSX (Tailwind `border`
defaults to 1px = hairline, so TSX and CSS agree on the value).
Update four existing contracts that hardcoded `border: 1px solid …` to
match the new token form (artifact-pane, command-palette, permissions-
unified-card, startup-loading-shell).
Verification: 1950/1950 desktop tests pass. Screenshots vs main: turn-
narrative / settings-appearance 0% incremental over item 15, first-run
+375px (0.00009%, the onboarding 1.5→1 hairline). No regression.
…em 12)
Roadmap §1.3 / P-RADIUS: when a rounded surface sits inside another rounded
surface with padding between them, the inner radius = outer radius −
padding so the two curves share a center and read as one machined shell.
Document the convention on the radius tokens in maka-tokens.css with the
two forms it takes:
1. outer − padding lands on a tier → pick that tier directly (e.g. an
8px surface card inside a 12px modal with 4px padding: 12 − 4 = 8).
The radius-converge contract SELECTOR_TIER already pins this.
2. outer − padding does NOT land on a tier → use
`calc(var(--radius-*) - Npx)` (the radius-converge calc allowlist
permits only this shrink form). Two sites use it today — an input
inside a 12px modal shell with an 8px inset (12 − 8 = 4px, not a
tier): .maka-search-modal-input-row and .maka-palette-input-wrap.
Audit conclusion: the settings-modal inner cards use the surface/control
tier inside the modal shell (form 1, governed by SELECTOR_TIER), and the
seven settings inner surfaces that keep --radius-modal are peer sub-modals
(login modal, scan modal, select popup), not nested cards — so no nesting
violations and no CSS value changes are needed.
radius-nesting-contract.test.ts pins the two calc-nested input sites so a
later cleanup can't drop the calc and revert to a hardcoded --radius-control
(6px) that would read too round against the 12px shell corners, and
restates the shrink-only rule (no calc addition) for the nesting sites.
Verification: 1952/1952 desktop tests pass. No CSS values changed → no
visual regression (screenshots unchanged from item 14).
…520 PR4 item 16)
Two responsive seams had drifted:
1. @media breakpoints. CSS @media queries evaluate at parse time, before
custom properties resolve, so `@media (max-width: var(--bp))` is INVALID
— breakpoints cannot be tokenized with var(). Instead a contract
whitelists the eight max/min-width pixel values the app actually uses
(620 / 720 / 760 / 820 / 900 / 980 / 990 / 1100) and bans any other bare
`@media (max-width: Npx)`. A new breakpoint must be added to the
whitelist, which forces a conscious decision instead of a silent drift.
prefers-reduced-motion / prefers-color-scheme are not width breakpoints
and stay out of scope.
2. The chat content measure (--maka-chat-measure: 680px). This IS a regular
property value, so it can be tokenized. It was a LOCAL token on
.mainColumn with a `680px` fallback at every call site; promote it to
:root in maka-tokens.css so it is canonical, drop the redundant
`, 680px` fallbacks at six call sites, and adopt it in the onboarding
hero (was a bare `width: min(680px, 100%)`). The chat column, tool
output, composer, and onboarding hero now share one measure with no
fallback. A 680px HEIGHT cap on the settings form modal is a different
semantic and stays bare — the contract scopes the ban to width.
responsive-breakpoint-contract.test.ts whitelists the breakpoints, pins
--maka-chat-measure to 680px exactly-once in maka-tokens.css, bans a local
re-declaration in styles/, and bans a bare 680px in width / max-width /
min-width declarations.
Update project-context-badge.test.ts to match the no-fallback
composer-workspace-row width formula.
Verification: 1959/1959 desktop tests pass. No CSS values changed (the
chat-measure move is functionally a no-op — every site already resolved to
680px via the local def or the fallback) → first-run screenshot unchanged
from item 14 (375px / 0.00009%, the onboarding 1.5→1 hairline).
…ADOW) (#520 PR4 item 13)
P-SHADOW (roadmap §1.2 / maka-tokens.css): blur layers in box-shadow must
derive from --foreground, not pure black — a pure-black rgba()/oklch()
shadow on maka's warm shell reads as a dirty smudge, while a foreground-
derived shadow shares one light source with the border ring and ink.
Thirteen bare pure-black box-shadow usages (rgba(0,0,0,A) and
oklch(0 0 0 / A)) survived across settings, plan-reminders, and
reference-shell. Swap each pure-black color for
oklch(from var(--foreground) l c h / A), KEEPING the shadow geometry
(offset / blur / spread) intact so only the color warms — the safe
P-SHADOW win. The dark-mode shadow recipe overrides in maka-tokens.css
(--shadow-medium / --shadow-modal for dark mode) intentionally keep
pure-black oklch(0 0 0 / 0.5|0.6) — on a dark canvas a pure-black shadow is
correct ("dark mode shadows collapse to a single ring; modal keeps one
deep drop") — those are token definitions, not box-shadow usages, so they
are out of scope.
The broader recipe-converge (mapping the ~20 already-foreground-derived
elevation shadows onto var(--shadow-*) recipes) is deferred: the recipes
add a 1px border ring + standardize the blur, which changes the visual
weight of compliant surfaces — a design-review change, separate from the
P-SHADOW color fix here. The themed colored skill-card shadow
(oklch(0.2 0.02 224 / 0.4)) and the hard 1px blur-0 hover edge
(0 1px 0 0 ...) are not pure-black and are not flagged.
box-shadow-converge-contract.test.ts bans a pure-black color
(oklch(0 0 0 / A) / rgba(0,0,0,A) / #000 / black with non-zero alpha) in
any box-shadow declaration (multi-line values captured), and confirms the
shadow recipes are defined. It does not scan --shadow-*: token
definitions, so the intentional dark-mode pure-black recipes are not
flagged.
Update settings-form-a11y-contract.test.ts: the three connection / auth-
contract / model-table near-flat shadow assertions pinned the pure-black
rgba form as the "reference implementation" shadow; P-SHADOW supersedes
that — the geometry (0 1px 3px) and alpha (0.03) are unchanged, only the
color form moves to foreground-derived.
Verification: 1965/1965 desktop tests pass. Screenshots vs main: 0%
incremental over item 14/15/16 (the color warm-up is within the 2% fuzz on
light theme — near-black and pure-black at low alpha are visually
identical there; the fix matters on warm/dark shells where pure-black
reads as a smudge).
…r tier check (#520 PR4 review)
P1: --h-control-md / --h-control-xl referenced --space-7 / --space-9, which
are NOT in maka's discrete spacing scale (it defines --space-0/1/2/3/4/5/6/
8/10/12/16 — 7 and 9 are skipped). A var() with no fallback resolving to an
undefined custom prop is invalid at computed-value time, so the jump-bottom
FAB (md=28px) and settings nav/back rows (xl=36px) collapsed to width/height
auto / min-height 0 — masked by their padding/content, but fragile and wrong.
Fix with calc(var(--spacing) * 7) / calc(var(--spacing) * 9) (the --spacing
base step IS defined, = 4px), so 28/36px resolve explicitly without expanding
the general --space-* scale (which would invite p-7 / gap-7 drift).
Add assertCustomPropRefsDefined to css-test-helpers: walk the var(--xxx)
reference chain in a token's value and assert every referenced custom prop is
defined somewhere in the tokens file (token :root, @theme inline, or bridge
alias). A pin-only check that --h-control-md is declared with var(--space-7)
passes while the token is broken — this helper catches the bug class. Add a
contract test that runs it on every --h-control-* tier, plus a negative case
that feeds a --space-7 reference and asserts it throws.
P3: isAllowedControlHeight previously let a MAPPED selector pass with a
direct var(--space-N), calc(var(--spacing) * N), or a layout-chrome token
(--h-titlebar / --maka-sidebar-topbar-button-size / …) instead of its
expected --h-control-* tier — bypassing the semantic scale the mapping table
exists to enforce. Tighten: a mapped selector's height / min-height / width
must be exactly var(--h-control-<expected>) or a neutral literal (0 / auto /
100%). Unmapped controls are added to CONTROL_HEIGHT rather than allowed to
slip via a space token. No current mapped selector uses a bypass value, so
nothing real is flagged; the negative cases now assert the bypass forms fail.
Verification: 1967/1967 desktop tests pass (was 1965; +1 ref-chain test, +1
ref-chain negative case). Screenshots vs main: settings-appearance diff drops
0.0159 → 0.0136 (the settings nav rows restore to explicit 36px instead of
the padding-masked auto height); turn-narrative unchanged (the FAB collapse
was already masked by its icon + padding). typecheck clean.
…ets by selector (#520 PR4 review)
P2: the border-width contract only flagged a SINGLE bare-px value in a
border-width: longhand (SINGLE_BARE_PX_RE). A multi-value form like
border-width: 1px 2px (a non-uniform stroke, NOT a triangle) was spared — a
false negative a future drift could hide behind. The triangle-carets-are-
multi-value heuristic spared ALL multi-value, which is the hole.
Fix: flag ANY bare px in a border-width: longhand (single OR multi-value),
and allowlist the three known triangle/caret SELECTORS (.maka-turn-thinking
summary::before, .maka-bubble-assistant …checkbox:checked::after,
.maka-permission-raw > summary::before) whose multi-value is geometry, not a
stroke. Selector tracking walks the line before { to keep the current
selector; a new caret with a new value on a new selector is flagged until
explicitly added to the allowlist (correct — review new carets consciously).
Add negative cases: border-width: 1px 2px on a non-allowlisted selector must
fail; the three allowlisted caret selectors must pass; single bare-px still
fails; token / 0 still pass.
Verification: 1967/1967 desktop tests pass. The real renderer CSS scan stays
clean (the only three border-width: longhands are the allowlisted carets).
…y one (#520 PR4 review)
checkSelectorTier used a single checkedAny flag — if a selector was mapped
with multiple REQUIRED props (e.g. .maka-chat-jump-bottom [width, height],
.settingsNavItem [height, min-height]), the contract only checked that AT
LEAST ONE appeared. Deleting the width declaration but keeping height passed
silently, dropping the square-control width requirement.
Replace checkedAny with seenProps: Set<string>, accumulated across all
matched blocks (base + @media / :state variants), and require every prop in
check.props to appear at least once — a missing required prop is flagged as
'is missing required <prop> declaration'. Each declaration that does appear
is still value-checked against the expected tier. No current mapped selector
is missing a prop, so nothing real is flagged.
Add a negative case: a .maka-chat-jump-bottom fixture with only height must
flag the missing width; a .settingsNavItem fixture missing height OR
min-height must flag; a complete fixture passes.
Verification: 1968/1968 desktop tests pass.
…tor-allowlist impl (#520 PR4 review)
The previous review fix changed border-width longhand scanning from 'only
flag a SINGLE bare-px value' to 'flag ANY bare px (single OR multi-value)
unless the selector is an allowlisted triangle/caret', but the top summary
paragraph still described the old single-only rule — comment, impl, and
negative cases disagreed.
Update the top summary to match: triangle carets are multi-value geometry
allowed only on allowlisted caret selectors (TRIANGLE_CARET_SELECTORS), and
any bare px — single OR multi-value — is flagged elsewhere. The item-2
invariant and inline comment already said this; only the summary was stale.
Verification: 1968/1968 desktop tests pass; non-allowlisted border-width:
1px 2px continues to fail.
…e detection (#520 PR4 review)
The helper only checked the FIRST hop: it collected var(--xxx) refs in the
target prop's value and checked each was defined, but did not recurse into a
ref's own value. A chain like --h-control-xs → --space-5 → --missing
(--missing undefined two hops out) passed because --space-5 was defined and
the helper stopped there. It also had no cycle detection (--a → --b → --a
would infinite-loop).
Rewrite as a small DFS from the target prop through every var() ref,
maintaining visiting/visited sets: an undefined ref at any depth throws
'references undefined <name> (via <path>)', and a back-edge to a node on the
current path throws 'circular custom-prop reference: a → b → a'. Each node
must still be declared exactly once. Function name kept; the doc comment now
says 'recursively, with cycle detection' and means it.
Add negative cases: --h-control-xs → --space-5 → --missing must fail (the
2-hop case the old helper missed); --a → --b → --a must fail (cycle). The
direct --space-7 case and the valid closed chain still behave as before.
Verification: 1968/1968 desktop tests pass; typecheck clean. The real
--h-control-* chain (xs/sm/lg/2xl → --space-N → calc(var(--spacing) * N) →
--spacing: 4px; md/xl → calc(var(--spacing) * N) → --spacing) still passes.
@Astro-Han
Astro-Han merged commit 0954d9b into mainJul 5, 2026
@Astro-Han
Astro-Han deleted the refactor/layout-surface-sizing branch July 5, 2026 05:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han