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

21 KiB

title type audience authority status source
Component audit guide guide human + agent binding — the pre-flight checklist before touching any component current 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 — the demo template is locked.
  3. Read 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 — the route
  2. docs/guides/component-guide.md — build steps + rules A1–A37
  3. docs/guides/completion-checklist.md — acceptance
  4. docs/guides/demo-authoring.md — the LOCKED demo template (D-1.x is error-level)
  5. docs/guides/component-audit.md — this file
  6. docs/CANON.md — the ruling semantic vocabulary
  7. docs/architecture/morfo.md + sema.md + soma.md — the layer contracts
  8. docs/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 — 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 §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 §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 § "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.

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. 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.

Powered by TurnKey Linux.