Component Blocks Reference
Reusable UI widgets for the Layout Designer
Overview
Blocks vs Widgets: Component blocks are self-contained layout sections (Asset Browser, Timeline, AI Chat). For individual field inputs (color pickers, sliders, entity selectors), see Field Widgets Reference.
Component blocks are self-contained UI widgets that can be placed in layout sections alongside form fields. They are managed through the Component Block Registry (src/lib/component-block-registry.ts) and rendered by ComponentBlockRenderer.
Built-in Blocks
entity-assets
| Property | Value |
|---|---|
| Category | media |
| Modes | edit, view |
| Source | builtin |
Full asset browser with upload, primary image selection, and asset role management.
Config Options:
title(string, default: “Entity Assets”) — Section headingshowUpload(boolean, default: true) — Show upload controls
entity-timeline
| Property | Value |
|---|---|
| Category | display |
| Modes | edit, view |
| Source | builtin |
Timeline event panel showing entity history with auto-populate capability.
Config Options:
compact(boolean, default: true) — Use compact display modeshowAutoPopulate(boolean, default: true) — Show auto-populate button
production-status
| Property | Value |
|---|---|
| Category | workflow |
| Modes | edit, view |
| Source | builtin |
Production pipeline status editor showing general/game/TV status with inline save.
Config Options: None (uses entity’s production_status field directly)
markdown-content
| Property | Value |
|---|---|
| Category | display |
| Modes | edit, view |
| Source | builtin |
Rendered markdown body content using MarkdownRenderer component.
Config Options:
field(string, default: “markdown_body”) — Which entity field contains the markdown
primary-image
| Property | Value |
|---|---|
| Category | media |
| Modes | edit, view |
| Source | builtin |
Hero/primary image display with placeholder fallback when no image is set.
Config Options:
field(string, default: “primary_image”) — Entity field containing the image pathheight(string, default: “300px”) — Display height
entity-chat
| Property | Value |
|---|---|
| Category | interaction |
| Modes | edit |
| Source | builtin |
AI-powered entity chat panel for conversational editing of entity fields.
Config Options:
visibility(select: team/private, default: “team”) — Chat session visibility
entity-relationships
| Property | Value |
|---|---|
| Category | display |
| Modes | edit, view |
| Source | builtin |
Automatically scans common relationship fields and renders them as grouped link cards. Scanned fields include: relationships, allies, enemies, members, key_personnel, associated_brands, subsidiaries, parent_company, primary_npcs, controlled_territories.
Config Options: None (auto-discovers fields)
entity-workflow-trigger
| Property | Value |
|---|---|
| Category | workflow |
| Modes | edit |
| Source | builtin |
Workflow trigger buttons for entity-level automations.
Config Options: None
Creating a Custom Block
Step 1: Create the Component
// src/components/blocks/MyCustomBlock.tsx
interface MyCustomBlockProps {
config?: Record<string, any>;
entityType: string;
entityId: string;
data: any;
mode: 'edit' | 'view';
}
export default function MyCustomBlock({ config, data, mode }: MyCustomBlockProps) {
return <div>{/* Block UI */}</div>;
}Step 2: Register in ComponentBlockRenderer
// src/components/ComponentBlockRenderer.tsx
case 'my-custom-block':
return <MyCustomBlock config={config} entityType={entityType} entityId={entityId} data={data} mode={mode} />;Step 3: Add to Registry
// src/lib/component-block-registry.ts — add to BUILTIN_BLOCKS array
{
id: 'my-custom-block',
label: 'My Custom Block',
description: 'Does something useful',
icon: 'Sparkles',
category: 'display',
source: 'builtin',
default_config: {},
config_schema: {
title: { type: 'string', label: 'Title', default: 'Default Title' },
showExtra: { type: 'boolean', label: 'Show Extra', default: false },
},
modes: ['edit', 'view'],
}The block will automatically appear in the Layout Designer palette under its category.
Config Schema
Blocks can declare a config_schema for designer-editable settings. The BlockConfigPanel renders this dynamically.
Supported Field Types
| Type | Renders As | Example |
|---|---|---|
string | Text input | type: 'string', label: 'Title', default: 'Assets' |
number | Number input | type: 'number', label: 'Max Items', default: 10 |
boolean | Toggle checkbox | type: 'boolean', label: 'Show Upload', default: true |
select | Dropdown | type: 'select', label: 'Visibility', default: 'team' |
Rendering
ComponentBlockRenderer receives these props:
interface ComponentBlockRendererProps {
componentId: string; // Block ID or "plugin:{id}:{panel}"
config?: Record<string, any>;
entityType: string;
entityId: string;
data: any;
mode?: 'edit' | 'view';
}It uses React.lazy for code-splitting large components (EntityAssets, EntityChatPanel) and dispatches to the correct block based on componentId. Unknown blocks show a warning fallback. Plugin blocks (matching plugin:*:*) are routed to PluginBlockIframe.
Manifest Components
These components work with the project manifest (S2.1/S5.1) for entity asset browsing.
EntityAssetIndicator
Location:
city-brains-studio/src/components/assets/EntityAssetIndicator.tsxDisplays asset count and total size for an entity using manifest data.
Shows asset indicators like “2 images, 1 video (14 MB)” or “6 assets (14 MB)” for an entity. Used in entity list views and detail pages.
Props:
interface EntityAssetIndicatorProps {
summary: EntityAssetSummary | null; // From useManifest().getEntityAssetSummary()
compact?: boolean; // Use compact mode (default: false)
}Display modes:
- Full mode:
"2 images, 1 video, 3 audio (14 MB)"— Shows breakdown by type - Compact mode:
"6 assets (14 MB)"— Shows total count only - Empty state:
"No assets"— When entity has no assets
Example usage:
import { useManifest } from '@/hooks/useManifest';
import { EntityAssetIndicator } from '@/components/assets/EntityAssetIndicator';
function EntityListView({ entityType, entityId }) {
const { getEntityAssetSummary } = useManifest();
const summary = getEntityAssetSummary(entityType, entityId);
return (
<div className="entity-row">
<h3>Entity Name</h3>
<EntityAssetIndicator summary={summary} />
</div>
);
}Related:
- useManifest Hook — Fetches manifest and provides asset summary
- Manifest API — Backend REST API for manifest data
useManifest Hook
Location:
city-brains-studio/src/hooks/useManifest.tsReact hook that fetches and caches the project manifest.
Fetches the manifest from /api/manifest on mount and provides computed data for entity browsing.
Returns:
interface UseManifestResult {
manifest: ProjectManifest | null;
isLoading: boolean;
error: string | null;
entries: ManifestEntry[];
getEntityEntries: (entityType: string, entityId: string) => ManifestEntry[];
getEntityAssetSummary: (entityType: string, entityId: string) => EntityAssetSummary | null;
entityIndex: Record<string, string[]>; // type -> [id, id, ...]
refresh: () => Promise<void>;
lastRefreshed: Date | null;
manifestAge: number | null; // seconds since generation
}Features:
- Auto-refresh on WebSocket events — Listens for
manifest_changedevents - Periodic polling — 60-second polling in cloud mode (skipped in desktop mode)
- Manual refresh — Call
refresh()to trigger immediate server-side refresh - Computed indices — Entity index and asset summaries are memoized
Example usage:
import { useManifest } from '@/hooks/useManifest';
function EntityBrowser() {
const { manifest, entityIndex, getEntityAssetSummary, refresh, isLoading } = useManifest();
if (isLoading) return <div>Loading manifest...</div>;
return (
<div>
<button onClick={refresh}>Refresh Manifest</button>
<p>Manifest generated: {new Date(manifest?.generated_at || '').toLocaleString()}</p>
{Object.entries(entityIndex).map(([type, ids]) => (
<details key={type}>
<summary>{type} ({ids.length})</summary>
{ids.map(id => {
const summary = getEntityAssetSummary(type, id);
return (
<div key={id}>
<a href={`/entities/${type}/${id}`}>
{id}
</a>
{summary && <EntityAssetIndicator summary={summary} />}
</div>
);
})}
</details>
))}
</div>
);
}Schema types:
| Type | Description |
|---|---|
ProjectManifest | Full manifest (project_id, entries, provider_type, etc.) |
ManifestEntry | Single entry (relative_path, entity_type, size_bytes, etc.) |
EntityAssetSummary | Computed summary (image_count, video_count, total_size_bytes, etc.) |