0029. @xvii is the only source of panel components and vendors the shadcn primitives
Status: accepted, 2026-10-21. Refines 0007 (a shadcn registry for shared
panel code).
Context
ADR 0007 made ui a shadcn registry for the code panels share, composed from upstream primitives that
each panel installed from upstream itself. Since then the primitives were patched (menus as wide as
their items, selects below their field, sheet widths) by editing the copies in every panel by hand, and
the registry's own items drifted: in October 2026 most panels ran an older app-shell and
responsive-dialog than the registry published, and nothing noticed. Making panels work on phones is
mostly a change to primitives every panel uses directly (touch target sizes, safe areas, localized
close labels, scrolling tab lists), so a fix there has to reach all of them, and stay.
Decision
ui vendors every shadcn primitive a panel uses (src/components/ui, src/hooks/use-mobile.ts) and
publishes each one as an @xvii item under its upstream name. Changes to primitives are made only in
ui, kept small and marked // xvii: <why>; refreshing one from upstream is
shadcn add <name> --overwrite in ui and re-applying the marked lines.
@xvii items depend on the vendored primitives by URL, never on upstream names, so re-adding an item
can't bring an unpatched primitive back.
- Panels add components only from
@xvii. A primitive the registry lacks is vendored into ui first.
- Files a panel gets from the registry are generated. A registry check (
pnpm xvii:check, and a CI job
in every panel) fails when one differs from the published registry or when those folders hold a file
the registry doesn't publish; pnpm xvii:sync re-adds every installed item.
- Behaviour every panel needs (40 px targets on coarse pointers, safe areas, localized labels inside
primitives) lives in the vendored primitives as Tailwind classes and props. Panels still write no
CSS.
Consequences
- A registry fix reaches a panel at its next change at the latest, because CI forces the sync; a sweep
is one command per panel.
ui owns upstream updates of primitives. A panel can't pick a newer upstream primitive on its own.
- Breaking prop changes in an item still need the panels that use it updated in the same sitting; the
check makes a skipped one visible instead of silent.
- In ADR 0007, "src/components/ui (upstream)" now reads "src/components/ui (vendored by @xvii)".