# Design audit — all — 2026-08-10

Capture: `figma-spec.json` @ 2026-08-02T10:01:51.636Z · file "CBAR - Design System (Copy)" · bridge live: **yes**
Live file: "CBAR - Design System (Copy -2)" · plugin connected · 28 pages
Previous: first run

## On the capture, and why it was not regenerated

The capture names a file called *"CBAR - Design System (Copy)"*; the plugin is
connected to *"CBAR - Design System (Copy -2)"*. That looks like grounds for a
`pnpm figma:spec` before anything else, and the skill says so.

It was not re-run, for a reason that is itself a finding about the tooling:
`gen-figma-spec.mjs` asks for `sets --page "*"`, the whole-document walk, and on
this file that walk **does not return** — two attempts at `pages` (the same
`loadAllPagesAsync` cost) each burned the full 120 s timeout and the second one
left the plugin dead, requiring a manual reopen. Re-running the generator would
have cost the session for a capture I could not trust either way.

Instead the capture was **validated directly**, which is stronger than
regenerating it: for the four sets this audit actually goes into, the live
`componentPropertyDefinitions` were compared against the captured `props`.

| Set | Node id | Live children | Capture variants | Axes identical |
| --- | --- | --- | --- | --- |
| Button | `2446:6959` | 560 | 560 | yes |
| IconButton | `2399:522` | 280 | 280 | yes |
| Input | `2249:1168` | 40 | 40 | yes |
| ProgressCircle | `2264:23` | 35 | 35 | yes |

So the file was renamed, not moved on. Every structural claim below is read from
the capture; every colour and measurement is read **live**.

## Summary

**13 findings** — 5 kit-defect, 2 design-defect, 5 deliberate, **0 token-drift**,
plus one whole-category exclusion. 20 of the registry's 40 entries are linked to
a Figma set and were compared; 20 entries have no set; 7 sets have no kit entry
and 1 more is invisible to the comparison entirely (see REG-02).

Axis rows across the 20 linked entries: **20 match, 7 diff, 13 state (excluded),
9 no-prop, 16 kit-only**.

**The token layer is clean.** All **72** `--ui-color-*` ramp steps in
`tokens.css` match `Color/Foundation` in the live variable table exactly — zero
drift, so nothing here routes to `/figma-sync`. The only four kit-side entries
without a Figma counterpart are the two documented interpolations
(`neutral-950`, `brand-950`; CBAR's ramps stop at 900) and the two documented
role aliases (`yellow-surface` → `yellow/500`, `yellow-ink` → `yellow/800`).

The measurement layer is clean too. Button's height/padding/gap/radius ladder,
its 28px line height, Input's four field heights and paddings, and Alert's radius
and padding all match the live file exactly (evidence under each finding, and in
*Verified, no divergence* at the end).

**The yellow-collection hazard is real and was hit during this audit.** Resolving
the semantic aliases by variable *name* silently produces Tailwind's yellow ramp,
because the stale standalone `Yellow` collection defines the same ten names. Only
a collection-aware (or id-aware) resolution gives the right answer. Confirmed
live: every `*/warning` semantic aliases to `Color/Foundation:yellow/*`, and the
standalone `Yellow` collection is referenced by nothing. `tokens.css` holds the
correct ramp.

---

## Findings

### BTN-01 · Button · design-defect · confirmed

**Figma** `Button` `2446:6959` — variant `2446:7312` is named
`colorPalette=primary` and its fill is bound to the variable
**`surface/colored/secondary`** (`#4BC7B5`, turquoise). Variant `2446:6972`,
named `colorPalette=secondary`, is bound to **`surface/colored/primary`**
(`#004976`, navy).

**Kit** `src/styles/theme.css:262` (`.palette-primary`) → `--ui-color-brand-*`;
`src/styles/tokens.css:52` `--ui-color-brand-500: oklch(0.392 0.098 244.9)`
`/* #004976 */`.

**Evidence** — this run adds evidence the previous write-up did not have. The
divergence is not a hex that drifted; it is the **variable binding itself** that
is crossed. The alias chain from the live table is unambiguous:

```
surface/colored/primary   → brand/primary/500   → #004976  (navy)
surface/colored/secondary → brand/secondary/500 → #4BC7B5  (turquoise)
```

and `IconButton` — the same design system, the same page family — binds them the
right way round:

| Set · variant | Bound variable | Hex |
| --- | --- | --- |
| Button `colorPalette=primary` `2446:7312` | `surface/colored/secondary` | `#4BC7B5` |
| Button `colorPalette=secondary` `2446:6972` | `surface/colored/primary` | `#004976` |
| IconButton `colorPalette=primary` `2406:2254` | `surface/colored/primary` | `#004976` |
| IconButton `colorPalette=secondary` `2399:521` | `surface/colored/secondary` | `#4BC7B5` |
| Alert `status=info-secondary` `2446:317` | `surface/colored/primary` | `#004976` |
| Alert `status=info` `2446:337` | `surface/colored/secondary` | `#4BC7B5` |

Button is the single outlier among the sets checked. Alert, IconButton and the
variable table all agree with the kit.

**Consequence** A designer reading the Button set sees `primary` painted
turquoise; the kit renders it navy. 160 of the set's 560 variants are affected
(the two palettes × 5 treatments × 4 states × 4 sizes).

**Fix** none — see verdict. Already written up in the showcase Findings tab
(`/parity?tab=findings`). Update that page with the variable-binding evidence
above and the IconButton/Alert counter-evidence; change **no** component. Fixing
this in the kit would make the whole kit disagree with the table `tokens.css` is
generated from, and `/figma-sync` would undo it.

### BTN-02 · Button / IconButton · design-defect · confirmed

**Figma** Button `2446:8964` names the tertiary ramp `colorPalette=third` and
binds `brand/tertiary/500` (`#0082BA`). IconButton `2412:2919` names **the same
ramp** `colorPalette=turquoise` and binds the same `brand/tertiary/500`.

**Kit** `src/styles/tokens.css` — `--ui-color-tertiary-*`, with
`--ui-color-third-*` kept as a deprecated alias for one major.

**Evidence** IconButton carries **both** `secondary` (`#4BC7B5`, the real
turquoise) and `turquoise` (`#0082BA`, the tertiary blue) as sibling values on
one axis. So inside a single design file the tertiary ramp has two names, and one
of those names denotes a different ramp on the neighbouring component.

**Consequence** No kit consequence — the kit's `valueMap` already absorbs
`tertiary` → `third` for Button and ProgressCircle. It is a hazard for the next
person mapping IconButton, who will reasonably read `turquoise` as `secondary`.

**Fix** none — see verdict. Add to the Findings tab alongside BTN-01; both are
naming/wiring defects in the same corner of the file.

### AVT-01 · Avatar · kit-defect · confirmed

**Figma** `avatar` `2446:757` — `colorPalette` draws `primary | neutral | third |
secondary`.

**Kit** `showcase/src/registry/avatar.tsx:23` —
`valueMap: { shape: { circle: 'shape' }, colorPalette: { tertiary: 'third' } }`.

**Evidence** `.palette-black` in `src/styles/theme.css:193` binds every `--ctl-*`
role to `--ui-color-neutral-*`. CBAR's `neutral` **is** the kit's `black`
palette, and the `valueMap` has no pair for it. The parity page therefore reports
`neutral` as figma-only and `black` as kit-only — a gap that does not exist.

**Consequence** Avatar reads as a `diff` row on `/parity` when it is a match on
that value; a design reviewer chases a divergence that is not there, and a real
one on the same row would be camouflaged.

**Fix** add `black: 'neutral'` to `avatar.figma.valueMap.colorPalette`.

### RAD-01 · RadioGroup · kit-defect · confirmed

**Figma** `RadioMark` `2451:5841` — **576 variants**, axes
`.isChecked? · size · variant (solid | subtle | outline | inverted) ·
.isDisabled? · .focusVisible? · colorPalette (gray | green | red | primary |
secondary | turquoise)`. Read live at depth 1 this run; the capture agrees.

**Kit** `showcase/src/registry/form-controls.tsx:174` — `axes: { colorPalette,
size, disabled }`. `src/components/radio-group/radio-group.tsx` has no `variant`
in its cva.

**Evidence** CBAR draws four radio treatments; the kit draws one. This is the
largest single component set in the file and the kit has no prop for its primary
axis. It has never appeared as a gap because of REG-02 below.

**Consequence** A consumer cannot build CBAR's `outline` or `inverted` radio at
all. Every other palette-bearing control in the kit takes `variant`; RadioGroupItem
is the exception, silently.

**Fix** add a `variant` axis to `RadioGroupItem`, composing `controlVariants` the
way Checkbox and Switch do. **Consumer-visible** — see the plan for the full
checklist this drags in.

### REG-01 · showcase registry · kit-defect · confirmed

**Figma** Seven sets have no kit entry: `IconButton` `2399:522` (280),
`tabs` `2466:14` (38), `Select.Content` `2452:1745` (4),
`Pagination left` `2516:1230` (5), `Pagination right` `2515:71` (5),
`search` `159:2944` (2), `search` `2490:502` (2).

**Kit** `showcase/src/registry/types.ts` — `figma?: { set: string; … }`, one set
per entry.

**Evidence** Four of those seven **do** have a kit counterpart —
`Select.Content` is `SelectContent`, `Pagination left`/`right` are
`PaginationPrevious`/`PaginationNext`, and `tabs` is `TabsTrigger`. They are
reported as uncovered only because an entry can name exactly one set, and a
component built from several Figma sets has nowhere to put the rest. The real
uncovered count is three (`IconButton` and the two `search` sets), not seven.

**Consequence** The headline parity number overstates the gap by more than
double, which makes it useless as a trend line — the metric the next audit is
supposed to diff against.

**Fix** widen the link to cover secondary sets (an `alsoCovers?: string[]`
alongside `set`, listed but not axis-diffed) and mark the genuinely uncovered
ones. Registry and parity page only; no component changes.

### REG-02 · showcase registry · kit-defect · confirmed

**Figma** Two distinct sets are both named `RadioMark`: `2428:3852` (44
variants, a `state` axis) and `2451:5841` (576 variants, the real one — RAD-01).

**Kit** `showcase/src/figma/spec.ts:66` — `findSet` is
`SETS.find(s => norm(s.set) === norm(name))`, a **first match by name**.

**Evidence** `radio-group` links `set: 'RadioMark'`, which resolves to
`2428:3852`. The 576-variant set is never compared by anything, which is exactly
why RAD-01 went unnoticed. `figma-spec.json` captured it correctly; the lookup
throws it away. The registry's own comment acknowledges the duplicate and
describes resolving to the first as intended — but the first is the *smaller*,
state-only set.

**Consequence** One silent blind spot today, and the mechanism is general: any
future duplicate set name hides a set with no warning anywhere.

**Fix** let an entry disambiguate by node id (`figma: { set, id? }`) and have
`findSet` fail loudly — or at minimum warn on `/parity` — when a name matches more
than one set. Point `radio-group` at `2451:5841`.

### ICO-01 · Button (icon sizes) · kit-defect · **unconfirmed**

**Figma** `IconButton` `2406:2254` — `border-radius: var(--radius-sm, 6px)`,
40×40, `padding: 12px`, `aspect-ratio: 1/1`.

**Kit** `src/components/button/button.tsx:21` — the Button base string carries
`rounded-xs`, and the four `icon-*` rungs inherit it. `--ui-radius-xs` is
`0.25rem` (4px); `--ui-radius-sm` is `0.375rem` (6px).

**Evidence** CBAR draws a text button at 4px and an icon button at 6px — Button
`2446:7312` is `border-radius: 4px`, IconButton `2406:2254` is 6px. The kit
models an icon button as `Button size="icon-*"`, so it gets Button's 4px.

**Consequence** Every icon-only button in a consuming app is 2px squarer than
CBAR draws it.

**Fix** give the four `icon-*` rungs `rounded-sm`. Marked **unconfirmed**: this
is resolved statically from the cva and the token file, not from
`getComputedStyle` — the showcase Figma panel was not opened this run (see
*Method and limits*). Confirm on `/button` before landing, since it is the one
finding here that changes rendered output.

### SW-01 · Switch · deliberate (unrecorded) · confirmed

**Figma** `Switch` `2454:482` — `Size` draws `xs | md | lg`.
**Kit** `showcase/src/registry/form-controls.tsx:125` — `size: SIZES` = `xs | sm
| md | lg`.

**Evidence** Same shape as Button's extra `sm` rung (BTN-03), and it follows the
shared `SIZES` constant every form control uses. Unlike Button's, **this one is
written down nowhere** — not in the cva comment, not in the registry entry, not
in `README.md` §9.

**Fix** none — but record it, or the next audit re-finds it as a `no-prop` gap.
A one-line comment on `SIZES` in `form-controls.tsx` covering Switch and
RadioGroup would close it.

### ALR-01 · Alert · deliberate (unrecorded) · confirmed

**Figma** `Alert` `2446:316` — `status` defaults to `info`, which binds
`surface/colored/secondary` (`#4BC7B5`, turquoise).
**Kit** `showcase/src/registry/alert.tsx:53` — `colorPalette: 'primary'` (navy).

**Evidence** Alert's bindings were checked live and are **correct** (`info` →
turquoise, `info-secondary` → navy), so unlike Button's default this divergence is
not a symptom of BTN-01. It is a real difference in what `<Alert>` renders with no
props. It is also consistent with the kit: every palette-bearing component
defaults to `primary`.

**Fix** none — kit-wide consistency is the better default. Worth one line in the
entry saying so, since the reasoning is currently only inferable.

### BTN-03 · Button · deliberate · confirmed

Kit-only `sm` rung; CBAR's Button jumps `xs → md`. Recorded in
`showcase/src/registry/button.tsx:172` (`spec.caption`) and in the size cva
comment at `src/components/button/button.tsx:74`. Verified live: the set draws
`xl | lg | md | xs` only.

### TAB-01 · Tabs · deliberate · confirmed

Kit-only `variant="default"`; CBAR's `tabsList` draws `line | subtle | outline |
plain`. Recorded in the cva JSDoc at `src/components/tabs/tabs.tsx:37` —
*"Segmented control on a filled track. Pre-CBAR; not one of its four."*

### ALR-02 · Alert · deliberate · confirmed

Kit-only `colorPalette="tertiary"`; CBAR's Alert has no tertiary status.
Recorded in the entry comment at `showcase/src/registry/alert.tsx:38`.

---

## Excluded — `not-comparable`

One line each, so the next audit does not re-find them.

- **13 `state`-axis rows** across Button, Checkbox, Switch, RadioMark, Input,
  Textarea, Select, Pagination, Toast, Accordion. The kit draws these in CSS;
  `STATE_AXES` in `spec.ts:89` already excludes them.
- **`width`** — never compared. A Figma variant frames a fixed label; the kit's
  control hugs. Height is what the design specifies.
- **Icon-position slots** — Button `iconLeft?`/`iconRight?`, Badge
  `.iconStart?`/`.iconEnd?`, tabs `.iconStart?`/`.iconEnd?`, Toast
  `iconLeft?`/`iconRight?`. All BOOLEAN, several defaulting **on**. The kit takes
  icons as children, so position is child order and there is no prop to diff.
- **`avatar-type` (`initials | avatar`)** — expressed as `AvatarFallback` vs
  `AvatarImage`, not as a prop.
- **Card's three axes** (`Asset Type`, `Variant`, `Direction`) — the kit's Card is
  a composition of parts, not a variant set. Nine `no-prop` rows come from Card,
  slider, FileUpload and Tooltip together for this reason.
- **`slider` `variant|position|size`**, **`FileUpload` `type`**, **`Tooltip`
  `placement`** — modelled as props, children or composition. Tooltip's
  `placement` is the one arguably mappable to `TooltipContent`'s `side`; Figma
  draws only `bottom`, so there is nothing to diff yet.
- **All 20 Figma `defaultValue` divergences as a category.** A component set's
  `defaultValue` is its first-drawn variant, not a specified default: 8 of the 20
  are Figma's smallest rung (`xs`) and 2 its largest (`xl`), against a kit that
  defaults to `md` everywhere. Two were examined individually anyway because they
  are colour rather than size — Button's (explained entirely by BTN-01: Figma's
  `secondary` default *paints navy*, which is exactly what the kit's `primary`
  default renders, so the two agree on screen) and Alert's (ALR-01).

## Verified, no divergence

Recorded so the next run can diff against it rather than re-measure.

- **Button geometry**, live vs `controlHeights` + the size cva:
  `xs` 32px/10px/gap 4 · `md` 40/16/gap 8 · `lg` 48/20/gap 10 · `xl` 56/16/gap 10,
  radius 4px throughout. Every value matches, **including the non-monotonic
  padding** (`lg` 20px is roomier than `xl` 16px), which the kit reproduces rather
  than smooths.
- **Button type**: Figma `2446:7312` is DM Sans Medium 500, 14px, line-height
  **28px**. The kit's `md` rung carries `text-sm leading-7` — 14px/28px. Match.
- **Input field ladder**, measured on the inner `Input` frame rather than the
  labelled variant frame: `xs` 32 · `sm` 36 · `md` 44 · `lg` 56, padding-x
  8 / 10 / 12 / 16, radius 4px, border `1px solid #E5E5E5`. The kit's
  `fieldHeights` (`h-8/h-9/h-11/h-14`) and `px-2/px-2.5/px-3/px-4` match exactly,
  and `--input` is `--ui-color-neutral-200` = `#E5E5E5` =
  `border/subtle/default`. **The "CBAR draws fields taller than buttons" claim is
  confirmed against the live file** — 44 against 40 at `md`, 56 against 48 at `lg`.
- **Alert geometry**: Figma radius 8px, padding 12px, gap 10px; kit `rounded-md`
  (`--ui-radius-md` = 8px), `p-3`, `gap-x-2.5`. Match.
- **All 72 colour ramp steps** — see Summary.
- **`--ctl-solid-fg` white on `secondary`/`green`** — the recorded fidelity
  decision (`test/contrast.test.ts` measures both pairs with a floor of 1,
  `README.md` §9c). Re-confirmed as still deliberate; not re-litigated.
- **Yellow ramp non-monotonic at 500** (`#F3C67F` lighter than 400 `#F2C952`) —
  confirmed live as CBAR's own value in `Color/Foundation`, and confirmed that
  every `*/warning` semantic points at that collection and not the stale one.

## Method and limits

- **Read live** (bridge, `figma_*` over `/rpc`): the variable table (250
  variables, 5 collections, aliases resolved id-aware); node dumps of `Button`
  `2446:6959`, `IconButton` `2399:522`, `Input` `2249:1168`, `ProgressCircle`
  `2264:23`, `Alert` `2446:316`, `RadioMark` `2451:5841`; `figma_css` on 13
  variants; `figma_text` on 1.
- **Read from the 2026-08-02 capture**: every axis and slot listing for all 28
  sets, validated against the live file for the four sets in the table above.
- **Not audited.** The 20 registry entries with no Figma set were not compared —
  there is nothing to compare them against, and that is a finding only insofar as
  REG-01 covers it. `IconButton` and the two `search` sets are genuinely uncovered
  by the kit and were read only far enough to serve as evidence for BTN-01/BTN-02
  and ICO-01. Geometry was **not** checked for Badge, Avatar, Checkbox, Switch,
  Select, Textarea, Tabs, Toast, Tooltip, Card, Accordion, Breadcrumb, Pagination,
  slider or FileUpload — L3 went deep on Button, Input, IconButton and Alert only.
- **The showcase's live Figma panel was not opened this run.** Every geometry
  claim above is resolved statically (cva → `cva-presets` → `theme.css` →
  `tokens.css`) and compared against Figma's own `getCSSAsync` output, but not
  against `getComputedStyle` on a rendered element. That is why ICO-01 — the only
  finding that changes rendering — is marked `unconfirmed`. Everything else in
  this report is structural, a colour traced to the variable table, or a
  measurement that matched.
- **Bridge stability.** `figma_pages` and by extension `sets --page "*"` do not
  return on this file and killed the plugin once. Prefer `figma_node` from a known
  set id.

## Changed since — first run

No previous report. This one establishes the baseline: finding ids `BTN-01`,
`BTN-02`, `AVT-01`, `RAD-01`, `REG-01`, `REG-02`, `ICO-01`, `SW-01`, `ALR-01`,
`BTN-03`, `TAB-01`, `ALR-02` are stable and should keep their meaning in the next
run.
