DeveloperComponent Blocks Reference

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

PropertyValue
Categorymedia
Modesedit, view
Sourcebuiltin

Full asset browser with upload, primary image selection, and asset role management.

Config Options:

  • title (string, default: “Entity Assets”) — Section heading
  • showUpload (boolean, default: true) — Show upload controls

entity-timeline

PropertyValue
Categorydisplay
Modesedit, view
Sourcebuiltin

Timeline event panel showing entity history with auto-populate capability.

Config Options:

  • compact (boolean, default: true) — Use compact display mode
  • showAutoPopulate (boolean, default: true) — Show auto-populate button

production-status

PropertyValue
Categoryworkflow
Modesedit, view
Sourcebuiltin

Production pipeline status editor showing general/game/TV status with inline save.

Config Options: None (uses entity’s production_status field directly)


markdown-content

PropertyValue
Categorydisplay
Modesedit, view
Sourcebuiltin

Rendered markdown body content using MarkdownRenderer component.

Config Options:

  • field (string, default: “markdown_body”) — Which entity field contains the markdown

primary-image

PropertyValue
Categorymedia
Modesedit, view
Sourcebuiltin

Hero/primary image display with placeholder fallback when no image is set.

Config Options:

  • field (string, default: “primary_image”) — Entity field containing the image path
  • height (string, default: “300px”) — Display height

entity-chat

PropertyValue
Categoryinteraction
Modesedit
Sourcebuiltin

AI-powered entity chat panel for conversational editing of entity fields.

Config Options:

  • visibility (select: team/private, default: “team”) — Chat session visibility

entity-relationships

PropertyValue
Categorydisplay
Modesedit, view
Sourcebuiltin

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

PropertyValue
Categoryworkflow
Modesedit
Sourcebuiltin

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

TypeRenders AsExample
stringText inputtype: 'string', label: 'Title', default: 'Assets'
numberNumber inputtype: 'number', label: 'Max Items', default: 10
booleanToggle checkboxtype: 'boolean', label: 'Show Upload', default: true
selectDropdowntype: '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.tsx

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

Location: city-brains-studio/src/hooks/useManifest.ts

React 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_changed events
  • 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:

TypeDescription
ProjectManifestFull manifest (project_id, entries, provider_type, etc.)
ManifestEntrySingle entry (relative_path, entity_type, size_bytes, etc.)
EntityAssetSummaryComputed summary (image_count, video_count, total_size_bytes, etc.)