Page actions

views()

Creates an immutable view registry mapping spec element type names to Angular components. A view registry drives <render-spec> when it is provided through provideViews(), and it is also the shape the chat components accept for their generative-UI path.

Usage

import { views } from '@threadplane/render';
 
const ui = views({
  'plan-checklist': PlanChecklistComponent,
  'file-preview': FilePreviewComponent,
  'code-output': CodeOutputComponent,
});

Provide it once, then render specs whose element type values match the registered names:

// app.config.ts
import { provideViews } from '@threadplane/render';
 
export const appConfig: ApplicationConfig = {
  providers: [provideViews(ui)],
};
<render-spec [spec]="spec" />

The same registry is what the chat component accepts on its views input:

<chat [agent]="stream" [views]="ui" />

Composition

Compose registries via object spread. The last key wins for overrides:

const all = views({
  ...thirdPartyViews,
  ...myViews,
  'chart': MyCustomChart, // overrides thirdPartyViews['chart']
});

Per-View Fallbacks

Each entry is either a bare component class or a { component, fallback } object. The fallback mounts while any resolved prop on the element is still undefined -- the same shape defineAngularRegistry() accepts:

const ui = views({
  'plan-checklist': {
    component: PlanChecklistComponent,
    fallback: PlanSkeletonComponent,
  },
  'file-preview': FilePreviewComponent, // bare form uses the default fallback
});

withViews() and overrideViews() accept the same object form in their addition and override maps.

API

views(map)

Creates a frozen ViewRegistry.

ParameterTypeDescription
mapRecord<string, Type<unknown> | RenderViewEntry>Name → component class, or a { component, fallback?, schema?, description? } entry
ReturnsViewRegistryFrozen immutable registry

withViews(base, additions)

Adds new views without overwriting existing entries. Existing keys in base are preserved.

const extended = withViews(base, {
  'new-widget': NewWidget,    // added
  'existing': Other,          // IGNORED — base already has 'existing'
});

overrideViews(base, overrides)

Replaces entries in a registry. Keys in overrides win over base. Use this when you want to swap an existing renderer; use withViews when you want to add new node types without touching existing ones.

import { overrideViews } from '@threadplane/render';
 
const myRegistry = overrideViews(baseRegistry, {
  'code-block': MyCodeBlockComponent,
});

Returns a new frozen ViewRegistry. The base argument is not mutated.

withoutViews(base, ...names)

Removes views by name:

const restricted = withoutViews(base, 'dangerous-widget', 'internal-tool');

provideViews(registry)

Angular DI provider for the VIEW_REGISTRY token. Works at the application injector or on a route:

// Global
providers: [provideViews(ui)]
 
// Route-scoped
{ path: 'planning', providers: [provideViews(planningViews)] }

<render-spec> consumes VIEW_REGISTRY as a third-priority fallback in registry resolution. The full priority order is: the [registry] template input → RENDER_CONFIG.registry (from provideRender(...)) → VIEW_REGISTRY (from provideViews(...)) → an empty registry. So provideViews(myRegistry) drives rendering when no provideRender({ registry }) is wired and no [registry] input is bound. <render-element> never reads the token itself; it uses the registry <render-spec> already resolved and shared through RENDER_CONTEXT.

toRenderRegistry(registry)

Converts a ViewRegistry to the low-level AngularRegistry type the render components consume. Called internally by <render-spec> and by the chat components -- most developers do not need it directly.

View Components

View components are standard Angular standalone components. They receive resolved props as input() signals:

@Component({
  selector: 'plan-checklist',
  standalone: true,
  template: `
    <div class="border rounded-xl p-4">
      <h4>{{ title() }}</h4>
      <ng-content />
    </div>
  `,
})
export class PlanChecklistComponent {
  readonly title = input<string>('Plan');
}

The element type in the spec is matched against the registry key to resolve the component class, so the names in views({ ... }) are the allowlist of what a spec is permitted to render. For how the chat components decide that a streamed assistant message is a spec at all, see json-render vs A2UI.

viewsfunction

Creates a view registry from a name → component map.

views(map: Record<string, Type<unknown> | RenderViewEntry>): ViewRegistry

Parameters

ParameterTypeDescription
mapRecord<string, Type<unknown> | RenderViewEntry>

Returns

ViewRegistry

Examples

const registry = views({ metric: MetricCardComponent, chart: ChartComponent });
// providers: [provideViews(registry)]

Looking for something specific?