|
|
---
|
|
|
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 `<Flex>` / `<Grid>`
|
|
|
containers.
|
|
|
- **Item props** (`flex`, `grow`, `shrink`, `basis`, `order`,
|
|
|
`alignSelf`, `justifySelf`, `placeSelf`, `gridColumn`, `gridRow`,
|
|
|
`gridArea`) live on `<Box>`.
|
|
|
|
|
|
Chakra puts everything on `<Box>`. 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
|
|
|
|
|
|
`<input>` 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}-* → consumed directly by recipes
|
|
|
(+ --leading-ui, config data)
|
|
|
→ --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: <reason> */` comment
|
|
|
|
|
|
### 4.12 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 `<button onclick>`. |
|
|
|
| `flex: 1 1 100%` on input inside flex-wrap container | Combobox v2 | Forces wrap to new row even with chips fitting. Use `flex: 1 1 4rem` and let it share rows. |
|
|
|
| Re-registering child labels and unregistering on unmount | Combobox SelectedTags v1 | Labels disappear when popover closes. Register on mount, never unregister. |
|
|
|
| Auto-rendering everything in a wrapper | Combobox.SelectedTags v1 | Less customizable than explicit composition. Provide a default snippet, allow override. |
|
|
|
| `inputRef.focus()` after picking without guard | SearchField popover v1 | Re-fires `onfocusin` → reopens popover. Use `justCommitted` flag + microtask reset. |
|
|
|
| Agent runs `git reset` mid-session | Layout Batch 2 v1 worktree | Lost reported work. Brief MUST say "DO NOT git reset, stash, or checkout files." |
|
|
|
| Skipping reference audit before porting | Layout Batch 1 v1 | Missed `alignContent`, `columns/rows` shorthand, `grow`, `divider` slot. Always audit first. |
|
|
|
|
|
|
## 7. Canonical canaries
|
|
|
|
|
|
When unsure how to shape a new component, copy and adapt from these:
|
|
|
|
|
|
| Domain | Canary | Path |
|
|
|
| ---------------------------------------------- | ------------ | ----------------------------------------------------- |
|
|
|
| Overlay with state | Drawer | `web/routes/uix/components/drawer/+page.svelte` |
|
|
|
| Form input with composite parts | Search field | `web/routes/uix/components/search-field/+page.svelte` |
|
|
|
| Visual-only primitive (Box-style) | Box | `web/routes/uix/components/box/+page.svelte` |
|
|
|
| Visual-only container with own props | Flex | `web/routes/uix/components/flex/+page.svelte` |
|
|
|
| Multi-state composite (header / body / footer) | Date picker | `web/routes/uix/components/date-picker/+page.svelte` |
|
|
|
| Passive headless (image / svg) | Avatar | `web/routes/uix/components/avatar/+page.svelte` |
|
|
|
|
|
|
## 8. Audit log
|
|
|
|
|
|
Running log of completed reference audits. Add a row when you sign
|
|
|
off scope with the user. Use the §3 template; link the commit that
|
|
|
closed the work.
|
|
|
|
|
|
| Date | Component | Audited against | Commit | Notes |
|
|
|
| ---------- | -------------------------------- | ------------------------------------------------- | ---------- | ---------------------------------------------------------------------------- |
|
|
|
| 2026-05-22 | Combobox (multi tags) | radix-themes, chakra, mui (Autocomplete), mantine | `20709caf` | Chose Chakra v3 band-above-control after rejecting MUI inline |
|
|
|
| 2026-05-22 | Layout Batch 1 (Box) | radix-themes, chakra, mantine | `66c6897c` | Moved gridArea/Column/Row from Grid → Box (Radix split). Added placeSelf. |
|
|
|
| 2026-05-22 | Layout Batch 1 (Flex + 6 others) | radix-themes, chakra, mantine | `591b0885` | **Surface-level only — see "Known gaps" below. Re-audit before next round.** |
|
|
|
|
|
|
### Known gaps from Layout Batch 1 (HISTORICAL — 2026-05-22 snapshot)
|
|
|
|
|
|
> Superseded by the 2026-07 component re-audit programme (the full matrix
|
|
|
> went green — `docs/audit/components/_cierre.md`). The gaps below are the
|
|
|
> frozen record of that moment; CURRENT gaps live in each component's
|
|
|
> dossier (`## Gaps`, with disposition).
|
|
|
|
|
|
| Primitive | Missing feature | Reference | Priority |
|
|
|
| --------- | -------------------------------------------------------------------- | ----------------------- | -------- |
|
|
|
| Flex | `alignContent` (multi-line cross-axis align) | Radix, Chakra | should |
|
|
|
| Grid | `columns` / `rows` shorthand (e.g. `columns="3"` → `repeat(3, 1fr)`) | Radix Themes | should |
|
|
|
| Grid | `inline` boolean (display: inline-grid) | parity with Flex.inline | nice |
|
|
|
| Grid | `alignContent` | Radix, Chakra | should |
|
|
|
| Stack | `divider` slot (separator between items) | Chakra Stack | nice |
|
|
|
| Stack | `HStack` / `VStack` helpers | Chakra ergonomics | nice |
|
|
|
| Group | `grow` boolean (children fill width equally) | Mantine Group | should |
|
|
|
| Group | `preventGrowOverflow` | Mantine Group | nice |
|
|
|
| Wrap | `shouldWrapChildren` (auto-wrap each child in WrapItem) | Chakra Wrap | nice |
|
|
|
| Container | `fluid` mode (no max-width) | Mantine | should |
|
|
|
| Section | — | none missing | — |
|
|
|
|
|
|
## 9. Pre-port checklist (copy this into your task plan)
|
|
|
|
|
|
```
|
|
|
[ ] Read component-audit.md (this file)
|
|
|
[ ] Read demo-authoring.md
|
|
|
[ ] Read eidos/components/README.md
|
|
|
[ ] Identify reference libraries to audit against (§1)
|
|
|
[ ] Fill out the §3 audit template
|
|
|
[ ] Present scope table + decisions to user
|
|
|
[ ] Get user sign-off in writing
|
|
|
[ ] Implement: morfo → soma (if needed) → eidos → demo
|
|
|
[ ] Write src/uix/eidos/components/{name}/README.md with the
|
|
|
required sections (Baseline / Comparativa / Decisiones / Gaps)
|
|
|
[ ] Run `npm run component:audit` — verdict MUST be PASS
|
|
|
[ ] Run `npm run check` — 0 errors required
|
|
|
[ ] Visual walk: every Live control changes the stage; every Sema
|
|
|
`▶ play` button fires; snippets reflect control values
|
|
|
[ ] Add row to §8 audit log with commit hash
|
|
|
[ ] Update §6 anti-patterns if a new failure mode emerged
|
|
|
```
|
|
|
|
|
|
## 10. When in doubt
|
|
|
|
|
|
Stop and ask the user. The cost of a clarifying question is always
|
|
|
lower than the cost of redoing 8 components.
|