Upstream shadcn/ui through `patties add @shadcn/<name>`
Stamp the canonical shadcn/ui registry into a patties app with the same bytes
Purpose
Stamp the canonical shadcn/ui registry into a patties app with the same bytes npx shadcn add would write, through the existing add pipeline (one stamper, same guarantees). Interactivity comes from the page (export const hydrate = true, framework/28), so the adapter never injects island wrappers.
Surface
patties ui init --shadcn # @/* → ./app/* tsconfig alias + app/lib/utils.ts
patties add @shadcn/dialog # built-in namespace (new-york-v4 style)
patties add https://…/item.json # any shadcn registry item URL
patties add ./vendor/item.json # or a local one
bunx create-patties my-app --ui shadcn
@shadcn defaults to https://ui.shadcn.com/r/styles/new-york-v4; config.ui.registries["@shadcn"] overrides it (mirror / pinned copy).
Mapping (add/shadcn.ts)
| shadcn item | patties |
|---|---|
files[].content | written verbatim; only @/registry/<style>/{ui,lib,hooks,components}/… → @/{components/ui,lib,hooks,components}/… |
registry:ui | componentsDir/<file> |
registry:lib / hook / component / block | files[].root: "app" → app/lib, app/hooks, app/components |
files[].target | honored only inside app/{components,hooks,lib} (root: "app"); routes, config files, dotfiles and anything else are refused |
dependencies (+ bare imports not declared upstream) | peerDeps; unversioned ones pinned to ^<npm latest> (offline keeps latest); package.json edited, never installed |
registryDependencies (+ @/registry/… imports) | resolved depth-first through the same resolver, deduped |
cssVars.{theme,light,dark} | a shadcn-<name> token group merged into tokensFile |
| — | island: "no", status: "completed", spec: "shadcn:<source>" |
Mapped entries are re-validated against ComponentEntrySchema; unknown item types (registry:style, registry:theme, …) fail loudly. The schema gained an optional files[].root (components | app); component-only items omit it so older patties-ui releases still validate them. The stamper confines app files to components/, hooks/, lib/ with no dot-segments, for every source (registry payloads are untrusted even after preview, since --yes skips it).
Guarantees (unchanged from cli/15)
HTTPS only unless --allow-insecure; external source previewed and confirmed (or --yes); fetched code is never executed; no install is ever run.
Scaffolder
create-patties --ui shadcn vendors upstream button + dialog (drift-tested against the adapter's output), writes app/lib/utils.ts, the @/* alias, and a hydrated demo route; it drops the island demo. Scaffolded patties / patties-ui ranges are pinned to ^<version> baked into create-patties/src/versions.ts by scripts/sync-versions.ts, which the root version script runs after changeset version.
Tests
tests/cli/add-shadcn.test.ts, tests/integration/shadcn-dialog.test.tsx (stamped upstream Dialog opens/closes on a hydrated page), create-patties/tests/scaffold.test.ts.