Docs/uiv1.0.0-rc.1

UIComponentRegistry

Overview

UIComponentRegistry is a named-component catalogue that maps string names to ComponentConfig records containing metadata, an optional AIClient, and an optional AIStyler. The public root exports an initially empty singleton componentRegistry. Register configurations explicitly during application startup; importing component factories does not register them. These root imports are for Node-side code, not browser bundles.

Key Concepts

  • UIComponentRegistry — a Map-backed registry. register() adds or overwrites an entry; get() looks up an entry by name; list() returns all registered names.
  • ComponentConfig — the metadata record stored per component: name, description, an optional aiClient, an optional styler, and optional default props.
  • generateComponent — calls aiClient.generateCode(prompt) with a prompt that instructs the AI to produce a TypeScript component source file. Returns the generated source as a string.
  • componentRegistry — the module-level singleton instance. The public root does not run the internal UI auto-registration barrel; register configurations explicitly.

API Reference

ComponentConfig

interface ComponentConfig {
  name: string;           // Unique component name (e.g. 'Button')
  description: string;    // Human-readable description used in AI generation prompts
  aiClient?: AIClient;    // AI client for code generation (optional)
  styler?: AIStyler;      // AI styling bridge for CSS generation (optional)
  props?: Record<string, any>; // Default props snapshot (optional)
}

UIComponentRegistry

class UIComponentRegistry {
  // Register or overwrite a component entry
  register(config: ComponentConfig): void;

  // Look up a component entry by name; returns undefined if not found
  get(name: string): ComponentConfig | undefined;

  // List all registered component names
  list(): string[];

  // Generate a new component's TypeScript source via AI
  generateComponent(
    name: string,
    description: string,
    aiClient: AIClient
  ): Promise<string>;
}

Singleton

// Initially empty; populated by application startup code
const componentRegistry: UIComponentRegistry;

generateComponent prompt

The method constructs this prompt internally:

Create a TypeScript React-like component called <name> that <description>.
Include proper TypeScript types, props interface, and modern best practices.

The return value is the raw string returned by aiClient.generateCode(prompt).

Basic Usage

Listing registered components

import assert from 'node:assert/strict';
import { componentRegistry } from '@tagna/udiot';

// In a fresh process, before any application registrations:
assert.deepEqual(componentRegistry.list(), []);
for (const name of ['Button', 'Card', 'Input']) {
  componentRegistry.register({ name, description: `${name} UI component` });
}
assert.deepEqual(componentRegistry.list(), ['Button', 'Card', 'Input']);

Registering a custom component

import { componentRegistry } from '@tagna/udiot';

componentRegistry.register({
  name: 'Tooltip',
  description: 'A floating tooltip component with arrow indicator and positioning logic',
});

const config = componentRegistry.get('Tooltip');
console.log(config?.description);
// 'A floating tooltip component with arrow indicator and positioning logic'

Registering with an AIStyler

import { componentRegistry } from '@tagna/udiot';
import { AIStyler, AIClient } from '@tagna/udiot';

const aiClient = new AIClient({ apiKey: process.env.AI_API_KEY! });
const styler   = new AIStyler(aiClient);

componentRegistry.register({
  name: 'HeroSection',
  description: 'A full-width hero banner with background image and CTA button',
  aiClient,
  styler,
  props: { headline: 'Default Headline', ctaLabel: 'Get Started' },
});

Generating a new component with AI

import { componentRegistry } from '@tagna/udiot';
import { AIClient } from '@tagna/udiot';
import { writeFile } from 'fs/promises';

const aiClient = new AIClient({ apiKey: process.env.AI_API_KEY! });

const source = await componentRegistry.generateComponent(
  'PricingTable',
  'displays three subscription tiers with feature lists, pricing, and a highlighted recommended tier',
  aiClient
);

// Write generated source to disk
await writeFile('./src/ui/PricingTable.ts', source, 'utf-8');
console.log('Generated PricingTable component');

Dynamic component rendering pattern

import assert from 'node:assert/strict';
import { componentRegistry } from '@tagna/udiot';
import { createButton, createCard, createInput } from '@tagna/udiot';

const componentFactories: Record<string, (props: any) => { render(): string }> = {
  Button: (props) => createButton(props),
  Card:   (props) => createCard(props),
  Input:  (props) => createInput(props),
};

// Metadata and factories are separate; configure both in this example.
for (const name of Object.keys(componentFactories)) {
  componentRegistry.register({ name, description: `${name} UI component` });
}

function renderComponent(name: string, props: Record<string, any>): string {
  if (!componentRegistry.get(name)) {
    throw new Error(`Unknown component: ${name}`);
  }
  const factory = componentFactories[name];
  if (!factory) {
    throw new Error(`No factory registered for: ${name}`);
  }
  return factory(props).render();
}

// Usage
const html = renderComponent('Button', { children: 'Click me', variant: 'primary' });
assert.ok(html.includes('Click me'));
assert.throws(() => renderComponent('Unknown', {}), /Unknown component/);

Common Patterns

Component catalogue endpoint

import { componentRegistry } from '@tagna/udiot';

// In an API route handler — returns available components for a design tool
function getComponentCatalogue() {
  return componentRegistry.list().map(name => {
    const config = componentRegistry.get(name)!;
    return {
      name: config.name,
      description: config.description,
      hasAI: !!config.aiClient,
      defaultProps: config.props ?? {},
    };
  });
}

Updating an explicitly registered configuration

import assert from 'node:assert/strict';
import { componentRegistry } from '@tagna/udiot';

componentRegistry.register({ name: 'Button', description: 'Application button' });
const existing = componentRegistry.get('Button');
assert.ok(existing);
componentRegistry.register({ ...existing, props: { variant: 'secondary' } });
assert.equal(componentRegistry.get('Button')?.props?.variant, 'secondary');

Integration Notes

  • componentRegistry is a module-level singleton. It is shared across all imports of @tagna/udiot within the same Node.js process. Registrations from one module are visible to all others.
  • register() overwrites silently. If you register a name that already exists, the previous ComponentConfig is replaced without warning.
  • generateComponent() returns a TypeScript source string. It does not compile, evaluate, import, or register the generated component automatically. You are responsible for writing the output to disk and including it in your build.
  • generateComponent uses its explicit aiClient argument, not the client stored in a configuration. Stored clients and stylers are metadata for application code; the registry does not automatically attach them to rendered components.

Pitfalls

  • componentRegistry.get(name) returns undefined for unregistered names. Always guard the return value before accessing its properties.
  • The generateComponent prompt is fixed — it cannot be customised. If you need a tailored prompt, call aiClient.generateCode(yourPrompt) directly instead of using generateComponent.
  • componentRegistry is not cleared between requests in SSR. Registrations made during one request persist for the lifetime of the process. Avoid registering user-supplied component names — they will affect all subsequent requests.
  • props in ComponentConfig is a documentation/default-value hint. It is not used by any framework machinery — you must pass actual props to component factories (e.g. createButton(props)) separately.
From the canonical UDIOT public documentation.Try it in the playground ↗