# Demo authoring guide This document is the canonical reference for authoring component demo pages under `web/routes/uix/components/{name}/+page.svelte`. The template is **locked** — every demo must follow it. Per-component creativity is forbidden; the only thing that varies is the data the component declares. The canary template is the **drawer** demo — `web/routes/uix/components/drawer/+page.svelte`. When in doubt, copy from drawer and adapt. ## 1. File location + frame Every demo lives at `web/routes/uix/components/{kebab}/+page.svelte`. The file is wrapped by `web/routes/uix/+layout@.svelte` (the `@` resets intermediate layouts) so the chrome — top bar, sidebar rail, canvas frame — is provided. The demo only renders the canvas inner. The outermost element of every demo is: ```svelte
…
``` ## 2. Required imports ```ts import { X, type XSize, type XVariant, // …other public types } from '$uix/eidos/components/{name}'; import { compileMorfo } from '$uix/morfo'; import { xMorfo } from '@/uix/morfo/components/{name}'; import { getActiveUix } from '$active-uix'; const uix = getActiveUix(); ``` Multi-instance components (toast) also import `createToaster` (or the analogous instance constructor). Eidos-only components (icon, avatar) omit `getActiveUix` since they have no Sema tab. ## 3. Tabs Six tabs in this order: ``` Live · API · [morfo badge] Np·Me · [sema badge] N · Recipe · A11y ``` The Sema tab is **dropped** when the morfo declares 0 events (tooltip, icon, avatar). The badge counters use `partsList.length` and `events.length`. Tab union: ```ts type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; let tab = $state('live'); ``` There is **no** `Translates` tab and **no** `Anatomy` tab — both were inventions that have been removed. ## 4. Header ```svelte
{Category} · {Name}

{Name}

{one-paragraph summary — what the component does, how many parts and events, anything notable about its semantic shape}

parts{compiled.parts.order.length} events{events.length}
``` ## 5. Live preview is always rendered The stage sits **between** the header and the tablist, not inside the Live tab. This is critical so that: - The Sema tab's `▶ play` buttons can fire on a real instance. - The trace strip stays visible across tab switches. ```svelte
…
trace {#if trace.length === 0} {instructional copy — "open the X to see events"} {:else} {#each trace.slice(0, 3) as entry} {entry.event} · {entry.family}{entry.intent ? ' · ' + entry.intent : ''} {fmtTime(entry.at)} {/each} {/if} {state-key} {String(stateValue)}
``` ## 6. MutationObserver trace Watch `data-event` on the stage subtree. The runtime stamps it via the sema engine before each event fires: ```ts type TraceEntry = { event: string; family: string; intent?: string; at: number }; let trace = $state([]); let stageRef = $state(null); $effect(() => { const el = stageRef; if (!el) return; const obs = new MutationObserver((mutations) => { for (const m of mutations) { if (m.attributeName !== 'data-event') continue; const target = m.target as Element; const ev = target.getAttribute('data-event'); if (!ev) continue; trace = [ { event: ev, family: target.getAttribute('data-event-family') ?? '—', intent: target.getAttribute('data-event-intent') ?? undefined, at: Date.now() }, ...trace ].slice(0, 6); } }); obs.observe(el, { attributes: true, subtree: true, attributeFilter: ['data-event'] }); return () => obs.disconnect(); }); ``` For multi-instance components (toast Item) the observer still works: each Item gets its own stamps because the runtime is per-instance. ## 7. Live tab — per-architectural-layer subsections The Live tab groups controls by the layer that owns them. **There is no `[sema]` subsection in Live** — sema events live in their dedicated Sema tab. ``` Controls ── intro paragraph: layer-grouped, links to Sema tab for events ── [soma] props · headless behavior (subsection) ── [eidos] props · visual treatment (subsection) ── (additional groups: Group / Item / Demo content / Header etc.) ── [soma] somaSnippet pre/code (reactive $derived) ── [eidos] eidosSnippet pre/code (reactive $derived) ``` Subsection headings carry layer badges: ```svelte
soma props · headless behavior
``` Both code snippets are at the bottom of the Live tab, in this order: soma first, eidos second. Each in its own `data-uix-code` block with `data-uix-code-head` showing the layer badge + a one-line caption + language tag. ```svelte
soma headless · ARIA + behavior only svelte
{somaSnippet}
``` ## 8. Snippet derivation pattern ```ts const somaSnippet = $derived( [ "