Skip to content

refactor(ui-core): shadcn is the authoritative primitive contract, and VisionSet extends it - #812

Merged
YaelAnaya merged 57 commits into
mainfrom
refactor/shadcn-foundation-realignment
Aug 27, 2026
Merged

refactor(ui-core): shadcn is the authoritative primitive contract, and VisionSet extends it#812
YaelAnaya merged 57 commits into
mainfrom
refactor/shadcn-foundation-realignment

Conversation

@YaelAnaya

Copy link
Copy Markdown
Collaborator

Realigns @visionset/ui-core so that shadcn/ui is the authoritative implementation of every primitive. VisionSet extends it only by adding lines; it no longer forks it.

Plan: .agents/plans/2026-08-26-shadcn-foundation-realignment.md · Report: ...-realignment-report.md · Ledger: ...-realignment-ledger.md

What changes

The 21 primitives now equal the shadcn CLI 4.19.0 snapshot (preset b2iHradix-nova), with added lines only. The gate tests/scripts/shadcn_canonical.test.mjs checks every file against its pristine snapshot in frontend/ui-core/shadcn/. Measured at head: +0 −0 everywhere except two files.

  • badge — +8 lines: four cva entries success warning info quiet on emerald / amber / sky / muted. Geometry, padding, radius, focus and data slots are shadcn's base string, untouched. Zero coloured status borders.
  • sonner — framework adapter: next-themes replaced by a hook reading .dark on <html>. The next-themes dependency is gone. Marked SHADCN FRAMEWORK ADAPTER; the gate names the exact two removed lines.

Surviving proprietary deviations: 0 (SHADCN DEVIATION count = 0).

API and vocabulary

  • Buttonprimary (42 call sites → default), success and size="md" removed. Final: default outline secondary ghost destructive link.
  • Badge — legacy accent(7) → default, unnamed/neutralsecondary, quiet(3) kept, success/warning re-paletted.
  • Alert / Dialog / Select / Field / Progress — proprietary APIs removed (title=, the description registry, SelectItem meta=, FieldHintFieldDescription, Progress's variant). Each case is recomposed at the call site.
  • dropdown-menu — the historic rapid-reopen deviation is deleted; the e2e passes on the canonical file. menuSurface (lib/menu.ts) is applied by composition at all 8 call sites, now gated.
  • New from the CLIsheet, combobox (official, Base UI-backed), input-group. The hand-rolled patterns/Combobox.tsx (0 product consumers) is deleted.
  • Removed — the 11 legacy PascalCase files (Badge.tsx, Button.tsx, Card.tsx, Combobox.tsx, Dialog.tsx, Feedback.tsx, Input.tsx, Menu.tsx, Select.tsx, Table.tsx, Tabs.tsx).

Tokens and status colour

success / warning (+ -foreground) are retired from styles.css, tokens.ts, tests and the styleguide, and added to RETIRED_DECLARATIONS. No info token was created. Remaining extensions: stage, brand, origin-*. All status colour outside the Badge now lives in one file: patterns/statusTone.ts.

Contrast (WCAG, ink over the 10 % tint composited on the page background, gamma-space): emerald 4.88 light / 8.96 dark · amber 4.67 / 9.89 · sky 5.29 / 8.02 — all ≥ 4.5:1. Documented latent case: warning measures ≈4.3:1 on a full bg-muted panel; no status Badge renders there today.

Docs and gates

DESIGN.md rewritten (21 sections) — shadcn named as the authoritative primitive contract, plus the update workflow, the status palette, the stroke policy and the deviation policy. New gate file tests/scripts/shadcn_extensions.test.mjs machine-checks the extension contract. Styleguide rewritten. 9 visual baselines regenerated and reviewed image by image, light and dark, by the implementer and an independent reviewer.

Product integrity

Routes changed:              No
Product IA changed:          No
Backend changed:             No
Wire/API changed:            No
Domain behavior changed:     No
Annotation geometry changed: No

data-testid set: identical to the base across every changed .tsx. The only aria-*/role removals belong to the deleted PascalCase primitives; net additions come from the canonical Field / InputGroup.

Validation

Everything ran inside mcr.microsoft.com/playwright:v1.62.1-noble.

Command Result
pnpm install --frozen-lockfile PASS
pnpm version:check && pnpm generate:client:check PASS
pnpm -r build / pnpm -r lint PASS
pnpm -r test PASS — 2472 (annotator 1071, ui-core 1391, app 11)
pnpm test:scripts PASS — 164 gates
e2e Chromium 275/275
visual 10/10 (×3)

Not run here: the cycle suite — its webServer spawns uv and the frontend image has no uv. The CI job browser cycle (chromium) covers it. No host-native validation was performed.

Merge state

Rebased on nothing — the branch is based on a4c3630 and origin/main has since advanced to 92d19d4 (#811). Checked with git merge-tree: no textual conflicts, and a sweep of the merged tree finds no retired vocabulary (variant="primary"|"success" on Button, size="md", FieldHint, PascalCase primitive imports, --success/--warning, bg-success). #811 touched RecipeEditor.tsx and PreprocessingTab.tsx, which this branch also touched, but in non-overlapping regions — CI runs on the merge commit and is the authority on the combined state.

Rulings made without asking

  1. Built on the unpushed local refactor/shadcn-canonical-primitives (merged onto main as the first commit) instead of redoing its 30 commits.
  2. The palette gate's allow-list includes patterns/statusTone.test.ts — a test asserting exact classes has to hold them.
  3. Progress's call-site bg-success indicator overrides were removed rather than re-pointed at emerald (Progress carries no polarity). A completed batch's bar is no longer green; the state stays in the Badge and label.
  4. scripts/shadcn_add.sh now relativises every primitive after a CLI run, because the CLI rewrites dependency files too.
  5. The 16 px rhythm under a line TabsList lives at the pattern layer (ProjectNav); the primitive stays canonical.
  6. Combobox: the official Base UI-backed file was installed and the hand-rolled pattern deleted (0 product consumers).

…al files; the combobox is a pattern; scoped radix packages retire
YaelAnaya and others added 27 commits August 26, 2026 19:08
complete-promote-* and start-job-* are outline buttons now, not
"secondary" — and the page's one filled-button check is scoped to
Button (data-slot="button") so it stops also matching the
batch-state Badge, which legitimately carries data-variant="default"
for the in_annotation state.
…s the outline-with-radius shape

RecipeList's target chip was an outline Badge hand-rounded with
className="rounded-md" — exactly the shape the quiet variant exists
to replace. shadcn_extensions.test.mjs now flags an outline Badge
carrying a rounded-* className utility, in either attribute order,
self-tested both directions.
Canonical SelectTrigger is w-fit whitespace-nowrap, so the below-lg
class picker sized to its selected option's text instead of its
container and pushed the schema wider than a 320px viewport. The
picker's own wrapper gets min-w-0 and the trigger gets w-full; either
alone still leaves a 284px overflow. Updates the narrow/light schema
visual baseline to the now-full-width trigger.
…ile names

menu.ts cited a "Menu row" heading that no longer exists; styles.css
cited "Borders and Focus" where DESIGN.md's own H3 reads "Borders and
focus". product-principles.md still named the retired PascalCase
primitive files Tabs.tsx and Badge.tsx, which are tabs.tsx and
badge.tsx now.
…lows a theme flip

shadcn_extensions.test.mjs sweeps every DropdownMenuContent call site
outside the shadcn snapshot for lib/menu.ts's menuSurface class, self-
tested against a tag with and without it; today's 8 call sites all
pass. sonner.test.tsx adds a third case that flips <html> from light
to dark after mount and waits for the toaster's data-sonner-theme to
follow, covering the MutationObserver path the other two cases don't
reach.
Status Palette now notes that Badge contrast is measured against
page/card surfaces: on a full bg-muted panel the warning ink measures
~4.3:1, so a status Badge needs a re-check before it lands on a muted
panel. Rides with a stray double blank line in DatasetScreen.tsx.
The cycle suite is the one suite the realignment never ran — the frontend
image has no `uv`, so its `webServer` could not start and CI is the only
place it runs. Three of its assertions still read v1's vocabulary, and the
walk stopped at the first: the composed step on the run outcome card is
`default` (the call site fills it), `Open batch` beside it is `outline`,
and the same control on a batches row takes the prop's own default, which
is `outline` too. `composedTransitions.test.tsx` already asserted these
three values; only the browser walk was left behind.

The prop's docstring said "secondary" for the same reason.
… JSX

`TAG_ATTRIBUTE_RULES` walks JSX opening tags, so it sees a component
spelling `variant="primary"` and misses a spec asserting the same name
through the DOM — which is how `cycle.spec.ts` carried "primary" through
the whole realignment with CI the only thing that caught it.

Three shapes per attribute, because a spec has three ways to say it: the
two arguments of `toHaveAttribute`, a `[data-variant=…]` selector, and
`getAttribute`/`dataset` compared with `toBe`/`toEqual`. The first
matches across newlines on purpose: prettier puts those arguments on
their own lines once the call runs long, and that wrapped form is the one
that got through.

Only values no primitive carries any more are listed. `secondary` and
`success` are still real variants, so an assertion naming one asks about
its own call site, which a vocabulary sweep cannot answer — the second
stale assertion in that file was `secondary`, and this gate would not
have found it.
@YaelAnaya
YaelAnaya merged commit 6a0dabf into main Aug 27, 2026
15 checks passed
@YaelAnaya
YaelAnaya deleted the refactor/shadcn-foundation-realignment branch August 27, 2026 23:44
Sign up for free to 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