--- title: Component audit guide type: guide audience: human + agent authority: binding — the pre-flight checklist before touching any component status: current source: migrated from web/routes/uix/lib/COMPONENT_AUDIT_GUIDE.md (2026-07-02, docs-book F7.5) --- # Component audit guide > **STOP. READ THIS BEFORE TOUCHING ANY COMPONENT.** > > This file is a **binding pre-flight checklist**. Every time you create, > port, or modify a UIX component you MUST walk this guide front to back. > Skipping steps produced the broken Layout Batch 1 (commit `9ec2a57a`) > that had to be redone — a full day lost. Don't repeat the mistake. ## 0. The inviolable rule Before touching `src/uix/{morfo,soma,sema,eidos}/components/{name}` or `web/routes/uix/components/{name}/+page.svelte`: 1. **Read this entire file.** Yes, every time. 2. **Read [`demo-authoring.md`](./demo-authoring.md)** — the demo template is locked. 3. **Read [`src/uix/eidos/components/README.md`](../../src/uix/eidos/components/README.md)** — the eidos contract. 4. **Run the pre-flight audit in §3** against at least 4 reference libraries. Tabular output, not prose. 5. **Get the user's sign-off on scope** before writing any code. Agents you delegate to MUST be given this file in their brief. Their brief must reference §3 (audit template) and §6 (anti-patterns) explicitly. **The minimum package** for a component-building brief is this exact file list (titles are not enough — a package missing any of these produced a non-conforming build in the Knob exercise, STUMBLES.md #8): 1. [`docs/building-a-component.md`](../building-a-component.md) — the route 2. [`docs/guides/component-guide.md`](./component-guide.md) — build steps + rules A1–A37 3. [`docs/guides/completion-checklist.md`](./completion-checklist.md) — acceptance 4. [`docs/guides/demo-authoring.md`](./demo-authoring.md) — the LOCKED demo template (D-1.x is error-level) 5. [`docs/guides/component-audit.md`](./component-audit.md) — this file 6. [`docs/CANON.md`](../CANON.md) — the ruling semantic vocabulary 7. [`docs/architecture/morfo.md`](../architecture/morfo.md) + [`sema.md`](../architecture/sema.md) + [`soma.md`](../architecture/soma.md) — the layer contracts 8. [`docs/canon/vocabularies.md`](../canon/vocabularies.md) — the generated closed sets (archetypes, families+holds, verbs, intents, haptic kinds, scales, sizes, variants) you draw from 9. [`src/uix/eidos/components/README.md`](../../src/uix/eidos/components/README.md) — the eidos pattern ## 1. Reference libraries — who to consult and why Every component must be audited against the relevant references below. Pick at least 3, including Radix Themes when it's a visual primitive and Ark UI / React Aria when it's a headless behaviour. | Library | URL | Use for | | --- | --- | --- | | **Radix Themes** | https://radix-ui.com/themes | Visual primitives (Box / Flex / Grid / Container / Text). Canonical "Box-style split" architecture. | | **Radix Primitives** | https://radix-ui.com/primitives | Headless behaviour (Dialog / Popover / Toast / DropdownMenu). ARIA reference. | | **Chakra UI** | https://chakra-ui.com | Style-props heavy. Useful to spot props users expect on `Box` that we deliberately moved to `Flex`/`Grid`. | | **Mantine** | https://mantine.dev | Strong typography + layout primitives (`Stack`, `Group`, `Container`, `Text`, `Title`). | | **MUI** | https://mui.com | Reference for `sx` prop, transitions, theme system. | | **React Aria** | https://react-spectrum.adobe.com/react-aria | Industry-strength a11y. Use when keyboard or screen-reader contract is non-trivial. | | **Ark UI** | https://ark-ui.com | Headless state machines (combobox, picker, tags-input). Mirror their event names when possible. | | **Bits UI** | https://bits-ui.com | Svelte-native parallel to Radix Primitives — useful sanity check on component shape. | | **shadcn/ui** | https://ui.shadcn.com | Mostly visual recipes layered on Radix. Useful for opinionated styling defaults. | | **WAI-ARIA APG** | https://www.w3.org/WAI/ARIA/apg/patterns/ | The contract. Required reading for any role / aria audit. | **Anti-rule:** "I'll just look at how air did it" is NOT a reference audit. Air's port reflects the old architecture and the gaps from the previous era. Audit fresh. ## 2. Layer responsibilities Before deciding where a feature lives, recall the 4-layer architecture: | Layer | Owns | Examples | | --- | --- | --- | | **morfo** | declarative contract: parts, data-attrs, ARIA, keyboard, events | `src/uix/morfo/components/{name}.ts` | | **soma** | headless behaviour: state, effects, runtime providers, focus, ARIA wiring | `src/uix/soma/components/{name}/` | | **sema** | perceptual side-effects: sound, motion, haptic projections of semantic events | `src/uix/sema/components/{name}/` | | **eidos** | pure CSS visual recipe + `*Provider` wrappers that pipe `data-*` from morfo into the cascade | `src/uix/eidos/components/{name}/` | **The 2-of-3 rule:** a morfo extension is only justified if at least 2 of 3 consumer layers (soma / sema / eidos) need it. (`feedback_2of3_rule`.) **Visual-only primitives** (avatar, icon, all layout/typography primitives) get `scope: ['eidos']` in their morfo — no soma, no sema. Justify the 0-event surface in a header comment on the morfo file. ## 3. Pre-flight audit template Before writing or porting a component, fill out this table. Don't proceed without user sign-off. ``` ### {ComponentName} audit — {YYYY-MM-DD} #### Feature parity matrix | Feature | Radix Themes | Chakra | Mantine | UIX (port plan) | Decision | | --- | --- | --- | --- | --- | --- | | {prop or feature} | {how their X handles it} | {…} | {…} | {what we plan} | implement / defer / drop | #### Architectural choices - Layer ownership (morfo / soma / sema / eidos): {…} - Container vs item split: {if applicable, where each prop lives} - Composition vs single-component: {does this share root with another primitive?} - Sizes covered: {xs … xxl subset — declared in the recipe + types; parity rule in demo-authoring.md §6} - Variants: {surface / outline / ghost / soft …} - Color intent palette: {full / narrowed — justify} #### Reference comparison summary | Library | Closest equivalent | Difference vs UIX | Why we differ | | --- | --- | --- | --- | | {…} | {…} | {…} | {…} | #### Decision log - {gap}: {implement / defer / drop} — {reason} ``` Sign-off line at the bottom: `User signed scope on YYYY-MM-DD`. ## 4. Architectural rules (project-wide) These are non-negotiable. Violations trigger a rewrite. ### 4.1 Radix-style item / container split For layout primitives: - **Container props** (`alignItems`, `justifyContent`, `flexDirection`, `flexWrap`, `templateColumns`, `gap`) live on `` / `` containers. - **Item props** (`flex`, `grow`, `shrink`, `basis`, `order`, `alignSelf`, `justifySelf`, `placeSelf`, `gridColumn`, `gridRow`, `gridArea`) live on ``. Chakra puts everything on ``. We don't. Document the split in the demo's lede + cross-references between primitives. ### 4.2 Composition over visibility props Optional parts (Footer, Clear, Close, Header sub-items) **must not** be controlled by `*Button` boolean props on the root. Visibility is owned by composition — include the part to render it, omit to hide. (Doctrine: `eidos/README.md` picker conventions, presence = visibility.) ### 4.3 Chip parity Every chip-group control in a demo enumerates the **full** type union. Truncated arrays are a contract bug. If the component narrows the union deliberately (`Extract<…>`), the narrowing must be justified in the component README. ([`demo-authoring.md`](./demo-authoring.md) §6.) ### 4.4 Size category cheatsheet The shared `Size` scale is `xxs · xs · sm · md · lg · xl · xxl · full`. Each component declares which steps it maps in the recipe + types — that declaration IS the source. Demos reflect that narrowing 1:1 (parity rule: [`demo-authoring.md`](./demo-authoring.md) §6). ### 4.5 Per-event intent Each morfo event's intent reflects its OWN evaluative load, not the component's overall context. `close-save = fulfill`, `close-cancel = absent`, `open = fromProp`. (`feedback_per_event_intent_intrinsic`.) ### 4.6 No re-export façades `feedback_no_reexport_facades` + `feedback_no_reexport_shims`. If `$libs/days` exposes a date util, the consumer imports `$libs/days` directly. Do not add re-exports across layers. ### 4.7 Persistent label registries When a component's contract requires displaying a label for a value that may be unmounted (combobox after popover closes, multi-select chips), the soma provider's value→label map MUST NOT be cleaned up on item unmount — keep the entry around. Re-registration is idempotent via `SvelteMap.set`. (Combobox `SelectedTags` bug from commit `30e9517a`.) ### 4.8 Floating layer pattern Anchored popovers default to **intrinsic width** + `align="start"` + `sideOffset=6`. Do NOT set `matchAnchorWidth` unless the component contract explicitly requires it. The visual gap from forcing anchor-width is worse than the marginal alignment win. (Decision from 2026-05-22.) ### 4.9 Combobox-style keyboard `` keeps DOM focus; highlight moves virtually via `aria-activedescendant`. Arrow keys cycle, Enter commits, Escape closes, Tab moves out of the component (does NOT enter the listbox). Pattern: WAI-ARIA combobox. (`feedback_combobox_keyboard`, SearchField demo 2026-05-22.) ### 4.10 Chakra-style multi-select chips When a combobox / multi-select renders selected values as chips, the chips go ABOVE the input control (Chakra v3 multi pattern). Reason: the popover opens downward and would cover chips that sit below the input. Do NOT inline chips inside the control — MUI Autocomplete's inline pattern fights wrap behavior. (Decision from `20709caf`.) ### 4.11 No literal typography in recipes (`R-2.7`) Component CSS recipes consume tokens, never literal values, for `font-size` / `font-weight` / `line-height` / `letter-spacing` / `font-family`. The audit script flags violations with `warn` severity. **Why:** the system vertebrates via the foundation token chain: ``` typography.ts styles → --style-{name}-* → --leading-ui / --font-ui → --field-*-* (recipe tokens) → component CSS ``` A literal in a recipe breaks the chain — changing the foundation no longer propagates. See [`architecture/eidos.md`](../architecture/eidos.md) § "Typographic vertebration" for the full architecture (why two anchors, why recipes don't read `--style-{name}-*` directly, comparison vs Radix Themes / Chakra / Mantine / MUI). Allowed escape valves: - `var(...)` wrapping the value - numeric identities: `0`, `0px`, `0em`, `0rem`, `1` - keywords: `inherit`, `initial`, `unset` - explicit opt-out via trailing `/* literal: */` comment ## 4.11 Run the contract audit script The repo ships `scripts/component-audit.ts` (alias `npm run component:audit`). It is the **canonical verification tool** for contract compliance — it walks every component, parses morfo declarations, cross-checks against eidos recipes + demos, and emits a markdown report at `tmp/component-audit.md`. ```bash npm run component:audit # full run node --import tsx/esm scripts/component-audit.ts --only NAME # single component node --import tsx/esm scripts/component-audit.ts --severity error ``` A component is only "done" when its row in the scoreboard reads **PASS** with 0 errors. Warnings should be triaged in the audit log (§8). The audit script enforces: - `E-2.x` morfo / eidos / demo / README presence - `D-7.4` demo chip-array parity vs type union - `A-3.x` keyboard ↔ events declarative consistency - `R-1.x` morfo `data-*` attrs styled in eidos recipe - `F-1.x` README structure (Baseline / Comparativa / Decisiones / Gaps) Run it before every commit that touches a component. Block the merge on any error. ## 5. The demo template is locked Read [`demo-authoring.md`](./demo-authoring.md). The full template is mandatory — the tab set (v2: 9 tabs), the always-on stage + trace, the harness modules, layer-grouped controls, snippet parity, and the always-rendered contract tabs are all defined THERE; this guide does not copy the list (copies drift — the v1 6-tab enumeration that used to live here went stale against the v2 template). The canary is **button** (`web/routes/uix/components/button/+page.svelte`, the v2 worked reference). Layout primitives have their own canaries — **box** and **flex** (see §7). ## 6. Anti-patterns (failed approaches — don't repeat) These produced rework or rejected commits. Don't propose them again without naming the reason. | Anti-pattern | Where it failed | Why | | --- | --- | --- | | Shallow 100-line demo "marketing page" | Layout Batch 1 v1 (`9ec2a57a`) | Violates `DEMO_AUTHORING_GUIDE`. Reverted + redone. | | `matchAnchorWidth` on date-picker popover | 2026-05-22 morning | Visual gap when popover content narrower than input. User explicitly abandoned. | | Visibility via `*Button` boolean props | DatePicker pre-`69`, removed | Inverts composition. Use `{#if showX}` around the part instead. | | Inline chips inside Combobox control | Combobox v1–v3 (2026-05-22) | Fights flex-wrap; popover covers chips. Switched to band-above-Control (Chakra). | | `onpointerdown` + `preventDefault` for item picks | SearchField v1 | Eats the synthetic click — onclick never fires. Use plain `