03 / Develop · Veni builder · Components

The component registry ​

Veni builder hosts a shadcn-style component registry — a browsable catalog of the shared UI primitives, generated from its own themed UI and shipped to satellites. One source of truth (.tsx files per theme) is synced into JSON manifests, then consumed two ways: served live to AI agents over the API, and bundled into the veni CLI for veni add. This page is how that pipeline works and how to add a component; to use it, veni add is in The veni CLI.

From source to registry ​

The catalog's source of truth is the builder's own UI — each theme's components live as .tsx at ui/src/docs/themes/<theme>/ui/*.tsx (and page-level blocks at ui/src/docs/themes/<theme>/blocks/*.tsx). Every theme is equal; there is no master.

scripts/sync-registry.ts regenerates the draft-registry wholesale from those files — each .tsx becomes one manifest, fully synthesised:

  • name / type / files / title — from the file name.
  • dependencies — the npm packages the component imports.
  • registryDependencies — sibling components in the same theme it imports (resolved transitively at install).
  • cssVars / tailwindTheme — the functional token config, preserved across re-runs (it can't be derived from source, so the generator keeps what's there).

Stale entries (a .tsx that was deleted) are pruned. In CI, sync-registry.ts --check exits non-zero if the registry has drifted from the source — so the manifests can never fall out of sync with the components.

The registry item ​

Each manifest is a shadcn-shaped registry item — the recipe for installing one component: its source files, plus everything else that has to come with it. Here's button:

json
{
  "$schema": ".../registry-item.schema.json",
  "name": "button",
  "type": "registry:ui",
  "title": "Button",
  "files": [{ "path": "components/ui/button.tsx", "type": "registry:ui" }],
  "registryDependencies": [],
  "dependencies": ["class-variance-authority", "lucide-react", "radix-ui"]
}

Field by field:

FieldWhat it is
nameThe install id — what you type: veni add button.
typeregistry:ui for a component (a primitive like button), or registry:block for a block (a whole composition, like a login page).
titleDisplay name in the catalog.
filesThe source file(s) to copy, each with a path (where it lands under ui/src) and its own type.
registryDependenciesOther components in the same theme this one imports — not npm. veni add follows these transitively, so installing one pulls its whole graph.
dependenciesThe npm packages it imports. veni add adds them to ui/package.json, version-pinned against the reference package.json — never a floating range.

The two dependency lists are the crux: registryDependencies = sibling source files to copy in, dependencies = npm packages to install. A dialog that uses your button lists button in the first and radix-ui in the second — and one veni add dialog resolves both.

Adding a component ​

To add a primitive to a theme's catalog:

  1. Author the .tsx at ui/src/docs/themes/<theme>/ui/<name>.tsx (a block goes under ui/src/docs/themes/<theme>/blocks/). Import sibling components and npm packages the normal way — the sync reads those imports to build the dependency lists.
  2. Sync — bun run scripts/sync-registry.ts synthesises the manifest: dependencies from the npm imports, registryDependencies from the sibling imports, and name/type/files/title from the path.
  3. Set its tokens, if any — cssVars / tailwindTheme can't be derived from source, so add them once to the generated JSON; a re-sync preserves them, and veni add merges them into the app's tokens.css.
  4. Let CI guard it — sync-registry.ts --check fails if the registry ever drifts from the source, so a forgotten sync can't ship.

It's then browsable in the catalog, servable via /api/registry, and installable with veni add <name> --theme <theme> once the CLI is re-packaged.

Consuming it, two ways ​

Both channels read the same draft-registry, resolve the transitive registryDependencies within the theme, inline each file's source, and union + version-pin the npm dependencies against the reference package.json — so a caller installs a component and its whole graph in one shot.

The API — for AI agents ​

The registry controller serves it, token-gated (the same Veni credential the CLI holds):

EndpointReturns
GET /api/registryIndex of every installable item (filter by ?theme=, ?type=)
GET /api/registry/:type/:slug?theme=veniA lean install manifest (?format=md for Markdown)
GET /api/registry/:type/:slug?inline=trueThe manifest with every file's source inlined — a one-round-trip install
GET /api/registry/:type/:slug/file?path=…The raw source of one file

:type is component or block. A content hash rides in x-registry-version as a cache validator.

veni add — for humans ​

The veni CLI ships the catalog inside its download — packageCli bundles the sources into .veni/themes/<theme>/{registry,source}. So a component is installable once your CLI is current; refresh it (curl … install.sh | bash) to pull newer components. Then, from your app folder:

bash
veni add button dialog --theme veni

It resolves the dependency graph, copies each component's source into ui/src, adds the npm dependencies to ui/package.json, and merges the design tokens (cssVars / tailwindTheme). Add --overwrite to replace files you've already got.

Themes ​

Components resolve from one theme only — there is no cross-theme fallback, and a component a theme doesn't ship is an error. The bundled themes:

ThemeHolds
veniThe VENI brand — the full primitive set + page blocks
vela-uiThe VelaUI brand — its own primitive set

veni theme <brand> (or the wizard's Theme step) swaps an app's tokens.css to a brand; veni add … --theme <brand> pulls that brand's components.

Common snags ​

SymptomFix
veni add says the component is unknownIt isn't in that theme (no cross-theme fallback), or your CLI is stale — refresh it (curl … install.sh | bash) and retry.
A newly-authored component isn't in the registryRun bun run scripts/sync-registry.ts; CI's --check catches drift on commit.
veni add didn't bring stylesThe component's tokens live in cssVars / tailwindTheme — add merges them into the app's tokens.css; make sure the app is on the matching --theme.

See also ​

  • Scaffold — the starter ships integration-only; veni add pulls components in.
  • The veni CLI — veni add, veni theme, and every other command.