You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/web/routes/uix/lib/DEMO_AUTHORING_GUIDE.md

592 lines
22 KiB

# 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
<div data-uix-canvas-inner>…</div>
```
## 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). Components with 0 semantic events still
render the Sema tab; the tab states that Morfo declares no events. A
0-event demo is only valid after the component README explicitly justifies
why the component is passive or why its actions do not belong to Sema. Those
pages do not need `getActiveUix` unless they offer interactive playback.
## 3. Tabs
Six tabs in this order:
```
Live · API · [morfo badge] Np·Me · [sema badge] N · Recipe · A11y
```
The Sema tab is never hidden. When the morfo declares 0 events, render
an explicit empty state instead of making the absence invisible. Do not
assume 0 events is correct because the current morfo has no `events` array:
the component must first pass the Morfo/Sema audit in
`src/uix/eidos/components/{name}/README.md`. The badge counters use
`partsList.length` and `events.length`.
Tab union:
```ts
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
let tab = $state<Tab>('live');
```
There is **no** `Translates` tab and **no** `Anatomy` tab — both were
inventions that have been removed.
## 4. Header
```svelte
<header>
<div data-uix-eyebrow>{Category} · {Name}</div>
<h1 data-uix-page-title>{Name}</h1>
<p data-uix-page-lede>
{one-paragraph summary — what the component does, how many parts
and events, anything notable about its semantic shape}
</p>
<div data-uix-page-meta>
<span data-uix-meta-pill>
<span data-uix-meta-key>parts</span>{compiled.parts.order.length}
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>events</span>{events.length}
</span>
<!-- additional pills: variants, sizes, intents, apg link, scope -->
</div>
</header>
```
## 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
<div data-uix-stage>
<div data-uix-stage-area bind:this={stageRef}>
<X bind:open …>
…
</X>
</div>
<div data-uix-stage-trace>
<span data-uix-stage-trace-key>trace</span>
{#if trace.length === 0}
<span>{instructional copy — "open the X to see events"}</span>
{:else}
{#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each}
{/if}
<span style="margin-inline-start: auto;">
<span data-uix-stage-trace-key>{state-key}</span> {String(stateValue)}
</span>
</div>
</div>
```
## 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<TraceEntry[]>([]);
let stageRef = $state<HTMLElement | null>(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
<div data-uix-subsection-head>
<span data-uix-layer-badge="soma">soma</span> props · headless behavior
</div>
```
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
<div data-uix-code>
<div data-uix-code-head>
<span data-uix-layer-badge="soma">soma</span>
<span>headless · ARIA + behavior only</span>
<span data-uix-code-lang>svelte</span>
</div>
<pre><code>{somaSnippet}</code></pre>
</div>
```
## 8. Snippet derivation pattern
```ts
const somaSnippet = $derived(
[
"<script lang='ts'>",
" import * as X from '$soma/components/{name}';",
' let value = $state(...);',
'</' + 'script>',
'',
'<X.Provider …>',
' …',
'</X.Provider>'
]
.filter(Boolean)
.join('\n')
);
```
Two non-obvious rules:
- **`'</' + 'script>'` is split** to prevent Svelte's parser from
closing the demo's own `<script>` block.
- **`.filter(Boolean)`** lets you write `someProp && ' someProp'`
inline — `false` entries drop out cleanly so the rendered snippet
only shows the props the consumer actually passed.
### 8.1 Snippet parity is mandatory
The snippets are part of the demo contract. They must match the live preview's
real API surface and data contract:
- If the live preview renders a field/part, the snippet must render it too.
- If the live preview uses a schema, the snippet must declare the same visible
fields, imports, validators and defaults. Do not show a reduced schema while
the preview validates extra fields.
- If a control changes a prop (`size`, `variant`, `validationBehaviour`,
`disabled`, etc.), the snippet must reflect the active value.
- If a snippet is intentionally minimal, the live preview must also be minimal
or the snippet caption must say it is a separate minimal example. Component
demos should prefer parity over abbreviated examples.
For form demos specifically:
- Real-time validation is `validationBehaviour: 'onChange'`.
- A demo that starts in `onChange` must use initially valid defaults unless it
explicitly documents that it is demonstrating an initially invalid form.
- `Form.AutoFields` examples must keep SIUM metadata, fields and defaults in
sync with the manual `Form + Field` example.
## 9. Sema tab — events + interactive playback
Header with `<span data-uix-layer-badge="sema">sema</span>`. One table:
`name / family / verb / sequence / intent / play`. The play button
emits via the active uix's `EngineSemantic` onto the real DOM target:
```svelte
<button
data-uix-play
onclick={() => {
const target = (
stageRef?.querySelector('[data-{component}-{eventTargetPart}]')
?? stageRef?.querySelector('[data-{component}-{fallbackPart}]')
?? stageRef
) as HTMLElement | null;
if (!target) return;
void uix.events?.emit({
name: action.name,
family: action.semantic.family,
target,
...(effectiveIntent ? { intent: effectiveIntent } : {})
});
}}
>▶ play</button>
```
`effectiveIntent` resolves the morfo's intent declaration:
```ts
{@const intentDecl = 'intent' in action.semantic ? action.semantic.intent : undefined}
{@const effectiveIntent = typeof intentDecl === 'string'
? intentDecl
: (intentDecl ? intentBoundProp : undefined)}
```
(Static literal → use it; fromProp binding → use the demo's local
prop value; absent → don't pass intent.)
## 10. Morfo tab — declarative contract
Sections in this order:
1. **Header table**: name / kebab / scope / apg / parts.length / events.length
2. **Parts overview table**: kebab / marker / element / role / archetype / states / optional
3. **Per-part subsection** (only when the part declares any data / aria / keyboard)
- data-attrs table: attr / values / source kind
- aria-attrs table: attr / source kind / condition / severity
- keyboard table: key / action — key wrapped in `<span data-uix-kbd>`
4. **Events declaration table**: name / family / verb / sequence / intent / target / prewrite / commit
Iterate raw morfo (`xMorfo.parts`, `xMorfo.events`) for declaration
data — NOT `compiled` everywhere. The compiler exposes
`compiled.contracts.dataAttrsByPart.get(kebab)` for data-attrs only.
ARIA + keyboard come from raw morfo. Cast for type narrowing inside
the each block:
```svelte
{@const partAny = rawPart as unknown as {
kebab: string;
aria?: ReadonlyArray<{
attr: string;
value: { kind: string };
condition?: { when: string; prop?: string; part?: string };
severity?: string;
}>;
keyboard?: ReadonlyArray<{ key: string; action: string }>;
}}
```
When the morfo has no events (icon, tooltip), hardcode `<td>0</td>`
for `events.length` — `xMorfo.events?.length` TypeScript-errors
because the property doesn't exist on the const-narrowed type when
omitted.
## 11. Recipe + A11y tabs
Brief tables.
- **Recipe** = list of selectors with morfo / eidos source tag —
every selector is classified as `morfo`-backed or `eidos`-only.
- **A11y** = keyboard table + ARIA contract table. For overlay
components also include intent → role / aria-live mapping when the
morfo derives them.
## 12. Critical pitfalls
### 12.1 `<Foo>` element name in `<p>` parses as HTML
Writing ``Toggle is single-part — `<Toggle>` IS the button.`` inside
a `<p>` produces a Svelte parse error — it tries to close the `<p>`
because `<Toggle>` looks like an element. Use `&lt;Toggle&gt;` instead.
### 12.2 Curly braces inside literal text
Svelte interprets `{ … }` in template text as a JavaScript
expression. Wrap in template literal expression:
```svelte
<!-- bad -->
<td class="type">Snippet&lt;[{ checked: boolean }]&gt;</td>
<!-- good -->
<td class="type">{`Snippet<[{ checked: boolean }]>`}</td>
```
### 12.3 Object.assign on Svelte component constructors
Bulk `Object.assign(Root, { Trigger, Content, … })` causes a Svelte 5
hydration glitch where children re-mount on hydration (button appears
then disappears). The disciplined fix in `eidos/components/*/index.ts`
is **explicit per-property assignment** — see
`src/uix/eidos/components/README.md` rule #7 and the drawer
`index.ts` for the canonical pattern. Until every eidos package
adopts it, watch for this when adding new compound parts.
### 12.4 Children snippet self-shadow
When a wrapper renders `<Provider>{@render children?.()}</Provider>`,
the `children` prop must NOT be re-declared as `{#snippet children}`
in the same scope — it shadows the prop and recurses. The drawer
wrapper uses `let { children, ...rest } = $props()` and renders
directly.
### 12.5 Live state vs at-construction reads
Constructors that capture local state at mount time (e.g.
`createToaster({ duration, max, … })`) snapshot the **initial** value
of those `$state()` props. Subsequent slider changes do not re-create
the toaster. Either:
- accept the snapshot semantics and document it, or
- recreate the instance via a `$derived` (forces a rebuild on every
change — usually not desirable for stateful instances), or
- expose runtime config setters on the instance.
Toast demo accepts the snapshot semantics (the warning is benign).
uix: date-picker + date-range-picker components + component audit infra Two new Eidos components shipped end-to-end (wrapper + recipe + demo + Soma provider hardening) plus a checklist-driven audit pipeline that scores all 67 morfo components against doctrinal completion criteria. New components: - date-picker: full popover-anchored picker over date-field + calendar, with calendar/content/trigger parts and demo route. - date-range-picker: standalone wrapper with own calendar/grid/segment surface, demo route, and recipe CSS. - Both wrappers follow Option C disciplined (root + parts attached via explicit assignment, no Object.assign). Supporting Soma changes: - range-calendar provider tightened (211 LOC of behavior, 167 LOC of tests), README brought up to component doctrine. - date-field, date-picker, date-range-field, date-range-picker Soma providers + READMEs updated for new wrappers. - popover provider/close gain props needed by the picker wrappers. Morfo updates: - date-picker / date-range-picker / range-calendar morfos refined for the new APIs (parts, events, ARIA). Audit infrastructure (new): - src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across morfo / eidos wrapper / recipe CSS / demo / README / cross-layer scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION and DEMO_AUTHORING_GUIDE. - scripts/component-audit.ts + `npm run component:audit` — regex parser over all 67 components, emits tmp/component-audit.md with summary scoreboard + per-component findings. Validates against canonical SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS. - Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip, date-range-picker). Top systemic gaps: translations.label (49), README Gaps/Comparativa/Baseline sections (87 combined), keyboard /event ratio under-declaration (15), apg URL absent (19). Misc: - src/uix/kimi-audit-eidos.md — supplementary audit notes. - .gitignore: ignore .codex-* agent scratch artifacts at repo root. - continue.md + READMEs updated through the migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### 12.6 Visible controls and Morfo-owned attrs
Do not expose a control that has no visible effect in the live preview. If a
demo has `segments`, `paged navigation`, `modal`, `clear`, `min`, `max`,
`minDays` or `maxDays`, the stage must make the effect observable immediately.
Otherwise remove the control or mark it as a separate non-preview API note.
Demos must not hand-stamp required `data-*` attrs to simulate missing component
parts. If a selector is needed for Calendar, RangeCalendar, DatePicker or
DateRangePicker styling, declare the part/data attr in Morfo/Soma first and let
the component emit it. Manual attrs in the demo are only acceptable for the UIX
docs shell itself (`data-uix-*`).
eidos: theming fixes + size/variant parity batch + docs - theme: add surface.muted + content.muted to color contract (neutral-3 + neutral-10). Plugs 17 broken --color-content-muted and 3 broken --color-surface-muted references in recipes/components. - form.css: fix --color-neutral-element-hover typo → --color-neutral-hover. - archetypes.css + events.css: replace raw hsl/rgba indigo with color-mix(var(--color-primary-solid) …) — no raw colors left in eidos. - combobox dark scrollbar: unscope ::-webkit-scrollbar rules in uix.css and duplicate --uix-line on :root + :root[data-mode='dark'] so portaled overlays (Combobox listbox, Popover, Dialog, Drawer) inherit the theme. - sizes: 20 components expand from sm/md/lg to xs..xl (form controls, text inputs, progress/meter, field/form) or xs..lg (nav controls: breadcrumb, pagination, tag-group, toolbar). Composite panels keep sm/md/lg deliberately. - variants: field + toolbar drop arbitrary ControlVariant narrowings; both expose all 3 (surface | outline | ghost) with new outline CSS. - pagination demo: disambiguate siblingCount/boundaryCount as "per side" in label + API table (Radix/MUI convention). - docs: CHECKLIST §C-2.6 (contract token presence) + §D-7.4 (chip parity) + §D-7.5 (size category) added. DEMO_AUTHORING §12.7 (chip parity) + §12.8 (size category cheatsheet) added. eidos/README + active_architecture.md sync texts/migration nomenclature. - PENDIENTES.md: normas N-1..N-5 implantadas en esta sesión. Verification: 88/88 eidos tests, 0 type errors, 67/67 component:audit PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### 12.7 Chip parity — every theme value is selectable
Norm implanted 2026-05-21 (CHECKLIST §D-7.4).
Every chip-group control (`size`, `variant`, `color`, `intent`, …) must
enumerate the **full** union declared in the component's `types.ts`. If
`ControlVariant` is `'surface' | 'outline' | 'ghost'` and the component's
`Variant` type does not narrow it, the demo exposes 3 chips. Truncated
arrays — showing 2 of 3 — are a contract bug; the demo lies about the
component's surface.
If the component narrows the union deliberately (`Extract<…>`), the demo
matches the narrowed set exactly. Narrowings must be justified in the
component README; arbitrary narrowings just to "show fewer chips" are
removed (see `field`, `toolbar` — both widened to full `ControlVariant`
on 2026-05-21).
### 12.8 Size category cheatsheet
The shared `Size` scale is `xxs · xs · sm · md · lg · xl · xxl · full`.
Each component's recipe declares which size primitives it maps, the
`*Size` type narrows the union, and the demo chips reflect that narrowing
1:1.
Canonical category mapping (2026-05-21):
| Category | Sizes exposed | Components |
| --------------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------- |
| Form controls (toggle-like, single tactile target) | `xs · sm · md · lg · xl` | checkbox, switch, toggle, radio-group, rating-group, slider |
| Text inputs (control with input field) | `xs · sm · md · lg · xl` | search-field, number-field, date-field, editable, tags-input, combobox, select |
| Progress / meter (scalable bar) | `xs · sm · md · lg · xl` | progress, meter |
| Layout containers (field + form) | `xs · sm · md · lg · xl` | field, form |
| Nav controls (inline navigation strips) | `xs · sm · md · lg` | breadcrumb, pagination, tag-group, toolbar |
| Composite panels (no smaller-than-sm usable) | `sm · md · lg` | calendar, date-picker, date-range-picker, file-upload, stepper, tooltip |
| Pre-existing wide scales (kept) | `xs..xl` or `xxs..xxl` | avatar, dialog, drawer, popover, tabs, icon |
When adding a new size to a component:
1. Add `'size-{X}-{prop}': '…'` entries to the component's recipe in
`src/uix/eidos/lib/recipes/base.ts`.
2. Add `[data-{kebab}][data-size='{X}']` selector(s) to the component's
`*.css`.
3. Widen `Size = Extract<Size, …>` in the component's `types.ts`.
4. Regenerate: `npm run generate:eidos-css`.
5. Update the demo's chip array.
If the recipe has **no per-size entries** (component reads `--control-height-X`,
`--space-X`, `--font-size-X` directly — e.g. combobox), step 1 is just adding
the `[data-size='X']` selector with the right primitive `var()` references.
docs: consolidate kind contract + composition norms (sweep) After several commits on pickers, the docs lagged behind the actual contract. This sweep aligns PENDIENTES + eidos README + DEMO_AUTHORING_GUIDE with what landed. PENDIENTES.md: - 'Pickers' section rewritten as a consolidated state table. Everything done is marked hecho; the two big items (MonthPicker/YearPicker, MonthRangePicker/YearRangePicker as separate components) are explicitly **descartar** because they're achieved via <DatePicker kind='X'> / <DateRangePicker kind='X'>. Duplicating component surfaces for what a prop captures is doctrinally rejected. - Promotion of YearView/MonthView to standalone <YearCalendar> / <MonthCalendar> is **diferir** — currently coupled to picker provider context, no real use case outside picker yet. - Time picker / color picker propagation of modal+Footer pattern marked **implementar**. - Playwright browser tests for the picker flows marked **implementar** — range state machine + kind chip + modal need coverage. - Range view: 'differentiate start/end vs in-range visually' added to theming backlog (currently all 3 use primary-solid, range tint not visible). - Two new norms N-6 and N-7: * N-6 picker kind = single source for input + view. Filtering lives at DateFieldProvider (soma), consumers iterate the segments output. Views are canonical Eidos parts. * N-7 composition over visibility props. Parts opt-in by inclusion, not by boolean prop. Demo wraps parts in {#if showX} with local state so the UI toggles still work without leaking demo logic into the parts. eidos/README.md: - New 'Cambios 2026-05-21 — pickers: kind + composition' section summarising kind + Footer composition + provider helpers + the 'composition wins, no separate variant components' decision. DEMO_AUTHORING_GUIDE.md: - §12.9 'Composition over visibility props': right vs wrong example for <DatePicker.Footer> with the Clear/Cancel/Close children. - §12.10 'Chakra-style kind for picker variants': demo skeleton for the Input snippet (no filter) and the Content {#if} branch. Task list: #29 retired (MonthRangePicker/YearRangePicker as separate components — replaced by <DateRangePicker kind='X'>). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
### 12.9 Composition over visibility props
Norm implanted 2026-05-21 (`PENDIENTES.md` N-7).
Optional parts (Footer, Clear, Cancel, Close, Header sub-items, etc.)
**must not** be controlled by `*Button` boolean props on the
component root. Visibility is owned by **composition**: include the
part to render it, omit it to hide it.
Wrong (the previous shape we removed from `<DatePicker>` /
`<DateRangePicker>`):
```svelte
<!-- DON'T: visibility decided by root prop, parts conditionally render -->
<DatePicker clearButton cancelButton closeButton>
<DatePicker.Footer>
<DatePicker.Clear /> <!-- decides internally if it renders -->
<DatePicker.Cancel />
<DatePicker.Close />
</DatePicker.Footer>
</DatePicker>
```
Right:
```svelte
<!-- DO: parts render unconditionally; consumer composes what they want -->
<DatePicker>
<DatePicker.Footer>
<DatePicker.Clear /> <!-- always renders when mounted -->
<DatePicker.Cancel />
<DatePicker.Close />
</DatePicker.Footer>
</DatePicker>
```
For demos: wrap the parts in `{#if showX}` with local state so the
user can toggle via switches, but the parts themselves don't read
that state. The demo's switches decide whether the part is
INCLUDED in the markup.
Modal mode (`mode='modal'`) does NOT force the Close button to show;
the consumer must include `<X.Close/>` if they need an exit
affordance. Documented in the Close part's source.
### 12.10 Chakra-style `kind` for picker variants
Norm implanted 2026-05-21 (`PENDIENTES.md` N-6).
For `DatePicker` and `DateRangePicker`, the `kind: 'date' | 'month' | 'year'`
prop is the single source for granularity. Drives:
1. **Input segments**: filtered at the `DateFieldProvider` (soma).
Consumers iterate `segments` from the snippet without filtering.
2. **Popover view**: the consumer branches structurally on `kind` to
render `<Picker.YearView>` / `<Picker.MonthView>` / `<Picker.Calendar>`.
Demo skeleton:
```svelte
<!-- Input: no filter — soma emits the right segments per kind -->
<DatePicker.Input>
{#snippet children({ segments })}
{#each segments as { part, value }}
<DatePicker.Segment {part}>{value}</DatePicker.Segment>
{/each}
{/snippet}
</DatePicker.Input>
<!-- Popover: branch on kind to pick the right view -->
<DatePicker.Content>
{#if kind === 'year'}<DatePicker.YearView />
{:else if kind === 'month'}<DatePicker.MonthView />
{:else}<DatePicker.Calendar>…</DatePicker.Calendar>{/if}
<DatePicker.Footer>…</DatePicker.Footer>
</DatePicker.Content>
```
No separate components — `MonthPicker`, `YearPicker`, `MonthRangePicker`,
`YearRangePicker` are achieved via `<DatePicker kind='X'>` /
`<DateRangePicker kind='X'>`.
## 13. Verification checklist
Before declaring a demo done:
1. `npm run check` reports 0 errors for that file.
2. Walk every tab in dev mode — every tab renders.
3. Every prop control changes something visible on the live preview.
4. Every Sema `▶ play` button fires (look for `data-event` in the
trace strip).
5. Snippets reflect the current control values without stale
placeholders.
uix: date-picker + date-range-picker components + component audit infra Two new Eidos components shipped end-to-end (wrapper + recipe + demo + Soma provider hardening) plus a checklist-driven audit pipeline that scores all 67 morfo components against doctrinal completion criteria. New components: - date-picker: full popover-anchored picker over date-field + calendar, with calendar/content/trigger parts and demo route. - date-range-picker: standalone wrapper with own calendar/grid/segment surface, demo route, and recipe CSS. - Both wrappers follow Option C disciplined (root + parts attached via explicit assignment, no Object.assign). Supporting Soma changes: - range-calendar provider tightened (211 LOC of behavior, 167 LOC of tests), README brought up to component doctrine. - date-field, date-picker, date-range-field, date-range-picker Soma providers + READMEs updated for new wrappers. - popover provider/close gain props needed by the picker wrappers. Morfo updates: - date-picker / date-range-picker / range-calendar morfos refined for the new APIs (parts, events, ARIA). Audit infrastructure (new): - src/uix/COMPONENT_COMPLETION_CHECKLIST.md — 81 doctrinal rules across morfo / eidos wrapper / recipe CSS / demo / README / cross-layer scripts. Each rule keyed to active_architecture.md, GUIA_IMPLEMENTACION and DEMO_AUTHORING_GUIDE. - scripts/component-audit.ts + `npm run component:audit` — regex parser over all 67 components, emits tmp/component-audit.md with summary scoreboard + per-component findings. Validates against canonical SEMA_FAMILIES / SEMA_VERBS / ARCHETYPE_VOCABULARY / INTENTS. - Initial baseline: 1 PASS, 64 NEEDS-WORK, 2 BROKEN (tooltip, date-range-picker). Top systemic gaps: translations.label (49), README Gaps/Comparativa/Baseline sections (87 combined), keyboard /event ratio under-declaration (15), apg URL absent (19). Misc: - src/uix/kimi-audit-eidos.md — supplementary audit notes. - .gitignore: ignore .codex-* agent scratch artifacts at repo root. - continue.md + READMEs updated through the migration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
6. Composite components list the inherited event surface; avoid claiming "0
events" when Popover, Field, Calendar or RangeCalendar actions are visible.
7. Any `data-*` selector used by the preview is backed by Morfo/Soma, not by a
demo-only attr that hides a missing contract.
8. Date/calendar demos are visually checked in one-month, two-month,
modal/no-modal, min/max and clear/deselect states before being called done.
## 14. Status of demos under `web/routes/uix/components/`
All public Eidos components currently exposed in the UIX docs have
new-layout demos. The legacy `web/routes/{name}/` set is removed; this
is the only canonical demo home. `svg` is an internal rendering helper
and does not get a standalone component demo unless it becomes a public
component.
| Component | Demo |
| ------------ | --------------------------- |
| accordion | ✓ |
| avatar | ✓ (0 events, pending audit) |
| breadcrumb | ✓ (0 events, structural) |
| calendar | ✓ (3 events) |
| checkbox | ✓ |
| collapsible | ✓ |
| dialog | ✓ |
| drawer | ✓ (canary template) |
| field | ✓ (0 events, pending audit) |
| form | ✓ (3 events, SIUM demo) |
| icon | ✓ (0 events, structural) |
| meter | ✓ (0 events, passive) |
| number-field | ✓ (2 events) |
| pagination | ✓ (1 event) |
| popover | ✓ |
| progress | ✓ (0 events, passive) |
| radio-group | ✓ |
| rating-group | ✓ (1 event) |
| search-field | ✓ (2 events) |
| slider | ✓ (2 events) |
| switch | ✓ |
| tabs | ✓ |
| toast | ✓ |
| toggle | ✓ |
| toolbar | ✓ (1 event) |
| tooltip | ✓ (0 events, pending audit) |

Powered by TurnKey Linux.