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/completion-checklist.md

336 lines
37 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 completion checklist
type: guide
audience: human + agent
authority: canonical — the acceptance matrix for a component being done
status: current
source: migrated from src/uix/COMPONENT_COMPLETION_CHECKLIST.md (2026-07-02, docs-book F7.5)
---
# Component completion checklist
> Doctrinal criteria for considering a UIX component **done** across all four
> layers (Morfo · Soma · Sema · Eidos), its recipe CSS, and its demo page.
>
> Source of truth — [`architecture/active-architecture.md`](../architecture/active-architecture.md),
> `src/docs/GUIA_IMPLEMENTACION_SEMAUIX.md` (historical seed),
> [`demo-authoring.md`](./demo-authoring.md).
>
> Machine-validated by `scripts/component-audit.ts`. Run via
> `npm run component:audit [name]?`. Outputs a markdown report at
> `tmp/component-audit.md`.
>
> This is the **acceptance matrix** — the criteria for *done*, not a build
> guide. For HOW to build a component (the ordered authoring steps + rationale
> rules A1–A37), see [`component-guide.md`](./component-guide.md). The
> two are a complementary pair, not duplicate checklists.
## How to read this
Each rule has a **severity**, an **applicability**, and an **enforcement**:
- **Severity**:
- `error` — blocks the component from being considered done.
- `warn` — should be fixed but not blocking.
- `info` — informational, no remediation expected.
- **Applicability**:
- `all` — every public component.
- `interactive` — components with user actions (most). Identified by `morfo.events.length > 0` OR `morfo.parts.*.keyboard.length > 0`.
- `passive` — purely structural / display components (icon, avatar, breadcrumb, meter, progress). Allowed `0 events` only after README justifies it.
- **Enforcement** — who verifies the rule. Declaring a rule here does NOT
imply the audit script checks it; this column makes the gap explicit:
- `audit` — implemented in `scripts/component-audit.ts` (the report prints
the same rule ID).
- `tool:{name}` — enforced by another script/test (e.g. `tool:morfo:check`,
`tool:smoke`, `tool:check` for tsc/svelte-check).
- `manual` — human review; no mechanical check exists yet. Candidates for
promotion to `audit` are welcome (see §I).
---
## A. Morfo declaration
The morfo is DNA. If it's incomplete, every downstream layer is incomplete.
### A1 · Basics
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| A-1.1 | Exports a single `{Name}Morfo` const satisfying `Morfo` | error | all | audit |
| A-1.2 | Has `name`, `kebab`, `scope: ['soma', ...]` declared | error | all | audit |
| A-1.3 | Has `texts.label` as a valid idlangref (`#?components.{kebab}.label\|Fallback`), catalog entry in `langs/components/{kebab}.ts` | error | all | audit |
| A-1.4 | If interactive: `apg` URL declared pointing at the relevant W3C ARIA pattern | warn | interactive | audit |
### A2 · Parts
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| A-2.1 | Has at least one `Provider` part with `archetype: 'provider'` | error | all | audit |
| A-2.2 | Every part declares `kebab`, `archetype`, `kind: 'public' \| 'internal'`, `defaultElement`, `role` | error | all | manual |
| A-2.3 | Every public part has at least one `data-*` attr OR explicit justification in component README (`A2.3 exception: ...`) | warn | all | manual |
| A-2.4 | Parts with non-trivial state declare `states: [...]` array | warn | interactive | manual |
| A-2.5 | Archetype ∈ `ARCHETYPE_VOCABULARY` (`src/uix/morfo/types.ts`). No invented archetypes. | error | all | audit |
| A-2.6 | Every part with focusable behavior has a `tabindex` or `role` that the browser focuses (no silent unfocusable interactive parts) | warn | interactive | manual |
### A3 · Events — the part where current components leak
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| A-3.1 | If interactive: `events.length >= 1` | error | interactive | audit |
| A-3.2 | Every event has `name`, `semantic.family`, `semantic.target` (partRef) | error | all | tool:check |
| A-3.3 | `semantic.family` ∈ `SEMA_FAMILIES` (source: `src/uix/sema/types.ts` — do not copy the list) | error | all | audit |
| A-3.4 | `semantic.verb` ∈ `SEMA_VERBS[family]` | error | all | audit |
| A-3.4b | Per-event `family.verb` pairing is canonical (no verb borrowed from another family) | warn | all | audit |
| A-3.5 | `semantic.sequence` ∈ `'pre' \| 'coincident' \| 'post'` declared explicitly | warn | interactive | audit |
| A-3.6 | Event name follows `{verb}-{variant}` or `{family}-{verb}` pattern | warn | interactive | audit |
| A-3.7 | **Event/keyboard coverage**: every distinct keyboard action that mutates state has a corresponding semantic event. Pure focus moves don't need an event. | error | interactive | audit |
| A-3.8 | If component supports value reset/clear: `commit.reset` or `commit.discard` event declared | warn | interactive | manual |
| A-3.9 | If component supports navigation steps (calendar, stepper, pagination): `shift.navigate` event declared with target part | warn | interactive | manual |
| A-3.10 | Intent is declared when family requires it per `SEMA_FAMILY_POLICY` (`src/uix/sema/types.ts`). `target.partRef` always set | warn | interactive | audit |
### A4 · ARIA + keyboard
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| A-4.1 | Provider part has `aria-label` or `aria-labelledby` declared with `severity: 'recommended'` | warn | interactive | manual |
| A-4.2 | If component has invalid/disabled/readonly/required state: matching `aria-invalid`/`aria-disabled`/`aria-readonly`/`aria-required` declared conditionally | error | interactive | manual |
| A-4.3 | If APG pattern requires specific keys (e.g., Grid: Arrow×4 + Home/End/PageUp/PageDown), all are declared in part keyboard | warn | interactive | manual |
| A-4.4 | No reinvented keys (`Spacebar` is `' '`; `Esc` is `'Escape'`; etc.) — must match KeyboardEvent.key values | error | interactive | audit |
---
## B. Eidos wrapper
### B1 · API shape (Option C disciplined)
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| E-1.1 | Has `{name}.svelte` (root visual) + per-part files `{name}-{part}.svelte` | error | all | audit |
| E-1.2 | `index.ts` does **explicit per-property assignment** (`X.Part = Part`), not `Object.assign(X, { ... })` | error | all | audit |
| E-1.3 | `index.ts` exports `{Name}` named + `default {Name}` | error | all | audit |
| E-1.4 | No exports of `Provider`, `Base`, `Root`, `Parts`, or `Soma{Name}Provider` | error | all | audit |
| E-1.5 | Imports Soma as `import * as {Name} from '$soma/components/{kebab}'` — namespace, not destructured | warn | all | manual |
| E-1.6 | Types: `{Name}Props`, `{Name}Size`, `{Name}Variant` (no `EidosX*` prefixes) | error | all | manual |
| E-1.7 | For single-part components, the default IS the component (Toggle, Switch, Icon) — no fake compound API | error | all | manual |
### B2 · Files + structure
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| E-2.1 | `types.ts` exports the public Props + size/variant/color unions | error | all | audit |
| E-2.2 | `{name}.css` exists and is wired: imported by the component's own wrapper (current, code-split pattern) OR from `eidos/index.css` (layout primitives + shared visuals like spin-field) | error | all | audit |
| E-2.3 | `README.md` exists with baseline (Air or "no baseline"), external comparison table, decisions, gaps | error | all | audit |
| E-2.4 | Every part declared in morfo (`kind: 'public'`) has either a wrapper file or an explicit README note explaining why it's not exposed | warn | all | manual |
### B3 · Wrapper internals
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| E-3.1 | `{name}.svelte` renders Soma's `{Name}.Provider` (or equivalent) — does NOT mount Soma `Trigger`/`Content` directly | error | all | manual |
| E-3.2 | No `$state` re-declaration of bindable props from Soma (use `$bindable` proxy) | warn | all | manual |
| E-3.3 | Snippets receive `children` prop and don't shadow it with `{#snippet children}` in same scope | error | all | manual |
| E-3.4 | No data-*/CSS leaking from other layers: no `data-soma-*`, no `--soma-*`/`--air-*` CSS variables | error | all | manual |
| E-3.5 | All visual props (`size`, `variant`, `color`, `radius`) map to `data-{prop}="value"` on the root for CSS to read | warn | all | manual |
---
## C. Recipe CSS
### C1 · State coverage
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| R-1.1 | Has root selector `[data-{component}]` defining base layout/spacing | error | all | audit |
| R-1.2 | If morfo declares `data-disabled` on any part: `[data-disabled]` styled | error | interactive | audit |
| R-1.3 | If morfo declares `data-readonly`: `[data-readonly]` styled | warn | interactive | audit |
| R-1.4 | If morfo declares `data-invalid`: `[data-invalid]` styled (using `--color-risk-element` or similar) | warn | interactive | audit |
| R-1.5 | All focusable parts have `:focus-visible` styled | error | interactive | audit |
| R-1.6 | Hover state defined for trigger-like archetypes (`trigger`, `item`, `option`, `close`, `action`) | warn | interactive | manual |
| R-1.7 | Disabled state has `cursor: not-allowed` OR documented exception in README | warn | interactive | manual |
### C2 · Token discipline
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| R-2.1 | No raw colors (hex/rgb/named). All colors come from `var(--color-*)` or `var(--{component}-*)`. Applies to `recipes/base.ts`, `archetypes.css`, `events.css` and every component `*.css` — no exceptions. | error | all | audit |
| R-2.2 | No raw font-size in px/rem. Use `var(--font-size-*)` from Eidos recipe | warn | all | audit (via R-2.7) |
| R-2.3 | No magic numbers in spacing — use `var(--space-*)` or `var(--{component}-*)` | warn | all | manual |
| R-2.4 | Component-scoped tokens come from `EidosConfig.recipes` (`base.css`) — verify via `ActiveEidos.listRecipes()` | warn | all | manual |
| R-2.5 | No `--eidos-*` or `--soma-*` variable invented in component recipe | error | all | audit |
| R-2.6 | Every `var(--color-X)` referenced in recipes or component CSS is declared in `generated/base.css`. The theme contract (`SurfaceColorRoles`, `ContentColorRoles`, `BorderColorRoles`, `FocusColorRoles` + intent role maps) is the closed set; new tokens go through `themes/base.ts` + regen. | error | all | audit |
| R-2.7 | No literal typography in recipes (`font-size`, `line-height`, `letter-spacing`, `font-weight` raw values) — consume the foundation's type anchor via tokens. Escape valves: CSS keywords, numeric identities (`0`, `1`), or a same-line `/* literal: <reason> */` | warn | all | audit |
### C3 · Color resolution
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| R-3.1 | Recipe uses `[data-color='X']` selectors only for the subset declared in component's README | warn | colored | manual |
| R-3.2 | No legacy color names (`success`, `warning`, `danger`, `info`) | error | all | audit |
| R-3.3 | Intent ↔ color resolution implemented: when component receives `intent != 'neutral'`, `data-color` reflects the intent | warn | colored | manual |
### C4 · Recipe Contract — transversal systems
> Canon: [`canon/recipe-contract.md`](../canon/recipe-contract.md). These
> rules enforce that every recipe consumes the theming's transversal systems
> (state-layer, tokenized elevation, opacity token, logical axes, motion channel)
> instead of hand-rolling its own idiom. **All R-4.x are `error`**: R-4.1/4.2/4.3/4.4/4.6
> graduated after the 2026-07-02 mechanical backfill; R-4.5 after the motion migration
> emptied its backlog (recipes consume the channel via preset stamp, signatures, or
> registered keyframes — the trigger-vs-materials doctrine is in the contract). The
> WIP tracks `words` / `palabras` / `chronos` are excluded. Escape valves: a same-line
> `/* literal: <reason> */` (values) or `/* functional: <reason> */` (keyframes).
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| R-4.1 | No literal `box-shadow` — elevation goes through `var(--shadow-*)` / `var(--depth-{plane}-*)` (or the inset-ring pattern, which carries `var()`) | error | all | audit |
| R-4.2 | No literal fractional `opacity` outside `@keyframes` — disabled/muted states consume `var(--opacity-*)` | error | all | audit |
| R-4.3 | `:hover` backgrounds are the state-layer (`var(--state-*)`) or a palette token — no raw values, no hand-rolled `color-mix(… currentColor …)` | error | interactive | audit |
| R-4.4 | No physical-axis token keys (`padding-x/-y`, `margin-x/-y`) in `lib/recipes/base.ts` — logical axes (`padding-inline/-block`) are the canon | error | all | audit |
| R-4.5 | Local `@keyframes` require a `/* functional: … */` annotation — perceptual signatures live in `EidosConfig.motion`, not in component CSS | error | all | audit |
| R-4.6 | No direct `var(--scale-*)` / `var(--primitive-*)` in component CSS — consume `var(--color-{role}-{slot})` or recipe tokens | error | all | audit |
---
## D. Demo page (`web/routes/uix/components/{kebab}/+page.svelte`)
### D1 · Template compliance
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| D-1.1 | Outer element is `<div data-uix-canvas-inner>` | error | all | audit |
| D-1.2 | Tab union matches the canonical v2 9-tab template (`'live' \| 'system' \| 'motion' \| 'sema' \| 'services' \| 'api' \| 'morfo' \| 'recipe' \| 'a11y'`); the v1 6-tab union is accepted only pending migration | error | all | audit |
| D-1.3 | Imports `compileMorfo` + the component's morfo | error | all | audit |
| D-1.4 | Imports `getActiveUix` if Sema tab has interactive Play buttons | warn | interactive | manual |
| D-1.5 | Has MutationObserver on `data-event` attribute, populating `trace` state | error | interactive | audit |
| D-1.6 | Has `<div data-uix-stage>` between header and tablist (live always rendered) | error | all | audit |
| D-1.7 | Has `<div data-uix-stage-trace>` with at least the trace strip | error | interactive | audit |
### D2 · Header
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| D-2.1 | Header has `data-uix-eyebrow` (Category · Name) | error | all | audit |
| D-2.2 | Header has `<h1 data-uix-page-title>` and `<p data-uix-page-lede>` (single paragraph summary) | error | all | audit |
| D-2.3 | Header has `data-uix-page-meta` with at minimum `parts` and `events` pills | error | all | audit |
### D3 · Snippet parity (DEMO_AUTHORING §12)
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| D-3.1 | `eidosSnippet` derived and rendered (the v2 template's single snippet; v1 demos may additionally carry `somaSnippet`) | error | all | audit |
| D-3.2 | Snippet code reflects current control values (not static placeholders) | warn | all | manual |
| D-3.3 | If live preview uses a schema/options/state, snippet declares the same | warn | all | manual |
### D4 · Sema tab
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| D-4.1 | Sema tab always rendered (even for 0-event components, with explicit empty state) | error | all | manual |
| D-4.2 | Sema tab has events table: `name / family / verb / sequence / intent / play` | error | interactive | manual |
| D-4.3 | Play buttons emit via `uix.events.emit(...)` onto a real DOM target inside the stage | error | interactive | audit |
### D5 · Morfo tab (declarative contract surface)
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| D-5.1 | Has header table: name / kebab / scope / apg / parts / events | error | all | manual |
| D-5.2 | Has parts overview table | error | all | manual |
| D-5.3 | For each part with data/aria/keyboard: per-part subsection rendered | warn | all | manual |
| D-5.4 | Events declaration table rendered | error | interactive | manual |
### D6 · A11y tab + Recipe tab
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| D-6.1 | A11y tab has keyboard table (from morfo) + ARIA contract table | warn | interactive | manual |
| D-6.2 | Recipe tab lists selectors with their layer source (`morfo` / `eidos`) | warn | all | manual |
### D7 · Visible controls discipline (DEMO_AUTHORING §12.6)
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| D-7.1 | Every control on the Live tab produces a visible change on the stage | warn | all | manual |
| D-7.2 | No demo-only `data-*` attrs hand-stamped to fake morfo selectors | error | all | manual |
| D-7.3 | Soma layer badge `[soma]` and Eidos layer badge `[eidos]` used to group control subsections | warn | all | manual |
| D-7.4 | **Chip parity**: every chip-group control (`size`, `variant`, `color`) enumerates the **full** union of the component's type — no truncated arrays. Theme defines 3 variants → all 3 are selectable. See DEMO_AUTHORING §6. | error | all | audit |
| D-7.5 | **Size coverage**: the chip array for `size` matches the component's declared union in recipe + types, 1:1 (form controls / text inputs / progress-meter / field-form expose `xs..xl`; nav controls expose `xs..lg`; passive panels keep `sm..lg`). | warn | all | manual |
---
## E. Cross-layer integrity
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| X-1.1 | `npm run morfo:check` PASS for this component | error | all | tool:morfo:check |
| X-1.2 | `npm run perm:check` PASS for this component if it's instrumented | warn | interactive | tool:perm:check |
| X-1.3 | `eidos-lint-all.ts` invalid count = 0 for this component | error | all | tool:eidos-lint |
| X-1.4 | `npm run check` does not produce errors in this component's files | error | all | tool:check |
| X-1.5 | Component's smoke route loads without console errors (`SMOKE_SCOPE=/uix/components/{kebab} npm run smoke`) | error | all | tool:smoke |
---
## F. Documentation completeness (component README)
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| F-1.1 | README has section "## Baseline" with Air comparison or explicit "no Air baseline" | error | all | audit |
| F-1.2 | README has section "## Comparativa" with at least 3 external references (Ark UI, Bits UI, Radix/shadcn, React Aria, MUI, or similar) | error | all | audit |
| F-1.3 | README has section "## Decisiones" with explicit choices made vs alternatives | warn | all | audit |
| F-1.4 | README has section "## Gaps" listing what's deferred / not implemented, each with disposition (implementar / diferir / descartar) | error | all | audit |
| F-1.5 | If component is `passive` (0 events): README explicitly justifies why (`## Passive justification`) | error | passive | audit |
---
## G. Doctrinal canon (intent + color + sequence)
### G1 · Subset declaration
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| G-1.1 | README declares `## Subset` listing which `color` values + which `intent` values the component accepts (per GUIA_IMPLEMENTACION_SEMAUIX §3) | warn | colored | manual |
| G-1.2 | Types restrict `intent`/`color` props to the declared subset via union types — not free-form string | warn | colored | manual |
| G-1.3 | Recipe CSS only matches `[data-color='X']` for X in the declared subset | warn | colored | manual |
### G2 · Sequence canon
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| G-2.1 | Events that animate exit before structural commit use `sequence: 'pre'` (dismiss, close-cancel, etc.) | warn | interactive | manual |
| G-2.2 | Events that confirm a result use `sequence: 'post'` (commit-save, submit, etc.) | warn | interactive | manual |
| G-2.3 | Continuous progress events use `sequence: 'coincident'` (sustain.progress) | warn | passive | manual |
---
## H. Severity summary table
A component is **PASS** when:
- **0 errors** across A–H
- **≤3 warnings**, each justified in README "## Audit exceptions"
- All required scripts (X-1.x) pass
A component is **NEEDS-WORK** when 1-5 errors or >3 unjustified warnings.
A component is **BROKEN** when >5 errors OR any X-1.x script fails.
---
## I. How to add a new rule
1. Append to the appropriate section table **with its Enforcement value**.
2. If `Enforcement: audit`: implement it in `scripts/component-audit.ts` with a
check function that returns `CheckResult`, using the SAME rule ID the table
declares (the report and this doc must grep-match).
3. If `Enforcement: manual` / `tool:X`: no script change, but the value must be
honest — declaring a rule here does not make it checked.
4. Add the rule to the doctrinal source if it crosses a layer
([`architecture/active-architecture.md`](../architecture/active-architecture.md),
[`CANON.md`](../CANON.md), or [`demo-authoring.md`](./demo-authoring.md)).
5. Document the rationale at the top of the new rule's check function.
Rules should be **doctrinally grounded** (cite the source doc) and, when
`audit`, **machine-checkable** (avoid pure aesthetic criteria — those go in the
per-component README review). Keep this table and the script in sync: every
`audit` rule ID exists in the script, and every rule ID the script emits exists
here (`npm run docs:check` verifies both directions).

Powered by TurnKey Linux.