# Layout Hints

# Layout hints reference

Layout hints are an optional declarative config attached to a registered artifact type. The frontend AutoRenderer reads them and produces a structured layout from the design library primitives instead of falling back to schema-driven defaults.

Hints live alongside the artifact-type registration. The frontend's `@amdahl/artifact-renderers` package consumes them. Since they are pure data on the registry, every consumer (LibraryPage, MCP resource catalog) sees the same hints.

## Why use hints

The AutoRenderer can render any registered Zod schema without hints, but the result is a schema-walking dump (every field rendered top-to-bottom in declaration order). Hints let you:

- Group related fields into named sections.
- Choose a richer primitive than the default for a given field (e.g. render a `quote` + `speaker` pair as `<QuoteCluster>` instead of two separate `<Body>` blocks).
- Show child artifacts, edges, or events as inline sections.
- Configure filter chips on multi-item sections.
- Customize the header (title field, badge fields, subtitle field).
- Choose what shows in the right sidebar.

Hints are reviewable in PR diffs (no React drift), schema-validated, and zero-cost to update - change the hints, every renderer of that type updates on next mount.

## The shape

```ts
interface LayoutHints {
  sections?: SectionHint[]
  sidebar?: {
    show?: ('edges' | 'events' | 'versions' | 'metadata')[]
    sections?: SectionHint[]
  }
  header?: {
    titleField?: string // default: 'title' or 'name' or first string field
    badges?: string[] // default: ['status', 'schema_version']
    subtitleField?: string
  }
}

interface SectionHint {
  title?: string
  fields?: string[] // empty = all fields
  source?: 'content' | 'children' | 'edges' | 'events' // default 'content'
  childArtifactType?: string // filter for source='children'
  edgeType?: string // filter for source='edges'
  edgeDirection?: 'from' | 'to' // default 'from'
  render: RenderPrimitive | { primitive: RenderPrimitive; props?: Record<string, unknown> }
  filterChips?: string[] // expose chip filter on multi-item sections
  span?: 'full' | 'half' | 'third' // default 'full'
}
```

## RenderPrimitive values

Each value maps to a component from `@amdahl/ui` or to a renderer-package internal:

| Primitive              | Source                    | Use for                                     |
| ---------------------- | ------------------------- | ------------------------------------------- |
| `Headline`             | @amdahl/ui/typography     | Large headers, artifact titles              |
| `Subheadline`          | @amdahl/ui/typography     | Section subtitles                           |
| `Title`                | @amdahl/ui/typography     | Card / panel titles                         |
| `Display`              | @amdahl/ui/typography     | Hero text                                   |
| `Body`                 | @amdahl/ui/typography     | Short prose, max ~280 chars                 |
| `Lead`                 | @amdahl/ui/typography     | Lead paragraphs                             |
| `Mono`                 | @amdahl/ui/typography     | IDs, timestamps, code-style                 |
| `MonoLabel`            | @amdahl/ui/typography     | All-caps mono labels                        |
| `Badge`                | @amdahl/ui/data-display   | Statuses, enum values                       |
| `Tag`                  | @amdahl/ui/data-display   | Categorical labels                          |
| `TagCloud` / `TagList` | @amdahl/ui/data-display   | Array of strings as tag cloud               |
| `Stat`                 | @amdahl/ui/data-display   | Metrics, counts, percentages                |
| `Avatar`               | @amdahl/ui/data-display   | User images / initials                      |
| `Mention`              | @amdahl/ui/data-display   | UUID -> referenced artifact preview         |
| `Citation`             | @amdahl/ui/typography     | Inline source attribution                   |
| `Code`                 | @amdahl/ui/data-display   | Code snippets                               |
| `MarkdownView`         | renderer-package internal | Long-form markdown body                     |
| `JsonView`             | renderer-package internal | Raw JSON tree fallback                      |
| `DefinitionList`       | @amdahl/ui/data-display   | Field/value pairs                           |
| `AssetCard`            | @amdahl/ui/data-display   | Single artifact card                        |
| `AssetCardList`        | renderer-package internal | List of artifact cards (one per array item) |
| `QuoteCluster`         | @amdahl/ui/data-display   | `{quote, speaker, ...}` block               |
| `DiscoveryCard`        | @amdahl/ui/data-display   | Citation / source card                      |
| `Insight`              | @amdahl/ui/data-display   | Highlighted callout                         |
| `DateField`            | renderer-package internal | Formatted datetime                          |
| `Link`                 | @amdahl/ui/typography     | URL with link styling                       |

Add new primitives by adding the `RenderPrimitive` enum value + a thin adapter in `packages/artifact-renderers/src/_internal/primitives/`. New primitives ship as platform-level changes (one design library file + one enum entry).

## Default behavior (no hints)

When `layoutHints` is absent, the AutoRenderer applies a schema-driven default:

- **Header** uses field named `title` or `name` (or the first string field) as title; renders `status` and `schema_version` as badges.
- **Main column** walks the Zod schema in declaration order and picks a primitive per field via the [zod-to-primitive mapping](#zod-to-primitive-mapping).
- **Sidebar** shows edges + events.

This produces a competent rendering for any registered type. Polish via hints.

## Zod-to-primitive mapping

Default primitive picks when no hint overrides them:

| Zod shape                              | Default primitive        |
| -------------------------------------- | ------------------------ |
| `z.string().min(1).max(280)`           | `Body`                   |
| `z.string()` longer                    | `MarkdownView`           |
| `z.string().datetime()`                | `DateField`              |
| `z.string().uuid()`                    | `Mention`                |
| `z.string().url()`                     | `Link`                   |
| `z.enum([...])`                        | `Badge`                  |
| `z.number()`                           | `Stat`                   |
| `z.boolean()`                          | `Badge` (Yes/No variant) |
| `z.array(z.string())`                  | `TagList`                |
| `z.array(z.object(...))`               | `AssetCardList`          |
| `z.object(nested)`                     | `DefinitionList`         |
| `z.literal(...)`                       | `Mono`                   |
| `z.record()`, `z.any()`, `z.unknown()` | `JsonView`               |

Implementation lives at `packages/artifact-renderers/src/_internal/zodToPrimitive.ts`.

## Worked examples

### Customer post (legacy compat type)

```ts
layoutHints: {
  sections: [
    { title: 'Quote', fields: ['body'], render: 'QuoteCluster' },
    { title: 'Source', fields: ['url', 'published_at', 'author', 'company'], render: 'DefinitionList' },
  ],
  sidebar: { show: ['edges', 'events'] },
}
```

### Battlecard (Tier 1 strict)

```ts
layoutHints: {
  header: {
    titleField: 'competitor_name',
    badges: ['status'],
    subtitleField: 'competitor_url',
  },
  sections: [
    { title: 'Positioning claims', fields: ['positioning_claims'], render: 'AssetCardList', filterChips: ['evidence_id'] },
    { title: 'Objection handlers', fields: ['objection_handlers'], render: 'DefinitionList' },
  ],
  sidebar: { show: ['edges', 'events'] },
}
```

### Wave plan (admin / debug oriented)

```ts
layoutHints: {
  sections: [
    { title: 'Plan', fields: ['name', 'description'], render: 'DefinitionList' },
    { title: 'Waves', fields: ['waves'], render: 'AssetCardList' },
    { title: 'Dispatches', source: 'children', childArtifactType: 'agent_run', render: 'AssetCardList', filterChips: ['profile_id', 'status'] },
  ],
  sidebar: { show: ['events'] },
}
```

## Filter chips

When a section's `filterChips` array is set, the AutoRenderer generates `<DropdownMenu>` chips above the section. Each chip maps to a field; clicking filters the displayed items. State is local to the renderer (no URL persistence at this layer; library-level filters use URL params).

## Section span

Set `span: 'half'` or `span: 'third'` to lay out sections in a multi-column grid. Default `'full'` gives one section per row.

## See also

- [Artifact registry architecture](../../artifact-registry-architecture.md) for the registry boundary, origins, edges, events.
- [Artifact types catalog](../../artifact-types-catalog.md) for the live list of types and their declared hints.
