Skip to content

Own the interface primitives instead of vendoring them from shadcn - #605

Merged
davidmckayv merged 3 commits into
mainfrom
chore/devendor-shadcn
Sep 18, 2026
Merged

davidmckayv merged 3 commits into
mainfrom
chore/devendor-shadcn

Conversation

@davidmckayv

Copy link
Copy Markdown
Contributor

app/src/components/ui was treated as vendored code: app/components.json pointing at the shadcn registry, the shadcn CLI in the app's dependencies, and a Biome exclusion (!app/src/components/ui) that skipped the directory for both formatting and linting.

Nothing ever re-pulled from that upstream, so the arrangement bought no updates. What it did buy: a shadcn add <component> would silently revert anything we changed there, and 25 files — a quarter of the interface — went unformatted and unchecked. That came up reviewing #600, which fixes a real bug in ui/sidebar.tsx and would have been reverted by the next shadcn add sidebar.

What changed

  • Removed app/components.json and the shadcn CLI dependency. The CLI was in dependencies, not devDependencies, so it was shipping a build tool into the runtime dependency tree — that is most of the ~300 lockfile lines this deletes. @shadcn/react stays: that one is a real package the app imports (chat-transcript, message-scroller, questionnaire).
  • Removed the Biome exclusion. The directory is now formatted and linted like the rest of the app (16 files reformatted, 12 lint findings auto-fixed).
  • One genuine defect fixed: field.tsx keyed its error list by array index instead of by the message, so React reused rows when the error set changed.
  • Three a11y rules turned off for this directory by name (useSemanticElements, noLabelWithoutControl, useKeyWithClickEvents): they read a reusable primitive as if it were a finished page — a generic Label takes htmlFor from its caller, and role on a styled wrapper is the component's interface, not a missing <fieldset>. Swapping those elements would change UA styling across the design system. Three named rules in one directory is a decision somebody can review; excluding the directory outright was not.

Checks: format clean, biome lint --error-on-warnings clean across 826 files, app typecheck 0 errors, 839 app tests pass.

`app/src/components/ui` was treated as somebody else's code: a `components.json` naming the shadcn
registry, the `shadcn` CLI in the app's dependencies, and a Biome exclusion that skipped the whole
directory for both formatting and linting. Nothing re-pulled from that upstream, so the arrangement
bought no updates; it only meant a `shadcn add` would silently revert anything we changed there, and
that a quarter of the interface went unformatted and unchecked.

The primitives are ours now. The manifest and the CLI are gone -- the CLI sat in `dependencies`
rather than `devDependencies`, so it was also shipping a build tool into the runtime tree, which is
most of the lockfile this removes. `@shadcn/react` stays: that is a real package the app imports.

With the directory checked like everything else, one genuine defect surfaced and is fixed: a field's
error list keyed its items by array index rather than by the message, so React reused rows when the
set of errors changed. Three accessibility rules are turned off for this directory by name, because
they read a reusable primitive as a finished page: a generic `Label` takes its `htmlFor` from the
caller, and `role` on a styled wrapper is the component's own interface rather than a missing
`fieldset`. Naming three rules in one directory is a decision somebody can review; excluding the
directory outright was not.
Removing it broke the build. `app/src/styles.css` imports `shadcn/tailwind.css`, so the package is a
real build input rather than only the CLI that copies components in -- a distinction the first pass
missed by looking for TypeScript imports and not CSS ones.

What removes the vendoring is `components.json`, which is the file `shadcn add` reads to know where
to write; without it there is no command that can overwrite these primitives. The package stays, and
the comment in `styles.css` that told the next person not to edit a primitive because an upstream
would overwrite it is no longer true, so it says what is true now.
@davidmckayv

Copy link
Copy Markdown
Contributor Author

Correcting the PR body: the shadcn package stays. app/src/styles.css imports shadcn/tailwind.css, so it is a real build input, not just the CLI — removing it broke bun run build (I had checked TypeScript imports but not CSS ones). What actually removes the vendoring is deleting components.json: that is the file shadcn add reads to know where to write, so without it no command can overwrite these primitives. Everything else stands — the Biome exclusion is gone, the directory is formatted and linted, the array-index key is fixed, and three a11y rules are named off for primitives. Full suite now green including the build.

@davidmckayv
davidmckayv merged commit 37201c5 into main Sep 18, 2026
17 checks passed
@davidmckayv
davidmckayv deleted the chore/devendor-shadcn branch September 18, 2026 17:06
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