Skip to content

refactor(renderer): one variant system, on cva - #45

Merged
DarrellVS merged 2 commits into
devfrom
37-cva
Sep 22, 2026
Merged

DarrellVS merged 2 commits into
devfrom
37-cva

Conversation

@DarrellVS

Copy link
Copy Markdown
Owner

Closes #37. Two commits: a stale e2e fix from #17, then the refactor.

The rule (now in CLAUDE.md)

Anything that comes in kinds is a cva recipe in Base/variants.ts. Anything with one look is a constant in Base/geometry.ts. cn() = clsx + the tailwind-merge that was installed but unused.

What moved

Recipe Now Sites
BUTTON* strings + BaseButton switch BaseButton on buttonVariants (tone, size, icon-only, reserve-icon) 34 in 13 files; strings deleted
Hand-rolled icon buttons BaseButton icon-only (one component, not two) 14 in 7 files + Reka trigger via recipe
Menu rows h-[34px] px-2.5 menuItemVariants on Reka items 16
Panels BasePanel 6
Chips over video BaseChip (numeric) 6
Hand-rolled empty states BaseEmptyState size="panel" + actions slot + icon tone 6
State ternaries that are variants status dot, wizard step, switch track/thumb, editor toolbar toggle, tone="success" 11

Decisions the issue asked for

  • Danger: a tone, outlined, never filled. Every destructive button already hand-wrote that outline; a filled red button beside the filled accent would be two primaries. strong is still the only filled accent (unit-tested).
  • Icon buttons: BaseButton icon-only, no BaseIconButton.
  • Panels: one padding in use, so one (plus none). The p-4 outlier was normalised.
  • Ternaries kept as they are: layout switches (compact, flush, block, fullscreen), animation states (open ? 'rotate-180', collapsing grid-rows, fades, editor preview slots), and one-off text emphasis (muted vs foreground on a single label). A variant nobody else asks for is a second place to look.

Visible changes (intended)

  • Menu Delete / Unpublish rows now share the other rows' geometry (they had drifted to gap-2 px-3 py-2, off the label column).
  • Menu rows show Reka's keyboard highlight (there was none); busy rows use :disabled, so the keyboard skips them too.
  • Icon buttons share one glyph grey (muted-500) and disabled opacity (50%). The editor controls were muted-600 before.
  • ClipStarButton and the clip menu trigger now have rounded-md corners, like every other icon button, instead of rounded-sm.

Found while doing it

A Base* component used without an import renders as <basebutton>, and nothing catches it: vue-tsc accepts unknown tags. The card's drag grip did exactly that. A pixel diff caught it (the card title was a few px wider). tests/unit/renderer/baseImports.spec.ts now fails on that (proved by removing the import), and also holds the recipe rules.

Gates

  • npm run check: 844 tests. npm run build: green.
  • screens.spec.ts: 8 passed. Both palettes, 581 text runs, nothing changes size on hover or focus.
  • 11 UI specs: 47 passed (the 2 failures were stale [Feature Request]: Smart game-based export folder routing #17 export paths, fixed in the first commit). After the rebase onto the revert: open the session in the editor from the post-game card (#34) #44 revert: toast, primitives, modals, 9 passed.
  • Before/after screenshots, 26 steps across every route: settings, stats, tag patterns, publisher and storage differ by 10 px (a sidebar game icon loading at a different moment). Library and editor differ only by the icon-button grey. Nothing moved.
  • Grep: 0 h-[34px] px-2.5 in components, 0 BUTTON_*, 0 chip or panel recipes left inline. The one py-8 text-center left is the end-of-list line in ClipsListFooter, which is not an empty state.

🤖 Generated with Claude Code

DarrellVS and others added 2 commits September 22, 2026 23:54
Two assertions still expected every render in the flat top level
`Exports/`. Since #17 a timeline cut from one game lands in
`<Game>/Exports/` and belongs to that game, which is what both tests
build, so both failed whenever the suite ran. The full suite had not run
since #17 merged, which is how they went unnoticed.

`features.spec.ts` and `measure.spec.ts`: 13 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Closes #37.

`class-variance-authority`, and `cn()` over `clsx` plus the
`tailwind-merge` that was already a dependency and used by nothing. The
rule, decided once and written into CLAUDE.md: anything that comes in
kinds is a recipe in `Base/variants.ts`, anything with one look is a
constant in `Base/geometry.ts`.

**One button.** `BaseButton` on `buttonVariants`: `tone` (default,
strong, quiet, danger, success), `size` (md, sm, dense), `icon-only`,
`reserve-icon`. All 34 `BUTTON*` string call sites across 13 files moved
to it and the strings are deleted. The hand-written `switch` it used to
carry is gone, and with it the disagreement: `geometry.ts` refused a
danger tone while `BaseButton` had a filled one. **Danger is now a tone,
outlined and never filled**, which is what every destructive button
already hand-wrote (`border-danger text-danger-ink hover:bg-danger/10`);
a filled red button beside the filled accent is two primaries. `strong`
stays the only filled accent. A caller's `class` goes through `cn`, so
`class="px-2"` means `px-2`.

**Icon buttons are `BaseButton icon-only`**, not a second component: 14
hand-rolled ones in 7 files, in four greys and three disabled opacities,
now share one quiet glyph recipe. The clip menu's Reka trigger calls the
recipe directly.

**Menu rows are `menuItemVariants`** on Reka's own items (16 rows). The
destructive rows had drifted to `gap-2 px-3 py-2` against everybody
else's `gap-2.5 h-[34px] px-2.5`, so Delete sat off the label column;
they share one geometry now and a tone is only a colour. Rows gained a
`data-[highlighted]` style, so arrowing through a menu finally shows
where you are, and `:disabled` replaces the `opacity-50
pointer-events-none` each busy row carried, which also takes it out of
the keyboard order.

**`BasePanel`** for six bordered panels (one had drifted to `p-4`),
**`BaseChip`** for six labels over video, and **`BaseEmptyState`** on
cva with `size="page" | "panel"`, a tone for the icon and an `actions`
slot, now used by the six empty states Storage Saver and Publisher had
hand-rolled.

**Ternaries that are really variants became variants**: the status dot
(4 sites), the wizard step bar (2), the switch's track and thumb, the
editor's two pressed toolbar toggles, and the Connections button that
turns green once registered (`tone="success"`). The rest stay, because
they are layout switches (`compact`, `flush`, `block`), animation states
(`open ? 'rotate-180'`, collapsing `grid-rows`, fades) or one-off
emphasis, and a variant nobody else asks for is a second place to look.

`useClipActions.ts` loses a `variant` field nothing read, and the
`ButtonVariant` and `PopoverAction` types go with it.

**A guard, because this broke once while it was being done.** A `Base*`
component used without an import renders as `<basebutton>` and nothing
fails: `vue-tsc` accepts unknown tags. The card's drag grip did exactly
that until a pixel diff of the card title (a few pixels wider) caught
it. `tests/unit/renderer/baseImports.spec.ts` now fails on any template
using a Base component it does not import (proved by removing the
import), and holds the recipe rules: one filled accent, danger never
filled, icon-only square at every size, a caller's padding wins, and
menu rows share one geometry.

Gates:
- `npm run check`: green; `npm run build`: green.
- `screens.spec.ts`: 8 passed, both palettes, 581 text runs measured,
  nothing changes size on hover or focus.
- 11 UI specs (features, primitives, pager, modals, focus, measure,
  libraryHeader, overflow, compress, editor, safety): 47 passed; the
  two failures were stale export paths from #17, fixed in the commit
  before this one.
- Before and after screenshots of 26 steps across every route:
  settings, stats, tag patterns, publisher and storage differ by 10
  pixels, a sidebar game icon loading at a different moment. The rest
  is the intended quiet glyph grey on icon buttons (editor controls,
  theme toggle). Nothing moved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@DarrellVS DarrellVS added this to the 3.5.0 milestone Sep 22, 2026
@DarrellVS DarrellVS added the refactor Changes shape without changing behaviour label Sep 22, 2026
@DarrellVS DarrellVS self-assigned this Sep 22, 2026
@DarrellVS
DarrellVS merged commit 85fb248 into dev Sep 22, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Changes shape without changing behaviour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant