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/docs/guides/component-audit.md

350 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
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.
## 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}-* → --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: <reason> */` 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 `<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 (to address before Layout Batch 2 or Typography)
| 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.

Powered by TurnKey Linux.