Skip to content

fix(design-system): make the radius contract describe the app it governs - #1535

Merged
jackwener merged 1 commit into
mainfrom
claude/radius-first-principles
Jul 27, 2026
Merged

fix(design-system): make the radius contract describe the app it governs#1535
jackwener merged 1 commit into
mainfrom
claude/radius-first-principles

Conversation

@jackwener

Copy link
Copy Markdown
Member

Follow-up to #1520. Audited the radius system from first principles — the four tier values, every calc() shrink site, the COMPONENT_RADIUS table, and the SELECTOR_TIER table.

The suite was 18/18 green. It was green because the non-conforming parts were either absent from the tables or had the rule rewritten around them. Two contracts had gone further and were holding defects in place — the fix was the thing failing CI.

Contracts that were protecting defects

.providerLogo — a repaired invariant, silently reverted, then locked

PR-UI-13 pinned a single ~27-28% radius ratio across plate sizes, after an audit found the 44px and 32px plates reading as different shapes. The tier convergence rewrote both to --radius-surface:

plateradiusratiocomment claimed
44px8px18.2%/* 27.3% — anchor ratio */
32px8px25.0%/* 28.1% — matches md ratio */

Both comments arithmetically false, the drift restored, and SELECTOR_TIER pinned it — restoring the ratio failed CI.

Square icon plates are governed by ratio, not tier: a fixed px cannot read the same at 32px and 48px. Added --radius-plate: 27% — scale-invariant, restores the PR-UI-13 anchor (12px at 44px), correct at sizes not yet added. Applied to all six plates, which had drifted to four different ratios (18.2 / 25 / 16.7 / 22.2%). The compact and catalog-row overrides no longer restate a radius at all; with a ratio the size change alone suffices.

Compared visually at all four sizes before merging — the old column visibly drifts (48px reads squarer than 32px), the new one is one shape scaled.

.maka-search-modal-input-row — concentric nesting that was never adjacent

Pinned to calc(var(--radius-modal) - 8px) = 4px as "an input inside a 12px modal shell with an 8px inset", warning that a tier "would read as too round against the shell corners".

Measured live: that row is the middle of a 3-row grid — 57px below the shell's top edge, 12px in from its sides, adjacent to no shell corner. The 8px was the vertical margin to the header band, an axis with nothing to do with the corner. Its twin .maka-palette-input-wrap — same primitive, same position, same 12px shell — used the control tier all along, so the two searches differed by 50% in roundness.

The rule itself was the root cause. It said only "with padding between them" and never mentioned adjacency, so the search modal satisfied it literally — and its one worked example was this bug. Rewritten to state the precondition: the inner corner must sit in the outer corner, with an equal inset on both edges meeting there. It now cites the one genuinely concentric site in the codebase (segmented: 8px track, uniform 2px pad, buttons hugging its corners, 8−2=6 landing exactly on control).

Rules that had been rewritten to fit the code

  • TabsList / TabsTrigger were dropped from COMPONENT_RADIUS when refactor(ui): hover/selected state + tab component governance (post #496 color migrate) #499 moved them to primitives/tabs.tsx, on the stated grounds they were "governed by primitives-design-contract escape hatches". That file contains no radius assertion at all — leaving the vocabulary's own "tab trigger" and "tab list" as the only named roles governed nowhere. They had merely changed file; re-pointed.
  • Vocabulary said checkbox → pill 999px, while every checkbox renders rounded-[var(--radius-control)]. The code is right (a fully round checkbox is a radio); the vocabulary was wrong, and because Checkbox is absent from the table nothing ever caught it.
  • .enabledEmptyChip was pinned to control by reading its class name. It renders as a full-width two-line card with 16px padding — surface now, matching its twin .settingsWechatQrState.

Dead by construction

input-group's **:[textarea_button]:rounded-[calc(var(--radius-sm)-1px)] compiles to :is(… *):is(textarea button) — a <button> inside a <textarea>, which the HTML content model forbids. Not merely unused, unmatchable. Removed.

Deliberately not changed

  • Toggle / ToggleGroup look dead from the app's side (no JSX call sites) but are design-system primitives with a dedicated story, pinned by storybook-baseline-contract. Entries stay.
  • The tier values (6/8/12/999). The audit raised real questions — one control token spans a 16px Kbd to a 36px Input, a 2.25× swing in roundness, and 6-vs-8 is below the perceptual threshold as a global semantic distinction (it only does work in concentric nesting). But changing them moves the whole app's surface: a design decision, not a defect fix. Left for a separate call.

Gate

lint · format:check · build · typecheck · knip (both workspaces) · check-console · check-dead-css — all clean, plus 2879 main-process tests passing.

An audit of the radius system found the same failure repeated: the rules
were not derived from the design language, they were retrofitted to
whatever the CSS already said. The suite was 18/18 green — but green
because the non-conforming parts were either absent from the tables or
had the rule rewritten around them. Two contracts had gone further and
were actively holding defects in place.
Contracts that were protecting defects
--------------------------------------
.providerLogo — PR-UI-13 had pinned a single ~27-28% radius ratio across
plate sizes after an audit found the 44px and 32px plates reading as
different shapes. The tier convergence rewrote both to --radius-surface,
which is 18.2% at 44px and 25% at 32px: the exact drift that PR removed.
Both inline comments still claimed 27.3% / 28.1%, now arithmetically
false, and SELECTOR_TIER pinned the regression, so restoring the ratio
failed CI.
Square icon plates are governed by RATIO, not by tier — a fixed px
cannot read the same at 32px and 48px. Added --radius-plate: 27%, which
is scale-invariant, restores the PR-UI-13 anchor (12px at 44px), and is
correct at sizes not yet added. Applied to all six plates, which had
drifted to four different ratios (18.2 / 25 / 16.7 / 22.2%). The compact
override and the catalog-row override no longer restate a radius at all
— with a ratio the size change alone is enough.
.maka-search-modal-input-row — pinned by radius-nesting-contract to
calc(var(--radius-modal) - 8px) = 4px, described as concentric nesting
inside the 12px shell, with a warning that a tier "would read as too
round". Measured live: that row is the middle of a 3-row grid, 57px
below the shell's top edge, 12px in from its sides, adjacent to no shell
corner. The 8px was the vertical margin to the header band — an axis
with nothing to do with the corner. Its twin .maka-palette-input-wrap
(same primitive, same position, same shell) used the control tier all
along, so the two searches differed by 50% in roundness. Now control,
and pinned in SELECTOR_TIER so they stay together.
The rule itself was the root cause: it said only "with padding between
them" and never mentioned adjacency, so the search modal satisfied it
literally. Rewritten in maka-tokens.css and the nesting contract to
state the precondition — the inner corner must sit in the outer corner,
with an equal inset on both edges meeting there. Its worked example was
this very bug; it now cites the one genuinely concentric site instead.
Rules rewritten to fit the code
-------------------------------
TabsList / TabsTrigger were dropped from COMPONENT_RADIUS when #499
moved them to primitives/tabs.tsx, on the stated grounds that they were
"governed by primitives-design-contract escape hatches". That file
contains no radius assertion at all, leaving the vocabulary's own "tab
trigger" and "tab list" as the only named roles governed nowhere. The
components had merely changed file; re-pointed.
The vocabulary said `checkbox` belongs to pill/999px while every
checkbox renders rounded-[var(--radius-control)]. The code is right (a
fully round checkbox is a radio); the vocabulary was wrong, and because
Checkbox is absent from COMPONENT_RADIUS nothing ever caught it.
.enabledEmptyChip was pinned to control by reading its class name — it
renders as a full-width two-line card with 16px padding. Surface now,
matching its twin .settingsWechatQrState.
Dead by construction
--------------------
input-group's `**:[textarea_button]:rounded-[calc(var(--radius-sm)-1px)]`
compiles to `:is(... *):is(textarea button)` — a <button> inside a
<textarea>, which the HTML content model forbids. Not merely unused;
unmatchable. Removed.
Also refreshed the segmented.css comment, which justified using the tier
over the calc form on the grounds that the calc was unspellable — true
until #1514 fixed the value splitter. The tier is still right, for the
better reason that it survives a token revalue.
Not changed, deliberately
-------------------------
Toggle / ToggleGroup look dead from the app's side (no JSX call sites)
but are design-system primitives with a dedicated story, pinned by
storybook-baseline-contract. Their entries stay.
The tier VALUES themselves (6/8/12/999) are untouched. The audit raised
real questions there — one control token spans 16px Kbd to 36px Input,
a 2.25x swing in roundness, and 6-vs-8 is below the perceptual
threshold as a global semantic distinction — but changing them moves
the whole app's surface and is a design decision, not a defect fix.
Verified: lint, format:check, build, typecheck, knip (both workspaces),
check-console, check-dead-css, and 2879 main-process tests. Plate
geometry was compared visually at all four sizes before and after.
@jackwener
jackwener merged commit ef50859 into mainJul 27, 2026
3 checks passed
@jackwener
jackwener deleted the claude/radius-first-principles branch July 27, 2026 10:48
jackwener added a commit that referenced this pull request Jul 27, 2026
…name (#1537)
The CSS scanner matched the property name as a bare substring, so
`--os-track-border-radius: …` in base.css was scanned as though it were
a plain `border-radius` declaration. Both such properties currently hold
whitelisted tokens, so nothing failed — but the moment one held a raw px
the offender would have read
renderer CSS: border-radius: 4px;
naming a declaration that does not exist anywhere in the stylesheet, and
sending whoever has to fix it looking for the wrong thing.
Keeping these in scope is deliberate, not merely tolerated. `.os-theme-maka`
is OverlayScrollbars' theming API: the library's own CSS consumes those
properties and renders a real scrollbar corner. A radius that reaches the
screen is governed no matter which declaration spells it. So the fix is to
capture the whole property name rather than to anchor the match and drop
the coverage.
The lookbehind is load-bearing: without it a prefixed property also matches
at its inner `border-radius` and is reported twice. `\b` cannot do this job
— `-` is a non-word character, so a word boundary already exists between
`-` and `border`.
Found while reviewing #1520; deliberately left out of that PR and #1535 as
pre-existing and orthogonal.
Verified: lint, format:check, typecheck, check-dead-css, and 2891
main-process tests. The new assertion fails against the old regex.
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

@jackwener