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: none — this component was removed from the source tree during the
studiobrain-coreopen-source cleanup and no longer exists. The section below is retained as historical reference.Displays 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:
studiobrain-core/shared/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 '@studiobrain/shared';
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.) |