Component Catalog
Internal UI component inventory, preview rules, and V2 registry contract.
Overview
Yayaw includes an internal component catalog at:
/dashboard/content/components
The catalog supports a mixed inventory:
- local components from
src/components/ui/* - imported components stored in DB snapshots (non-executable)
Related docs:
- Sections Catalog for reusable section creation from component recipes
- Design Tokens for runtime token preview behavior
- Control Plane for automation safety expectations
- CMS Overview for the full content stack
CMS Role
The component catalog is the CMS rendering inventory. It documents and previews the low-level UI pieces that sections can use, including local components and imported source snapshots. Components are not authored content by themselves; they are the safe rendering primitives used by reusable sections and direct page references.
Keep component imports non-executable, compile them into validated recipes, and publish only entries that pass compile and smoke-render checks.
Goals:
- list UI components from
src/components/ui - list imported UI components from DB revisions
- preview compatible components with live props controls
- show source files used by each entry
- validate token-aware rendering with the current runtime design tokens
Scope and Conventions
Local source of truth remains explicit: src/components/ui/catalog/registry.ts.
Current scope:
- top-level files in
src/components/ui/*.tsx - key entries for
src/components/ui/kibo-ui/*/index.tsx - key entry for
src/components/ui/yayaw-table/index.ts - imported entries from DB (
ui_component_registry_items) merged into the same inventory
Conventions:
- one catalog entry per key component
- one key component per folder for nested component directories
- target entrypoint is
index.tsxfor nested folders (legacy exceptions are explicit in the registry) - imported entries are global-scope registry items with source snapshots only
- collision rule is deterministic:
imported > localfor the same slug/id
Preview Model
Preview behavior is recipe-driven and shared with runtime rendering:
src/lib/server/services/components/ui-component-recipe-compiler.tssrc/components/ui/catalog/ui-recipe-renderer-server.tsxsrc/components/ui/catalog/ui-recipe-renderer-client.tsx
Flow:
- Source snapshot is compiled to
UiComponentRecipeV1. - Catalog preview renders the recipe with generated controls from
propSchema,apiSchema, andharnessSchema. - Block runtime resolves published recipe revisions and uses the same renderer path.
creative-composition is intentionally narrower: it accepts runtime inputs
only through propSchema. Its apiSchema and harnessSchema must be omitted or
empty, its fixtures must be empty, and the related API, harness, and fixture
settings cannot act as hidden runtime content channels.
Runtime-only preview policy:
- catalog preview always uses the internal recipe renderer
- no external docs iframe is embedded in the preview panel
- preview parity checks happen through variant tabs when multiple libraries expose the same component key
Publication gating:
compiled + smoke render OK=>published(auto-publication for imports/recompile)- otherwise =>
draft_blocked
Soft delete:
- imported components are removed from catalog with publication status
deleted - source snapshots remain in DB for audit/replay
AI SDK Integration
AI-driven compile fallback and classification now use Vercel AI SDK:
src/lib/server/services/components/ui-component-ai-client.tssrc/lib/server/services/components/ui-component-recipe-ai-fallback.tssrc/lib/server/services/components/ui-component-import-classifier.ts
Rules:
- all AI outputs are validated with strict
zodschemas - model usage follows a built-in resilient fallback chain; page/block builders can still request a preferred model from UI controls
- deterministic fallback is always available if AI is unavailable
- no AI failure blocks import or compile pipelines
Library and Category Classification
Imported components are classified with a hybrid strategy:
- cache by
sourceHash(reuse previous classification metadata) - deterministic heuristics (library + group)
- AI SDK enrichment (library/category refinement)
Metadata persisted on registry rows:
librarygroupclassificationSourceclassificationVersionclassificationModel(when AI is used)
Category policy:
- normalized to lowercase kebab-case
- new categories are accepted automatically after normalization
Library policy:
- controlled values:
shadcn-ui,kibo-ui,yayaw,ai-sdk,custom
AI Elements Vendored Components
AI Elements is vendored under a dedicated namespace:
src/components/ui/ai-elements/*- catalog local registry ids use
ai-elements-*to avoid collisions with core shadcn ids
Install/update script:
bun run components:install-ai-elements
Script behavior:
- resolves
@ai-elementsregistry index - installs each registry item into
src/components/ui/ai-elements - skips existing files by default (
--allto reinstall every item) - regenerates
src/components/ui/ai-elements/index.ts - regenerates deterministic composite preview overrides in
src/lib/server/services/components/ui-component-ai-elements-preview-overrides.generated.ts - continues on per-item failure and reports a summary
Catalog behavior:
- vendored AI Elements files are listed in
src/components/ui/catalog/registry.ts - they are tagged as library
ai-sdk - unknown AI Elements families compile to
compositerecipes by default to keep previews available - local
ai-elements-*composite recipes are enriched by deterministic overrides generated from source snapshots (components:generate-ai-elements-previews) - imported AI Elements items automatically use
ai-elements-*slug prefix when their base slug would collide with an existing local catalog id - when multiple libraries expose the same canonical component key, preview tabs let users switch variants and display whether their normalized sources are identical or different
- the dashboard loads only catalog summaries first; the selected variant's recipe and source are loaded on demand so opening the Components page does not compile or transfer every component preview
Unified Entries and Variant Tabs
The inventory is grouped by logical component key, not by raw registry id.
Rules:
- one inventory row per canonical component key
- each row can expose multiple library variants (tabs in preview)
- selecting a library filter keeps a row visible when it has at least one variant from that library
- selecting a category filter keeps a row visible when at least one variant matches that category
Canonical Key Grouping Rules
Canonical key resolution is defined in:
src/components/ui/catalog/catalog-identity.ts
Current normalization:
ai-elements-*ids are grouped under their stripped base key- all other ids keep their original value as canonical key
Library variant order is deterministic:
shadcn-uiai-sdkkibo-uiyayawcustom
Import Flows and Registry Sync
V2 supports component management from the catalog UI for users with components:manage.
Supported add flows:
- shadcn command input
- full registry alias sync import (defaults to
onlyNew=true) - source snapshot paste (
name,sourceFile,sourceSnapshot) - MCP source snapshot import through
yayaw_component_import - MCP declarative recipe import through
yayaw_component_recipe_import
Registry sync action:
syncRegistryAliasComponents(registryAlias, { onlyNew: true })
Non-blocking policy for new library releases:
- registry discovery uses index payload candidates (
index.jsonandregistry.json) - items not yet vendored locally can still be imported through DB snapshot pipeline
- allowlist validation remains enforced for source domains
MCP component tools expose the same DB-backed catalog boundary:
yayaw_components_listandyayaw_component_getinspect component inventory; setincludeSchemas=trueon the list call to receive prop/API/harness schemas, runtime capabilities, usage hints, and ready-to-use component referencesyayaw_component_importstores and compiles a source snapshotyayaw_component_recipe_importvalidates and stores a declarativeUiComponentRecipeV1asdraft_readywithout executing supplied codeyayaw_component_recompilerecompiles a stored snapshotyayaw_component_publishpublishes a compiled smoke-renderable revisionyayaw_component_deletesoft-deletes an imported entry
These tools do not mutate repository-local source files in production. If an agent needs to add or modify first-party React component files, that is a code change and should happen through a branch and pull request.
Declarative imports are restricted to first-party recipe kinds implemented by
the checked-in runtime. Their complete payload, defaults, fixtures, and recursive
prop value shapes must be bounded JSON. Import creates a draft-ready revision;
publication remains an explicit separate call. For contextual
design_system_extension, import and publication alone do not prove use: the
final plan must reference the exact published component revision from a newly
created composition section. A component import carrying designContextId is
accepted only after the context has an explicit selected prototype. The
compiler writes that context ID onto the immutable recipe revision; catalog
inventory reads provenance from the exact published revision rather than
mutable registry metadata. Recompiling or publishing another revision therefore
does not inherit provenance accidentally.
For creative-composition, the generic fixture allowance does not apply:
fixtures must be empty, while apiSchema and harnessSchema must be omitted
or empty.
An exact slug@revision runtime reference is accepted only after that revision
has been published at least once. Immutable publishedAt provenance keeps an
older approved reference renderable after the catalog pointer advances, while
a compiled draft cannot be injected into a generic page.
creative-composition recipes can express bounded art-directed topology:
custom grid ratios, responsive order and spans, canvas overlap, sticky
narratives with a linear mobile fallback, focal image crops and masks, display
type roles, decorative shapes, an explicit email upload fallback, and
purposeful motion with a runtime-owned reduced-motion state. Form and native
upload nodes are fail-closed behind a checked-in registry of exact public CMS
handlers. The registry contains one multipart handler:
POST /api/cms/forms/personalization-request. Its required controls are
email (email), firstName (text), consent (checkbox), and photo
(native images upload); birthDate (date) and message (textarea) are
optional. Names, types, and required flags must match this literal contract.
The photo must be JPEG, PNG, or WebP and no larger than 8 MB. The server
auto-rotates and re-encodes it without source metadata, re-inspects the
canonical bytes, and stores only that canonical image in private submission
storage rather than promoting it into CMS media. Upload and deletion states
are durable database ledger states; non-ready and expired requests never appear
in operator reads. This endpoint creates a personalization request for later
operator follow-up. It is not a checkout, order-confirmation, or payment
endpoint.
honest_form_controls additionally requires fields and a submit control inside
that registered actionable form; file_upload requires its exact multipart
POST contract. Arbitrary same-origin actions remain rejected. For every other
handler or action, only email-fallback honestly represents file collection.
Admin operators process private submissions with
yayaw_cms_personalization_requests_list,
yayaw_cms_personalization_request_get,
yayaw_cms_personalization_request_status_update,
yayaw_cms_personalization_request_delete, and
yayaw_cms_personalization_requests_prune. The prune operation supports
dry-run and removes requests after their 180-day retention deadline. The
long-lived Page AI worker runs bounded maintenance independently of queued AI
runs, recovering interrupted upload/deletion objects and enforcing retention;
a small best-effort prune after successful submission only supplements that
durable worker path. A display type capability requires an allowlisted
display style plus an explicit family role (brand-sans, brand-serif, or
brand-mono) that maps to the active design-token font family. Surface props
use stable semantic roles such as background, foreground, card, muted,
primary, accent, and border; they resolve through the active theme rather
than promise a literal color. Older color-like surface names remain
compatibility aliases and do not prove semantic_surfaces. Functional props
also carry bounded value types and required fields, so a string such as
"true" cannot stand in for a boolean runtime behavior. The server infers
creativeCapabilities only from a valid exact declarative tree and rejects a
supplied manifest that claims primitives the tree does not implement. A
selected prototype declares requiredCapabilities; before page persistence,
each mapped region must use the exact published recipe revision whose
server-derived structural and implementation manifests match the canonical
topology and prop names/schema/tree registered for that region. That exact revision must be the
mapped section's published root component, and its resolved instance bindings
must match the registered render manifest. new_recipe requires a same-context
created revision and cannot reuse a compositionHash topology already present
on a published creative-composition in the prepared inventory; a new ID,
slug, or revision does not make a clone new. reuse_published requires its declared
published component. A nested occurrence of the promised component does not
satisfy the contract.
The checked-in runtime also exposes these bounded fidelity primitives through
yayaw_components_list(includeSchemas=true):
canvas.heightacceptscontentfor content-owned height andbandfor a compact stage;canvas.mobileGapcontrols the bounded gap when children stack on mobileimage.aspect,image.mobileAspect, andimage.desktopAspectacceptauto,cinema,landscape,panorama,portrait,square,strip,tall, orvideo;image.heightacceptsauto,band,panel, orherodecoration.treatmentacceptssolid,outline, orwash;washrenders a layered pigment-like accent and does not replace required imageryframe.materialacceptsclean,paper,deckle, ortaped;frame.rotationandplacement.rotationaccept the bounded values-6,-3,0,3, or6, andtext.style: "note"provides a handwritten-note treatmenttext.weightacceptsnormal,medium,semibold,bold, orblackvisibility.showacceptsall,mobile, ordesktop, whiledisclosurerenders a bounded semanticdetails/summarycontrol with requiredlabeland optionalalign,appearance: "bare" | "framed", andsurface
These nodes infer compact_band, responsive_media, watercolor_wash,
paper_collage, typographic_weight, responsive_recomposition, and
responsive_navigation only from material behavior: compact_band requires
height: "band", responsive_media requires distinct non-auto mobile and
desktop aspects without a fixed height, and responsive_navigation requires a
mobile disclosure plus a non-empty desktop branch. Prototype planning must keep
a per-region feasibility ledger. Responsive order props (desktopOrder or
mobileOrder) require a direct grid or stack parent; responsive span props
(desktopSpan or mobileSpan) require a direct grid parent. A qualifying
ancestor is not sufficient. Every region declares exact allowlisted nodes,
canonical paths (tree, then tree.children.N recursively), bounded props, the
exact declarative recipePropSchema, an equal-key recipePropNames list, and
the exact recipePropBindings for its intended section instance. Required props
without defaults need bindings, binding keys must belong to the schema, and
every image.src must be exactly $props.<identifier> with that prop bound as
media_asset or localized_media_asset to durable prepared-inventory media.
mediaAssetIds must be the exact deduplicated set of asset IDs used by all
media bindings. The server rebuilds and validates the recipe through the
canonical runtime contract. It computes compositionHash from node names,
canonical paths, and structural props only, excluding content, media, actions,
surface/tone palette choices, and other non-structural props while normalizing
runtime references. It separately computes implementationHash from the prop
names/schema/tree and renderManifestHash from that implementation plus its
bindings. It compares all server-owned manifests with the exact published root
recipe instance used by the mapped section.
A different tree with the same coarse capabilities, a nested substitute, or
changed bindings fails. A global capability union cannot hide an infeasible
region. For design_system_extension, every A/B/C candidate must visibly map at
least one region to new_recipe.
Prototype-visible text, links, labels, placeholders, alternative text, and
upload messages must each be a direct $props.<identifier> sink. Text-like
bindings are exact localized literals for every prepared locale; links are
bounded safe literal or localized targets. Prototype-critical global-data
bindings are rejected rather than treated as frozen. repeat, $item, and
$index are also rejected: visible repeated content must be expanded into
explicit planned nodes so the registered topology and bindings match the
reviewed runtime.
Prototype registration also requires the complete A/B, A/C, and B/C pairwise
comparison set, with exact artifact-region evidence for both candidates in
every stable region. Ordered regional compositionHash values form the
structural signature. The normalized page-shell manifest is pinned separately
and must match the selected candidate exactly in the plan and runtime; it is
not part of the candidate-distinctness signature. Ordered regional
implementationHash and renderManifestHash values form the implementation
and render signatures. All three A/B/C signatures of every kind must differ.
Content, media, palette, schema-default, or binding changes cannot make
unchanged topology a separate direction. A server-computed normalized
16 × 16 grayscale edge-matrix
fingerprint additionally rejects cloned or insufficiently different full
boards. It is only a coarse full-board structure guard, never sufficient proof
of a distinct direction or fidelity, and supplements the three signatures,
pairwise critique, visual inspection, and substantive Visual QA.
Exact-runtime prototype registration uses
kind: "generated_set_v2". Before registration, every story or recipe image
used by these bindings must be finalized with image MIME, dimensions, and
SHA-256 provenance, then admitted by the single pre-selection media-only
inventory refresh. Board assets and post-registration runtime screenshots are
queried directly by asset ID and must not trigger another inventory refresh.
Each candidate supplies an exact page shell (layout plus bounded page
designTokens or null) and integer pixel artifactBounds for every visible
region. A region is at least 200 × 120 pixels and 2% of board area, all regions
together cover at least 40%, and pairwise overlap is at most 50% of the smaller
region. Page-scoped tokens must be null outside global page scope. For every
candidate pair, at least one matching region fingerprint must be materially
distinct; board chrome alone cannot distinguish a direction.
Clients provide planned recipe trees, schemas, bindings, and bounds, but never
the derived composition, implementation, render, page-shell, fingerprint, or
runtime hashes. The server computes those fields and resolves each candidate
through the canonical page and recipe runtime. Registration returns signed
runtime previews and a per-candidate runtimeHash. Visual QA compares each
board with every prepared locale in desktop/mobile and light/dark, uploads each
distinct full-page capture with the exact returned context, runtime hash, and
role, then calls yayaw_cms_prototype_runtime_review_complete. The
visual_qa value is a logical role on the current actor, not proof of an
independently authenticated reviewer. Boards and runtime previews remain
separate visual artifacts: the manifests and hashes detect substitution or
drift, but do not prove pixel equivalence.
Production runtime resolution fails closed without an immutable fingerprint
from NEXT_DEPLOYMENT_ID, resolved gitCommitSha such as
DEPLOYMENT_GIT_COMMIT_SHA, or resolved deploymentId; "local" is a
non-production fallback only. The deployment fingerprint and renderer contract
version enter runtimeHash, so a renderer build change invalidates earlier
review.
Selection is blocked until this runtime review is current. A renderer, deployment, recipe, media, screenshot, or token change can stale it. A design-token change after registration requires a new prepared context and three fresh candidates. After selection, exact recipe revisions can be imported and published, followed by one same-token inventory refresh before the strict version-2 plan. That plan reproduces the selected candidate's exact page-shell layout and page-scoped token layer.
Production enables v2 registration only after the reader-first rollout:
CMS_PROTOTYPE_RUNTIME_V2_WRITES_ENABLED defaults off in production, stays
false while the compatible reader build replaces and drains every pre-reader
instance, and is set to true only after that build becomes the rollback floor.
The separate v2 field does not make a mixed fleet safe. Once any v2 context
exists, never roll back to a pre-reader build; roll forward until those contexts
are retired or migrated. Legacy registration remains readable and writable
during the reader-first phase for transition compatibility, but a new exact
workflow must not downgrade to it. The disabled writer returns
cms_design_prototype_runtime_rollout_pending. Local and other non-production
environments default v2 writes on when the variable is absent.
Block Planner Usage Hints
The block AI planner consumes catalog usage hints when available:
- short example snippet from CLI/persisted context
- key registry dependencies
- docs/examples links
This metadata is read-only and passed in prompt payload to improve component and prop selection in generated block trees. It does not execute any DB source code.
CLI-First Context Enrichment
Imports now resolve context from the official shadcn CLI first, then apply deterministic fallbacks.
Implementation points:
src/lib/server/actions/components/component-registry-actions.tssrc/lib/server/services/components/ui-component-shadcn-cli-context.tssrc/lib/server/services/components/shadcn-cli-runner.tssrc/lib/server/services/components/ui-component-recipe-import-enrichment.tssrc/lib/server/services/components/ui-component-import-preview-evidence.ts
Behavior:
- CLI runner is local-first (
bunx shadcn) withnpx -y shadcn@latestfallback, timeout, retry, and structured logs - resolver aggregates:
shadcn viewpayload metadata/filesshadcn docs --jsonlinks for core shadcn componentsshadcn search+shadcn viewfor registry example blocks
- resolved context includes:
sourceTitle/sourceDescriptionexampleSnippetsregistryDependencies- docs links (
docs,examples) - interaction hint (
click-trigger,selection, or inline fallback)
- preview evidence priority is deterministic:
CLI -> payload -> web -> source snapshot - context is cached in import metadata by
sourceHash(contextVersion, links, snippets, dependencies), so repeated imports avoid repeated CLI calls - when CLI context is unavailable, import remains non-blocking and compile/publish gating behavior is unchanged
- no external source code is executed; only metadata and snapshots are parsed
Interaction-first policy:
- overlay-like previews should prefer explicit click triggers over always-open states
- composite AI blueprints are neutral by default (no AI/chat-specific microcopy unless source evidence explicitly requires it)
Design Token Behavior
The catalog preview does not rely only on baseline CSS.
It resolves effective runtime tokens for the current session (global + organization overrides) and applies scoped CSS variables to the preview surface. This lets dashboard users validate UI components against the same semantic token values used at runtime.
Validation
Registry consistency is validated by tests:
- unique entry ids
- source file existence
demoIdreferences resolved by the demo map
Runtime/catalog guards:
- no DB code execution (source snapshots are never evaluated)
- publication blocked when compile diagnostics or smoke render fails
- design-system extensions import new recipes only after prototype selection
with the active
designContextId; the refreshed inventory carries provenance from the exact published immutable revision created_component_not_newblocks a component that already existed in the initial prepared baseline, whilecreated_component_context_mismatchblocks a component imported by another workflow- contextual design reports
created_component_not_usedwhen a component declared as newly created is absent from every new composition, including when only a different revision of the same slug is referenced
Useful commands:
bun test src/components/ui/catalog/registry.test.ts
bun test src/lib/server/services/components/ui-component-recipe-compiler.test.ts
bun test src/lib/server/services/components/ui-component-catalog-query.test.ts
bun run components:generate-ai-elements-previewsEnvironment Variables
OPENAI_API_KEYOPENAI_COMPONENTS_AI_FALLBACK
These OpenAI variables are optional and only affect AI SDK enrichment for imported
components. Local AI Elements preview overrides are fully deterministic and do not
require OPENAI_API_KEY.
Contract Direction
- global product scope
- source snapshot storage for audit/replay
- no runtime execution of DB-stored code
Reference type:
UiComponentRegistryItemV1insrc/components/ui/catalog/types.ts
DB tables:
ui_component_registry_itemsui_component_recipe_revisionsui_component_publications