Commit Graph
6 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5 37616682f0 feat(ui): three button sizes, because the app has three kinds of button
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 31s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 1m4s
CI & Build / Build & push image (push) Successful in 45s
Measured before deciding: across the ~100 bespoke button rules, vertical
padding does not spread — it clusters. ~27 at 0.4–0.45rem, ~28 at 0.25–0.3rem,
~23 at 0.1–0.15rem. Those are three different components that happen to share a
name prefix: a page action, a row action, and an affordance that lives inside a
card.

Collapsing them to the single size the shared sheet had would have visibly
broken every card layout, which is why the one-off migration stopped here for a
decision rather than proceeding on the assumption that a button is a button.

  default        8px 16px   page action — what the house style specifies
  .btn-compact   4px 12px   toolbar, table row, list item controls
  .btn-inline    2px 4px    dismiss ×, confirm tick, add-chip

.btn-small and .btn-sm already sat at the compact step, so they are kept as
aliases for it — no template churn, and the two spellings stop being a third
thing that might drift.

.btn-inline is deliberately below the spacing scale's first step on the
vertical axis: 4px of padding on an 11px label already exceeds the line box
these sit in. Stated in the file so it reads as a measured exception rather
than someone ignoring the scale.

DesignView renders all three as real specimens. A size scale described in prose
is one nobody can check; rendered from the actual classes, it cannot claim
something the app does not do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
2026-08-01 22:21:44 -04:00
bvandeusenandClaude Opus 5 1a959b1db0 refactor(ui): one button definition, aligned to the design system
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / integration (push) Successful in 39s
CI & Build / Python tests (push) Successful in 1m12s
CI & Build / Build & push image (push) Successful in 1m12s
`.btn-primary` was defined five times in five scoped stylesheets and all five
had drifted: three paddings, three font sizes, three disabled opacities — and
ProjectListView had no disabled style at all, so a disabled button there looked
enabled. Nothing detected any of it. A scoped duplicate is not a rule
violation, not a broken reference, and not a recorded snippet, so no existing
check could see it (#2273).

assets/components.css is now the single definition of the core four —
primary, secondary, ghost, danger — plus the small modifier, in design-system
tokens throughout. Operator's call to align to the system rather than to the
majority of current values, so buttons move to the 8px radius and 12px label
the system specifies, from the app's 4px/14.4px.

Class names are unchanged, so there are no template edits: the existing surface
is repurposed, not rebuilt.

STAGING PROPERTY that makes this safe to land ahead of the rest: a Vue
`<style scoped>` rule compiles to `.btn-primary[data-v-…]` (specificity 0,2,0)
and beats a plain global selector (0,1,0). So the shared sheet changes nothing
for a view still carrying its own copy, and every intermediate state of the
remaining migration is coherent rather than half-applied.

Two divergences corrected on the way, both worth naming:

- SnippetEditorView's `.btn-secondary` was a GHOST in disguise — outline
  styling under the secondary name, while the house style says secondary is
  filled bronze. It now looks like what it is called.
- SnippetDetailView and SnippetListView tinted a ghost button's label with the
  ACCENT on hover. The house style reserves the accent for identity and active
  state, never general chrome.

DesignView reported "no shared button exists" as an honest gap and declined to
draw a look-alike. That gap is closed, so it now renders the app's real
classes — the specimens cannot drift from the app without drifting the app.

Net -177 lines. Follows: the ~20 semantic one-offs (.btn-save, .btn-delete,
.btn-toggle …) and the compound overrides in ProjectView, one of which puts the
accent on a primary action button.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
2026-08-01 19:52:35 -04:00
bvandeusen 8eef9e7845 fix(design-systems): the empty state's create button did nothing, and one surface
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 28s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 57s
CI & Build / Build & push image (push) Successful in 47s
Two reports, one root cause each.

**The dead button.** "Create the first one" set `showCreate = true`, but the
create form lived inside `<div v-else class="ds-body">` — the sibling branch of
the empty state. The two are mutually exclusive, so on a fresh install the flag
flipped and nothing rendered. The first action a new install can take was the
one that didn't work, which is a poor way to honour "an install with zero design
systems is the ordinary state".

The first system now gets its own form outside the list layout, and it drops the
parent picker entirely: there is nothing to inherit from yet, so it says so
instead of offering an empty select.

**Two surfaces, the wrong one first.** /design and /design-systems are halves of
one thing — the record that decides the styling, and what the browser renders
from it — and I had added them as two separate nav entries with the read-only
diagnostic listed first. Backwards: the record is what you work with; the live
view is the check on it.

Now one nav entry pointing at the record, with a shared tab bar joining the two.
The explorer is renamed "Live tokens", which is what it actually shows.

The tab bar is a component rather than the same markup in both views. Two copies
diverge the moment a third tab appears — and a design surface that ships
duplicated markup would be arguing against itself.
2026-07-31 08:29:37 -04:00
bvandeusen 78489308b8 feat(design-systems): link /design to its editable half
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 42s
CI & Build / Build & push image (push) Successful in 40s
The two pages are halves of one surface — what the browser renders and the
record that should decide it — and only one direction was linked. Missed in
0937b17 because the patch that added it silently didn't apply; the commit went
out without it.
2026-07-30 17:17:59 -04:00
bvandeusenandClaude Opus 5 4ca3ab02c4 feat(design-explorer): the drift panel — rulebook says X, tokens say Y
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / TypeScript typecheck (push) Successful in 11s
CI & Build / integration (push) Successful in 15s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 40s
Milestone #251 step 5 (#2262), plus the Settings control that makes it reachable.

The comparison is deliberately thin — set arithmetic over live token values,
which is the one thing the browser knows and the server doesn't. The hard half
(prose to claims) already lives in Python where pytest can assert on it.

Three claim kinds, and the third inverts the test: a `token` claim asks whether a
custom property of that name exists; a `color` claim asks whether any token
resolves to that value; a `prohibited_color` claim FAILS when present.

normalizeColour is the client-side twin of normalize_hex and has one job the
server cannot do: getComputedStyle reports colours as rgb()/rgba() regardless of
how they were authored. So one colour has three spellings in play — #FFFFFF in
the rulebook, #fff in the stylesheet, rgb(255,255,255) from the browser — and a
comparison that misses any of them under-reports silently rather than erroring.

THE PANEL STATES ITS OWN BLIND SPOT, which matters more than it sounds. This
compares the rulebook against TOKENS. A literal hardcoded in a component, where
a token should have been referenced, is invisible to it — the drift isn't in the
tokens at all (#2275: 67 hardcoded whites against a rule forbidding pure white).
Reading those would mean bundling every SFC's source into the app; the check
belongs in CI and is tracked at #2277. A drift report that silently omitted a
whole category would invite the reader to conclude the category is clean, so the
panel says so in the panel rather than in a comment nobody reads.

Findings are ranked violated → missing → ok, and `ok` rows are hidden behind a
toggle. Same principle the auto-inject menu is built on: a short list that gets
read beats a complete one that doesn't.

Settings gains a rulebook picker. "None" is a first-class choice, not an unset
error — most installs have no rulebook describing their design system, and
saving empty DELETES the setting rather than storing a zero. The panel's empty
state points at Settings and Settings points back at the panel, so neither is a
dead end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
2026-07-30 15:32:29 -04:00
bvandeusenandClaude Opus 5 3c0192d749 feat(design-explorer): the gallery, including what isn't there
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Python tests (push) Successful in 41s
CI & Build / Build & push image (push) Successful in 40s
Milestone #251 step 3 (#2260). New /design view, reachable from a Palette icon
beside Trash and Settings — a meta-surface like /rules, so an icon rather than a
sixth primary nav link, but not hidden either, since somewhere the design system
is visible is the entire point.

Renders three things, and refuses to render a fourth:

- REAL components, imported not recreated: StatusBadge, PriorityBadge, TagPill.
- REAL tokens, read at runtime via readTokens() so the page shows the live
  cascade rather than what the stylesheet claims. Grouped, swatched where the
  value is a colour, flagged where the token is mode-aware.
- Rule 65's four button variants and rule 60's type scale, listed as SPEC and
  marked missing.

That last part is the point of the step rather than a shortfall of it. Step 3's
premise was "render the real components, not copies — a gallery of look-alikes
drifts from the app within a month and then lies." Buttons have no shared
implementation to import: .btn-primary is defined four separate times in four
<style scoped> blocks, and 30 of 54 SFCs carry their own button CSS (#2273).
Drawing a button here would have made this page the fifth copy — committing the
exact drift the surface exists to catch. Same for the type scale: the three
families load (rule 59) but rule 60's sizes and weights are not tokens, so there
is nothing to read and a rendered specimen would be invented.

So the gallery reports them as gaps. A design system nobody can point at is a
design system that isn't there, and saying so is more useful than a page that
looks complete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
2026-07-30 14:41:06 -04:00