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:
{
"$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:
| Field | What it is |
|---|---|
name | The install id — what you type: veni add button. |
type | registry:ui for a component (a primitive like button), or registry:block for a block (a whole composition, like a login page). |
title | Display name in the catalog. |
files | The source file(s) to copy, each with a path (where it lands under ui/src) and its own type. |
registryDependencies | Other components in the same theme this one imports — not npm. veni add follows these transitively, so installing one pulls its whole graph. |
dependencies | The 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:
- Author the
.tsxatui/src/docs/themes/<theme>/ui/<name>.tsx(a block goes underui/src/docs/themes/<theme>/blocks/). Import sibling components and npm packages the normal way — the sync reads those imports to build the dependency lists. - Sync —
bun run scripts/sync-registry.tssynthesises the manifest:dependenciesfrom the npm imports,registryDependenciesfrom the sibling imports, andname/type/files/titlefrom the path. - Set its tokens, if any —
cssVars/tailwindThemecan't be derived from source, so add them once to the generated JSON; a re-sync preserves them, andveni addmerges them into the app'stokens.css. - Let CI guard it —
sync-registry.ts --checkfails 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):
| Endpoint | Returns |
|---|---|
GET /api/registry | Index of every installable item (filter by ?theme=, ?type=) |
GET /api/registry/:type/:slug?theme=veni | A lean install manifest (?format=md for Markdown) |
GET /api/registry/:type/:slug?inline=true | The 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:
veni add button dialog --theme veniIt 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:
| Theme | Holds |
|---|---|
veni | The VENI brand — the full primitive set + page blocks |
vela-ui | The 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
| Symptom | Fix |
|---|---|
veni add says the component is unknown | It 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 registry | Run bun run scripts/sync-registry.ts; CI's --check catches drift on commit. |
veni add didn't bring styles | The 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 addpulls components in. - The veni CLI —
veni add,veni theme, and every other command.