CSF → EDU Schema → Puck Config → DSL Config
The Configurable Experiences pipeline converts HEN UI component definitions into operator-composable experiences without code changes or deployments. The Component Catalog (EDU Schema) is the canonical owned contract — Puck Config is derived from it at runtime, never authored directly.
SAD §4 ADR-1 (Puck adoption) · ADR-2 (Catalog as single owned contract) · SAD §5.1 Component Onboarding Pipeline · SAD §5.4 Runtime Rendering
hen-ui/*.stories.tsxThe source of truth for HEN UI component metadata. Each story file includes parameters.edu (EduCSFParams) with componentId, displayName, description, category, slots, and argTypes for prop definitions. Written by HEN UI developers.
// composites/Flex/Flex.stories.tsx
import type { Meta } from '@storybook/react';
import type { EduCSFParams } from '@/hen-ui/types/edu-csf';
import { Flex } from './Flex';
const meta: Meta<typeof Flex> = {
title: 'HEN/Layout/Flex',
component: Flex,
parameters: {
edu: {
componentId: 'hen.flex',
displayName: 'Flex',
description: 'Flexbox layout for vertical or horizontal arrangements.',
category: 'layout',
excludeProps: ['asChild', 'className', 'ref'],
slots: { children: {} },
} satisfies EduCSFParams,
},
argTypes: {
direction: {
control: 'select',
options: ['column', 'row'],
description: 'Flex direction',
},
gap: {
control: 'select',
options: ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'],
description: 'Spacing between children',
},
},
};csf-to-edu-catalog.ts (build-time parser)ComponentCatalogEntryThe canonical owned contract. Serializable JSON — Puck-independent. Generated by the CSF parser, published to EDU-Events (S3) in production. Consumed by the Runtime Adapter, the LLM prompt builder, and Layer 1 validation. Types live in src/lib/catalog/types.ts.
{
"id": "hen.flex",
"exportName": "Flex",
"displayName": "Flex",
"description": "Flexbox layout for vertical or horizontal arrangements. Use for stacking content (column), button groups (row), or tag lists. Supports gap, wrap, align, and justify.",
"category": "layout",
"props": {
"direction": {
"type": "enum",
"description": "Flex direction",
"required": false,
"enumValues": [
"column",
"row"
]
},
"gap": {
"type": "enum",
"description": "Spacing between children",
"required": false,
"enumValues": [
"xxs",
"xs",
"sm",
"md",
"lg",
"xl",
"2xl",
"3xl"
]
},
"wrap": {
"type": "enum",
"description": "Whether items should wrap",
"required": false,
"enumValues": [
"wrap",
"nowrap"
]
},
"align": {
"type": "enum",
"description": "Cross-axis alignment (items-*)",
"required": false,
"enumValues": [
"start",
"center",
"end",
"stretch"
]
},
"justify": {
"type": "enum",
"description": "Main-axis alignment (justify-*)",
"required": false,
"enumValues": [
"start",
"center",
"end",
"between"
]
}
},
"slots": {
"children": {}
}
}runtime-adapter.tsx → createPuckConfig()createPuckConfig(catalog, renderMap)A runtime TypeScript object derived from the Catalog by the Runtime Adapter (src/lib/catalog/runtime-adapter.tsx). Maps prop types to Puck field types, resolves slots, wires React components from the hen-ui barrel export. Never stored — always derived at runtime.
Puck Editor → operator composes → onPublish*.dsl.json → saved-configs/The operator-authored experience. Pure JSON describing a composed UI. Stored in DynamoDB in production (dsl#entry# / dsl#result# SK prefixes). Consumed by Puck <Render> at runtime in site-publishing. This is the SAD §10.2 DSL Config Storage Schema.
| Component | ID | Category | Props | Slots | Events |
|---|---|---|---|---|---|
| Avatar | hen.avatar | content | 5 | — | — |
| Badge | hen.badge | content | 3 | — | — |
| Bar | hen.bar | content | 3 | — | — |
| Brow | hen.brow | content | 3 | — | — |
| Button | hen.button | form | 7 | — | — |
| Checkbox | hen.checkbox | form | 5 | — | — |
| Heading | hen.heading | content | 3 | — | — |
| Icon | hen.icon | content | 6 | — | — |
| Image | hen.image | content | 6 | — | — |
| Input | hen.input | form | 8 | — | — |
| Label | hen.label | form | 5 | children | — |
| Link | hen.link | form | 5 | — | — |
| List | hen.list | content | 2 | children | — |
| List Item | hen.listItem | content | 1 | — | — |
| Loading Dots | hen.loadingDots | content | 1 | — | — |
| Navigation Wrapper | hen.navigationWrapper | navigation | 2 | children | — |
| Progress | hen.progress | content | 3 | — | — |
| Radio | hen.radio | form | 3 | — | — |
| Select | hen.select | form | 6 | children | — |
| Separator | hen.separator | content | 2 | — | — |
| Spinner | hen.spinner | content | 1 | — | — |
| Switch | hen.switch | form | 4 | — | — |
| Text | hen.text | content | 5 | — | — |
| Brand Card | hen.brandCard | content | 0 | children | — |
| Card | hen.card | content | 1 | children | — |
| Carousel | hen.carousel | content | 2 | children | — |
| Checkbox Button | hen.checkboxButton | form | 4 | — | — |
| Data Point | hen.dataPoint | content | 8 | — | — |
| Drawer | hen.drawer | content | 3 | children | — |
| Feedback Box | hen.feedbackBox | content | 3 | children | — |
| Flex | hen.flex | layout | 5 | children | — |
| Footer | hen.footer | content | 0 | children | — |
| Grid | hen.grid | layout | 4 | children | — |
| Hero | hen.hero | content | 4 | children | — |
| Image Card | hen.imageCard | content | 5 | children | — |
| Image Grid | hen.imageGrid | content | 3 | children | — |
| Info Card | hen.infoCard | content | 3 | — | — |
| Navbar | hen.navbar | content | 4 | — | — |
| Page Section | hen.pageSection | layout | 4 | children | — |
| Property Value | hen.propertyValue | content | 5 | — | — |
| Section | hen.section | layout | 5 | children | — |
| Section Header | hen.sectionHeader | content | 5 | — | — |
| Simple Data Point | hen.simpleDataPoint | content | 5 | — | — |
| Split | hen.split | layout | 4 | children | — |
| Stat Styles | hen.statStyles | content | 1 | — | — |
| Sticky Column | hen.stickyColumn | layout | 2 | children | — |
| Timeline | hen.timeline | content | 1 | children | — |
| Tooltip | hen.tooltip | content | 2 | children | — |
| Value Prop | hen.valueProp | content | 2 | children | — |
| Reference Card | hen.referenceCard | content | 5 | children, footer | 2 |
| Reference Card Header | hen.referenceCardHeader | content | 2 | — | — |
src/lib/catalog/types.ts · SAD §10.1
interface ComponentCatalogEntry {
id: string; // "hen.camelCaseName"
exportName: string; // barrel export name
displayName: string; // editor sidebar label
description: string; // LLM semantic search
category: ComponentCategory;
props: Record<string, CatalogPropDefinition>;
slots?: Record<string, CatalogSlotDefinition>;
defaultProps?: Record<string, unknown>;
events?: Record<string, CatalogEventDefinition>;
}
type ComponentCategory =
| 'layout' | 'content' | 'form'
| 'navigation' | 'data';
type CatalogPropType =
| 'string' | 'number' | 'boolean' | 'enum';DynamoDB · SAD §10.2
// DynamoDB Storage Schema
PK: tenantId
SK: dsl#entry#uuid | dsl#result#uuid
Puck Data JSON:
{
content: Array<{
type: string; // ComponentCatalogEntry.exportName
props: Record<string, unknown>;
}>;
root: { props: { title?: string } };
zones: Record<string, unknown>;
}
// Status lifecycle
status: "draft" → "published"
// Published configs are immutable (MVP)hen-ui/src/types/edu-csf.ts · CSF authoring contract
interface EduCSFParams {
componentId: string; // "hen.camelCaseName"
displayName: string;
description: string; // LLM-targeted
category: EduComponentCategory;
excludeProps?: string[]; // e.g. ['asChild','ref']
slots?: Record<string,
EduSlotDefinition>; // named slot definitions
propOverrides?: Record<string,
EduPropOverride>;
events?: Record<string,
string>; // event: description
subComponents?: Record<string,
EduSubComponent>;
}src/lib/catalog/runtime-adapter.tsx · SAD §7
// Converts ComponentCatalog → Puck Config
// Never stored — always derived at runtime
function createPuckConfig(
catalog: ComponentCatalog,
renderMap: Record<string, ComponentType>
): HenPuckConfig {
// 1. Map CatalogPropDefinition → Puck field type
// string → text | textarea
// enum → select
// boolean → radio (Yes/No)
// number → number
// 2. Map CatalogSlotDefinition → Puck slot field
// 3. Wire render function from barrel export
// 4. Group by category for editor sidebar
}§4 ADR-1Adopt Puck as composition engine§4 ADR-2Catalog as single owned contract§4 ADR-3Tenant-themed preview via iframe CSS injection§4 ADR-4LLM via Inference Enabler (Pattern B)§5.1Component Onboarding Pipeline (build-time)§5.2Experience Authoring (operator-time)§5.4Runtime Rendering (user-time)§7Library/Package Design — createPuckConfig()§10.1Component Catalog Entry Schema (v1)§10.2DSL Config Storage Schema (DynamoDB)§17 Phase 1Foundation: catalog → render pipeline§17 Phase 2Author-time: Puck editor + tenant theme