docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
---
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),
docs(book): F7.6 (1/2) — decision logs moved to docs/decisions/ (verbatim Spanish)
LIBRO_VARIACIONES_Y_EXTENSIONES -> decisions/book-deviations.md and
GUIA_IMPLEMENTACION_SEMAUIX -> decisions/guia-semantica-historica.md,
both moved AS-IS in Spanish: the deviations registry is a logbook of the
author's literal decisions ('transcrita literal') and carries proposed
doctrinal text destined for the Spanish book — translating it would
destroy that function (s G calls itself bitacora); the guia was already
status: historical (Fase 6) and the plan exempts it explicitly. English
frontmatter added to both; internal cross-links repointed (CANON,
theming/reference, book-deviations D.11). docs-check's I2 phantom-field
exemption follows the moved file (it matched by the LIBRO_VARIACIONES
filename; now also matches decisions/book-deviations.md). Corpus swept:
CANON x3, README (E2/E3 strata + tables — also fixed the pre-Fase-6
leftover row still calling the guia 'authoritative for any new wrapper'
and the unswept eidos/TSC.md stratum mention), building-a-component D.4,
completion-checklist G-1.1 + header, theming/reference, architecture
x5 (active-architecture, eidos, morfo, overview, sema x2).
docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
> [`decisions/guia-semantica-historica.md`](../decisions/guia-semantica-historica.md) (historical seed),
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
> [`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 |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
docs+fix: old docs quarantined in docs/old-deprecated; STUMBLES doc-class fixes
Two user findings from the Knob build exercise (an agent building a new
component from the docs alone — STUMBLES.md).
Quarantine: the superseded fossils no longer share shelf space with the
live corpus. docs/old-deprecated/ (with an index README explaining what
lands there and pointing readers at docs/README.md) now holds the
executed audits and fix plans: fable_audit, fable-eidos-audit,
inherit_audit + inherit_fix_plan, ARCHETYPE_COHERENCE_AUDIT_2026-06-19
(still citable — the component-guide banner and the eidos components
README repoint to it), COMPONENT_COHERENCE_AUDIT. pendiente.md (a live
pending list, not a fossil) moved to docs/process/. docs-check treats
the folder as sealed chronicle (I1/I2 exempt; I6 skips its internal
links, as its README promises). Root-level *.md is now: README, CLAUDE,
AGENTS + the user's own working files.
STUMBLES fixes applied on the spot (the doc-class ones):
- #2 kind drift: the REAL enum is 'public' | 'private' | 'virtual'
(MorfoPartKind, 671/9/14 uses) — morfo.md omitted 'private', the
checklist invented 'internal' (0 uses). Both fixed; I2 gains the
phantom-'internal' guard. A-2.1's row now says what the audit script
actually checks (kebab only — the archetype may vary, the Toggle
provider-IS-trigger doctrine).
- #6: the langs catalog SHAPE (flat keys, per-language leaves, named
export, index registration) is now shown in morfo.md instead of only
its location.
- #8: component-audit s0 defines the minimum brief package as an
explicit 8-file list.
The engineering-class stumbles are registered as plan batches S1-S6
(generated vocabularies appendix, continuous-gesture trigger doctrine,
part-absent condition, Gesture.rotate, the soma->eidos CSS-var
contract, minor frictions). docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| A-2.1 | Has at least one part with `kebab: 'provider'` (the audit checks the kebab only — the archetype may vary: `trigger` when the Provider IS the interactive element (Toggle/Switch), `image` for icon, …) | error | all | audit |
| A-2.2 | Every part declares `kebab` , `archetype` , `kind: 'public' \| 'private' \| 'virtual'` (`MorfoPartKind`), `defaultElement` , `role` | error | all | manual |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| 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 |
fix(uix): C1 — zero BROKEN components; documented-exception valve in the audit
The 5 BROKEN components (cascade, gradient-builder, menu-dial, motion,
qr-code) are BROKEN no more: cascade/motion/menu-dial now PASS,
gradient-builder/qr-code drop to NEEDS-WORK with only demo-phase (D-*)
and C8 items left.
Framework-level piece: component-audit gains the documented-exception
valve the checklist already used for A-2.3/R-1.7 — a greppable
'R-x.y exception: reason' line in the component README turns the rule
into a PASS that reports the reason. Wired for R-1.1, R-1.2, R-1.5 and
E-2.2; the checklist rows say the same. This separates deliberate
design (cascade and motion deliberately ship NO recipe — they ride the
foundation stagger + state presets; menu-dial's focus/disabled states
live in the composed Fab/Button recipes) from plain omission, which
stays an error.
Mechanical fixes: texts.label + langs entries for cascade/motion (new
files, registered) and gradient-builder (label added to its existing
entry); menu-dial's missing default export. README contract sections
(Baseline/Comparativa/Decisiones/Gaps/Passive justification + Audit
exceptions) added to cascade, motion, menu-dial and qr-code — mostly
re-heading content those docs already argued; comparativas grounded in
M3 speed dial/MUI SpeedDial/PrimeVue, Framer Motion/AnimatePresence/
Svelte transitions, ark-ui/qr-code-styling per component.
Verified: component:audit 130 -> 78 PASS / 52 NEEDS-WORK / 0 BROKEN;
npm run check at the 61-error pre-existing baseline (0 own).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| 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), OR a documented `E-2.2 exception:` in README (headless components with no visual recipe) | error | all | audit |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| 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 |
docs(direction): el contrato pasa a ser canon, y el corpus deja de contradecirlo
El eje estaba cerrado en CODIGO y no en DOCUMENTACION. El contrato vivia solo en
`CONTINUE-direction.md`, un handoff que `docs/README.md` declara «never a source
of truth». Un capitulo E2 lo fija ahora: `docs/canon/direction-contract.md`.
Va a canon y no a arquitectura por la misma razon que `recipe-contract.md`: es
normativo y tiene guard ejecutable. Cubre la cadena y donde corre cada eslabon;
por que `undefined` no es `'ltr'`; las DOS atributos —`dir` crudo (nativo, lo que
`:dir()` mira) y `data-dir` resuelto (opt-in, para recetas que necesitan un hook
incondicional)—; cuando el estampado es OBLIGATORIO; la doctrina de selector; las
trampas que el guard no ve; que espeja y que no; y la mitad global de prefs.
EL CORPUS SE CONTRADECIA en cuatro sitios, y dos de ellos ENSENABAN mal:
- `component-guide.md:372` daba la cadena como `prefs → 'ltr'`, DOS eslabones,
mandando al wrapper a leer prefs directamente. Eso excluye la prop.
- `soma-architecture.md` §3.4 presentaba el resolutor a pelo (`soma.prefs.getDir()`
dentro del provider) como LA forma de obtener la direccion — justo lo que el eje
retiro del catalogo.
- `html.ts` ensenaba en su JSDoc `dir?: 'ltr' | 'rtl'`, el union a mano que la
regla E-2.5 ahora prohibe. El mecanismo que ensena (extender para estrechar una
clave HTML) es correcto y sobrevive; solo cambia el ejemplo.
- `active-architecture.md:416` no listaba `lang` en la proyeccion, contra
`contracts.ts` y contra su propio test de frontera. Igual `prefs/README.md`.
Y no lo mencionaba en absoluto: el glosario (`activeDir`, `resolvedDir`,
`data-dir`, RTL-1, el escape `rtl-physical:`), `eidos.md` (RTL-1 es el TERCER
guard de deriva y faltaba junto al builder tipado y eidos-lint), `soma.md`,
`building-a-component.md` (§Known traps es exactamente donde va «la prop mueve la
matematica y deja la pintura atras»), y la tabla de atributos de la arquitectura,
que afirma «todo lo que viaja entre capas viaja por atributos» y omitia `dir`.
El checklist gana cuatro reglas y el guard que faltaba (`rtl:check` no estaba en
la matriz de aceptacion pese a que la tabla canon lo nombra), mas un recorte en
E-3.5: «todas las props visuales mapean a `data-{prop}`» leia como mandato de
estampar `data-dir`, que `:dir()` no puede ver.
DIVERGENCIA DECLARADA (§7): la familia `chart` no tiene prop ni provider y
resuelve leyendo `getComputedStyle(node).direction`. Es el unico sitio del
catalogo que hace lo que §1 prohibe. Se registra en vez de esconderse.
Tres verificadores adversariales sobre el barrido; sus hallazgos, corregidos:
RTL-1 estaba anunciado como guard de TODO el capitulo (solo cubre §4) · «nunca
lee al padre» era absoluto y borraba la composicion sancionada en el punto de
llamada · el estampado se afirmaba incondicional en un sitio y condicional en
otro · las cuatro filas nuevas usaban una aplicabilidad que ni la leyenda ni
`component-audit.ts` conocen.
`docs:check` 0 errores sobre 564 docs. `check` 77 = la linea base.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| E-2.5 | If the component has any direction-dependent behaviour or paint: public `dir` prop declared as `dir?: Direction` — the alias, never a hand-written `'ltr' \| 'rtl'` union ([`canon/direction-contract.md`](../canon/direction-contract.md) §1) | error | all | manual |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
### 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 |
docs(direction): el contrato pasa a ser canon, y el corpus deja de contradecirlo
El eje estaba cerrado en CODIGO y no en DOCUMENTACION. El contrato vivia solo en
`CONTINUE-direction.md`, un handoff que `docs/README.md` declara «never a source
of truth». Un capitulo E2 lo fija ahora: `docs/canon/direction-contract.md`.
Va a canon y no a arquitectura por la misma razon que `recipe-contract.md`: es
normativo y tiene guard ejecutable. Cubre la cadena y donde corre cada eslabon;
por que `undefined` no es `'ltr'`; las DOS atributos —`dir` crudo (nativo, lo que
`:dir()` mira) y `data-dir` resuelto (opt-in, para recetas que necesitan un hook
incondicional)—; cuando el estampado es OBLIGATORIO; la doctrina de selector; las
trampas que el guard no ve; que espeja y que no; y la mitad global de prefs.
EL CORPUS SE CONTRADECIA en cuatro sitios, y dos de ellos ENSENABAN mal:
- `component-guide.md:372` daba la cadena como `prefs → 'ltr'`, DOS eslabones,
mandando al wrapper a leer prefs directamente. Eso excluye la prop.
- `soma-architecture.md` §3.4 presentaba el resolutor a pelo (`soma.prefs.getDir()`
dentro del provider) como LA forma de obtener la direccion — justo lo que el eje
retiro del catalogo.
- `html.ts` ensenaba en su JSDoc `dir?: 'ltr' | 'rtl'`, el union a mano que la
regla E-2.5 ahora prohibe. El mecanismo que ensena (extender para estrechar una
clave HTML) es correcto y sobrevive; solo cambia el ejemplo.
- `active-architecture.md:416` no listaba `lang` en la proyeccion, contra
`contracts.ts` y contra su propio test de frontera. Igual `prefs/README.md`.
Y no lo mencionaba en absoluto: el glosario (`activeDir`, `resolvedDir`,
`data-dir`, RTL-1, el escape `rtl-physical:`), `eidos.md` (RTL-1 es el TERCER
guard de deriva y faltaba junto al builder tipado y eidos-lint), `soma.md`,
`building-a-component.md` (§Known traps es exactamente donde va «la prop mueve la
matematica y deja la pintura atras»), y la tabla de atributos de la arquitectura,
que afirma «todo lo que viaja entre capas viaja por atributos» y omitia `dir`.
El checklist gana cuatro reglas y el guard que faltaba (`rtl:check` no estaba en
la matriz de aceptacion pese a que la tabla canon lo nombra), mas un recorte en
E-3.5: «todas las props visuales mapean a `data-{prop}`» leia como mandato de
estampar `data-dir`, que `:dir()` no puede ver.
DIVERGENCIA DECLARADA (§7): la familia `chart` no tiene prop ni provider y
resuelve leyendo `getComputedStyle(node).direction`. Es el unico sitio del
catalogo que hace lo que §1 prohibe. Se registra en vez de esconderse.
Tres verificadores adversariales sobre el barrido; sus hallazgos, corregidos:
RTL-1 estaba anunciado como guard de TODO el capitulo (solo cubre §4) · «nunca
lee al padre» era absoluto y borraba la composicion sancionada en el punto de
llamada · el estampado se afirmaba incondicional en un sitio y condicional en
otro · las cuatro filas nuevas usaban una aplicabilidad que ni la leyenda ni
`component-audit.ts` conocen.
`docs:check` 0 errores sobre 564 docs. `check` 77 = la linea base.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| E-3.5 | All visual props (`size`, `variant` , `color` , `radius` ) map to `data-{prop}="value"` on the root for CSS to read. ** `dir` is exempt**: it is native and stamped raw — `data-dir` is a legitimate extra (the resolved value, always present wherever a component opts into stamping it), never a substitute, since `:dir()` cannot see it | warn | all | manual |
| E-3.6 | If the component declares `dir` : the wrapper runs `activeDir(() => dir, soma)` at `Provider.create(…)` and the provider defaults **once** , in `resolvedDir` — it never re-implements the chain (no second defaulting, no prefs lookup, no DOM read). A further link (a submenu inheriting its parent menu) is composed at the **call site** , never added inside the provider | error | all | manual |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
---
## C. Recipe CSS
### C1 · State coverage
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
fix(uix): C1 — zero BROKEN components; documented-exception valve in the audit
The 5 BROKEN components (cascade, gradient-builder, menu-dial, motion,
qr-code) are BROKEN no more: cascade/motion/menu-dial now PASS,
gradient-builder/qr-code drop to NEEDS-WORK with only demo-phase (D-*)
and C8 items left.
Framework-level piece: component-audit gains the documented-exception
valve the checklist already used for A-2.3/R-1.7 — a greppable
'R-x.y exception: reason' line in the component README turns the rule
into a PASS that reports the reason. Wired for R-1.1, R-1.2, R-1.5 and
E-2.2; the checklist rows say the same. This separates deliberate
design (cascade and motion deliberately ship NO recipe — they ride the
foundation stagger + state presets; menu-dial's focus/disabled states
live in the composed Fab/Button recipes) from plain omission, which
stays an error.
Mechanical fixes: texts.label + langs entries for cascade/motion (new
files, registered) and gradient-builder (label added to its existing
entry); menu-dial's missing default export. README contract sections
(Baseline/Comparativa/Decisiones/Gaps/Passive justification + Audit
exceptions) added to cascade, motion, menu-dial and qr-code — mostly
re-heading content those docs already argued; comparativas grounded in
M3 speed dial/MUI SpeedDial/PrimeVue, Framer Motion/AnimatePresence/
Svelte transitions, ark-ui/qr-code-styling per component.
Verified: component:audit 130 -> 78 PASS / 52 NEEDS-WORK / 0 BROKEN;
npm run check at the 61-error pre-existing baseline (0 own).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| R-1.1 | Has root selector `[data-{component}]` defining base layout/spacing, OR a documented `R-1.1 exception:` in README (foundation-riding components with no recipe of their own) | error | all | audit |
| R-1.2 | If morfo declares `data-disabled` on any part: `[data-disabled]` styled, OR a documented `R-1.2 exception:` in README (state owned by a composed primitive's recipe) | error | interactive | audit |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| 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 show a focus treatment — `:focus-visible` , the field shell's `:focus-within` , a `:has(…:focus…)` rule, or the canonical `[data-focused]` state ring — OR a documented `R-1.5 exception:` in README (foundation archetype ring / shared layer / composed primitive / no focusable part) | error | interactive | audit |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| 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 |
docs(direction): el contrato pasa a ser canon, y el corpus deja de contradecirlo
El eje estaba cerrado en CODIGO y no en DOCUMENTACION. El contrato vivia solo en
`CONTINUE-direction.md`, un handoff que `docs/README.md` declara «never a source
of truth». Un capitulo E2 lo fija ahora: `docs/canon/direction-contract.md`.
Va a canon y no a arquitectura por la misma razon que `recipe-contract.md`: es
normativo y tiene guard ejecutable. Cubre la cadena y donde corre cada eslabon;
por que `undefined` no es `'ltr'`; las DOS atributos —`dir` crudo (nativo, lo que
`:dir()` mira) y `data-dir` resuelto (opt-in, para recetas que necesitan un hook
incondicional)—; cuando el estampado es OBLIGATORIO; la doctrina de selector; las
trampas que el guard no ve; que espeja y que no; y la mitad global de prefs.
EL CORPUS SE CONTRADECIA en cuatro sitios, y dos de ellos ENSENABAN mal:
- `component-guide.md:372` daba la cadena como `prefs → 'ltr'`, DOS eslabones,
mandando al wrapper a leer prefs directamente. Eso excluye la prop.
- `soma-architecture.md` §3.4 presentaba el resolutor a pelo (`soma.prefs.getDir()`
dentro del provider) como LA forma de obtener la direccion — justo lo que el eje
retiro del catalogo.
- `html.ts` ensenaba en su JSDoc `dir?: 'ltr' | 'rtl'`, el union a mano que la
regla E-2.5 ahora prohibe. El mecanismo que ensena (extender para estrechar una
clave HTML) es correcto y sobrevive; solo cambia el ejemplo.
- `active-architecture.md:416` no listaba `lang` en la proyeccion, contra
`contracts.ts` y contra su propio test de frontera. Igual `prefs/README.md`.
Y no lo mencionaba en absoluto: el glosario (`activeDir`, `resolvedDir`,
`data-dir`, RTL-1, el escape `rtl-physical:`), `eidos.md` (RTL-1 es el TERCER
guard de deriva y faltaba junto al builder tipado y eidos-lint), `soma.md`,
`building-a-component.md` (§Known traps es exactamente donde va «la prop mueve la
matematica y deja la pintura atras»), y la tabla de atributos de la arquitectura,
que afirma «todo lo que viaja entre capas viaja por atributos» y omitia `dir`.
El checklist gana cuatro reglas y el guard que faltaba (`rtl:check` no estaba en
la matriz de aceptacion pese a que la tabla canon lo nombra), mas un recorte en
E-3.5: «todas las props visuales mapean a `data-{prop}`» leia como mandato de
estampar `data-dir`, que `:dir()` no puede ver.
DIVERGENCIA DECLARADA (§7): la familia `chart` no tiene prop ni provider y
resuelve leyendo `getComputedStyle(node).direction`. Es el unico sitio del
catalogo que hace lo que §1 prohibe. Se registra en vez de esconderse.
Tres verificadores adversariales sobre el barrido; sus hallazgos, corregidos:
RTL-1 estaba anunciado como guard de TODO el capitulo (solo cubre §4) · «nunca
lee al padre» era absoluto y borraba la composicion sancionada en el punto de
llamada · el estampado se afirmaba incondicional en un sitio y condicional en
otro · las cuatro filas nuevas usaban una aplicabilidad que ni la leyenda ni
`component-audit.ts` conocen.
`docs:check` 0 errores sobre 564 docs. `check` 77 = la linea base.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| R-1.8 | If the recipe branches with `:dir(…)` : the provider stamps the **raw** `opts.dir.current` as the native `dir` attribute — an unstamped assertion moves the maths and leaves the paint behind ([`canon/direction-contract.md`](../canon/direction-contract.md) §2) | error | all | manual |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
### 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
feat(packs+text): incorporate the animation collection — Ambient pack + text-effects family + docs
Two streams, split by what the animation touches:
STREAM A — decorative backgrounds → the pack tier
- arts/scene: a consolidated scene runtime ($scene) that owns, once, the
citizenship every ad-hoc background reinvented or skipped (frame loop,
off-view pause, DPR cap, mandatory reduced-motion, WebGL context
loss/restore, scene budget, teardown). SceneDom port (adom satisfies it),
webgl/webgl2/canvas2d drivers + a custom-pipeline extension (vertexShader +
draw + glContext.depth/dprCap) for real geometry (beam, particles, dither,
grid, eter, pixel-blast, hyperspeed). 32 effects as shared resources.
- src/packs/ambient: the first pack — <Ambient effect="…"> mounts a registered
effect; the P contract (P-1..P-6) guarded by scripts/packs-check.ts; colors
are token-aware (P-4). One-way dependency, removable-by-construction.
- resolveToken extended to semantic color slots (--color-{role}-{slot}) so
consumers resolve theme tokens to concrete colors (the P-4 half).
STREAM B — animations over real text → canon
- Six components (count-up + text-{gradient,circular,blur,focus,scramble}):
each a morfo + eidos recipe (where there's styling) + demo. CountUp is a
service component (counts through uix.format.numbers). The five Text* are
passive decoratives. Upgrades over the seeds: SR hardening (real text
visually-hidden + aria-hidden decoration), a11y fix (no fake role=button),
measurement discipline (cached rects via dom.measure, no reflow storm),
reduced-motion, ecosystem citizenship (eidos.dom/timers, no raw platform).
- MorfoElement gains 'p'.
DOCS
- docs/architecture/packs.md (pack tier, admission rule, P contract, Aura
promotion path); docs/decisions/design-text-effects.md (the family design
record) + indexed in decisions.md / README.md; glossary entries
(scene/Ambient/Aura/text effects); scene README custom-pipeline + authoring
bridge; motion-guide content-effects note; strata tables acknowledge packs.
Gates: component:audit 141/0/0 · docs:check 0/0 · scene tests 9/9 · packs:check
0/36 · check 0 own errors. Verified in browser (32 effects mount+compile;
6 text components SSR+hydrate, CountUp re-formats by locale live, TextGradient
resolves token stops to OKLCH via var()).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
> `/* literal: <reason> */` (values), `/* functional: <reason> */` (keyframes) or
> `/* important: <reason> */` (`!important`).
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| 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 |
feat(packs+text): incorporate the animation collection — Ambient pack + text-effects family + docs
Two streams, split by what the animation touches:
STREAM A — decorative backgrounds → the pack tier
- arts/scene: a consolidated scene runtime ($scene) that owns, once, the
citizenship every ad-hoc background reinvented or skipped (frame loop,
off-view pause, DPR cap, mandatory reduced-motion, WebGL context
loss/restore, scene budget, teardown). SceneDom port (adom satisfies it),
webgl/webgl2/canvas2d drivers + a custom-pipeline extension (vertexShader +
draw + glContext.depth/dprCap) for real geometry (beam, particles, dither,
grid, eter, pixel-blast, hyperspeed). 32 effects as shared resources.
- src/packs/ambient: the first pack — <Ambient effect="…"> mounts a registered
effect; the P contract (P-1..P-6) guarded by scripts/packs-check.ts; colors
are token-aware (P-4). One-way dependency, removable-by-construction.
- resolveToken extended to semantic color slots (--color-{role}-{slot}) so
consumers resolve theme tokens to concrete colors (the P-4 half).
STREAM B — animations over real text → canon
- Six components (count-up + text-{gradient,circular,blur,focus,scramble}):
each a morfo + eidos recipe (where there's styling) + demo. CountUp is a
service component (counts through uix.format.numbers). The five Text* are
passive decoratives. Upgrades over the seeds: SR hardening (real text
visually-hidden + aria-hidden decoration), a11y fix (no fake role=button),
measurement discipline (cached rects via dom.measure, no reflow storm),
reduced-motion, ecosystem citizenship (eidos.dom/timers, no raw platform).
- MorfoElement gains 'p'.
DOCS
- docs/architecture/packs.md (pack tier, admission rule, P contract, Aura
promotion path); docs/decisions/design-text-effects.md (the family design
record) + indexed in decisions.md / README.md; glossary entries
(scene/Ambient/Aura/text effects); scene README custom-pipeline + authoring
bridge; motion-guide content-effects note; strata tables acknowledge packs.
Gates: component:audit 141/0/0 · docs:check 0/0 · scene tests 9/9 · packs:check
0/36 · check 0 own errors. Verified in browser (32 effects mount+compile;
6 text components SSR+hydrate, CountUp re-formats by locale live, TextGradient
resolves token stops to OKLCH via var()).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| R-4.7 | No `!important` without a same-line `/* important: <reason> */` annotation — the declaration wins every cascade fight, so the reason lives where it happens | error | all | audit |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
---
## 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 |
revert: deshacer la auditoría entera — se hizo sin leer la doctrina
Revert de los 7 commits de la sesión del 2026-07-29/30:
352ca8bbe style(soma): formato Prettier en el test de gradient-picker
17a1f167b test(soma): chat-list
e70397fca test(soma): field-langs y gradient-picker
c9b83d025 docs(morfo): qué hace pública una parte + hallazgos retirados
feb4a8424 test(soma): los primeros providers que no tenían red
f8e35b8fd fix(morfo): el eje intent/color
c39170abb fix(uix): la auditoría del sistema
`4e785d32b` (stats-band, otra sesión) queda intacto — el revert lo salta.
## Por qué se revierte todo y no una parte
La instrucción de partida era «audita el sistema, **para ello previamente lee
toda la documentación**». No se leyó. Se auditó primero y se justificó después,
y eso contaminó el conjunto, no unos commits concretos:
- Tres hallazgos del informe eran FALSOS, todos de la misma forma —
heurísticas de una sola vía dadas por hechas sin abrir el código:
D-3 `AgentTimersPort` (el puerto sí está satisfecho por el adaptador de
`defineActiveAgent`); «5 derivas de scope» (`avatar-group` / `path-trace` /
`rotate-align` sí están implementados, co-locados en el directorio del
padre); «29 morfos con `kind:'public'` irreal» (medía si existe
`<Componente.Parte>` e ignoraba que un primitivo de API plana compone por
props y snippets).
- `c9b83d025` existe SÓLO para retirar esos hallazgos del commit anterior.
- Los 6 tests de provider se montan sobre un `installSomaHarness` que FABRICA
cuatro servicios compartidos (`dom`, `langs`, `timers`, `prefs`), violando la
primera regla de propiedad de `architecture/active-uix.md`: «Only composition
roots create shared services. `morfo`, `soma`, `sema`, `eidos` and components
never create `dom`, `langs`, `prefs`, `format`, `clipboard` or equivalents:
they receive them from `ActiveUix`.» El arranque real son tres líneas
(`createActiveUix` → `setActiveUix` → `Soma.create()`) que estaban escritas.
- Y se le pidieron al usuario decisiones cuya respuesta estaba en el corpus
(`testing-and-tooling.md` dice que los tests de provider son convención, no
guard; la regla 1 zanja el arnés).
Triar qué conservar exigía justo el juicio que falló. Se revierte todo, se lee
el corpus, y vuelve sólo lo que se pueda sostener citando un documento — o lo
que sea defecto MEDIBLE y por tanto no dependa de criterio (el colapso de
uniones de props, que hacía compilar `<Avatar color="nonsense">`; que
`translations:check` crashease en cada ejecución de su historia). Nada se
pierde: los commits siguen en la historia y se recuperan con `cherry-pick`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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 |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| 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 |
feat(direction): RTL-2 — el guard del doble volteo, la forma que se me colo dos veces
Cierra §10.1, la ultima deuda del eje. `dee2c9e3a` arreglo los dos defectos pero
dejo la FORMA sin guard, y es la que sobrevivio a la migracion §6.3 y a que yo
diera el eje por cerrado en §9.19.
LA FIRMA — dentro de un bloque cuyo prelude tiene `:dir()`, la misma familia
logica (`border`/`padding`/`margin`/`inset`-`inline`) declarada en LAS DOS caras,
EXACTAMENTE UNA con valor neutro (`0`, `auto`, `none`, `initial`, `unset`,
`revert`). Ese desequilibrio ES el espejo cancelado: la propiedad ya se habia
volteado cuando la regla matchea, asi que reubicarla la devuelve al punto de
partida y la deja en el borde opuesto al de sus hermanas.
Es mas estrecha que «bloque `:dir()` con puras logicas» a proposito. Una regla
que CAMBIA un valor bajo RTL sin reubicarlo es legitima —asimetrico por diseño
existe—, y las dos caras con valor son un autor describiendo dos bordes reales.
Lo que delata el defecto es el PAR, una cara apagada. Hay un test negativo por
cada uno de esos casos, que son los que evitan que la regla se vuelva ruido.
LAS TRES DECISIONES que el handoff dejaba abiertas:
- `:dir(ltr)` tambien entra. Igual de sospechosa, y no anade ruido.
- Marcador PROPIO, `rtl-mirror: <reason>`. Reutilizar `rtl-physical:` mentiria:
aqui no hay nada fisico, y un marcador que miente es peor que ninguno. Mismo
mecanismo (`exemptLines` toma ahora el patron por parametro), dos vocabularios.
Un test comprueba que el marcador de RTL-1 NO exime a RTL-2.
- Regla aparte, `lintRtlMirror()` con su propio `RtlMirrorFinding`. Los 14 tests
de RTL-1 quedan intactos y el tipo lleva los campos que importan
(`family`/`neutral`/`payload`) en vez de forzar los de RTL-1.
VERIFICACION, en este orden:
- `rtl-lint.test.ts` 27/27 (14 de RTL-1 intactos + 13 nuevos).
- RECALL con el runner COMPLETO contra el arbol pre-arreglo: restaure los dos
ficheros de `dee2c9e3a^` sobre el arbol, corri `rtl:check` y los devolvi con
`git checkout` en el mismo bloque. **2/2 cazados** (`feed.css:147`,
`tree-view.css:211`). Probar la funcion no basta: el runner es lo que corre.
- `rtl:check` sobre HEAD: 1 error, el de `palabras`, preexistente y excluido.
- `docs:check` 0/0 sobre 564 docs. `check` 77 = linea base.
Un defecto de presentacion salio al hacerlo: el `calc()` multilinea de tree-view
partia el mensaje por el primer salto. Los campos que RTL-2 emite colapsan el
whitespace; con test.
Actualizados los textos que el handoff avisaba que quedarian obsoletos: el
`enforcement:` y §4 del contrato, la fila RTL del build contract, la fila X-1.6
del checklist, y los docblocks de `rtl-lint.ts` y `rtl-check.ts` — que son
doctrina, no adorno.
⚠️ Sigue siendo cierto lo que NINGUNA de las dos reglas ve: leen texto CSS. Un
`transform` inline escrito por JS, un preset de motion compartido y la geometria
SVG siguen necesitando el ojo en RTL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| X-1.6 | `npm run rtl:check` PASS (RTL-1 — logical inline anchor paired with a physical inline translate; RTL-2 — a `:dir()` rule that turns one logical face off and repaints the other, cancelling a mirror the property had already made). The run walks all of `src/uix/eidos` ; there is no per-component scope | error | all | tool:rtl:check |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
---
## 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 |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
docs(book): F7.6 (1/2) — decision logs moved to docs/decisions/ (verbatim Spanish)
LIBRO_VARIACIONES_Y_EXTENSIONES -> decisions/book-deviations.md and
GUIA_IMPLEMENTACION_SEMAUIX -> decisions/guia-semantica-historica.md,
both moved AS-IS in Spanish: the deviations registry is a logbook of the
author's literal decisions ('transcrita literal') and carries proposed
doctrinal text destined for the Spanish book — translating it would
destroy that function (s G calls itself bitacora); the guia was already
status: historical (Fase 6) and the plan exempts it explicitly. English
frontmatter added to both; internal cross-links repointed (CANON,
theming/reference, book-deviations D.11). docs-check's I2 phantom-field
exemption follows the moved file (it matched by the LIBRO_VARIACIONES
filename; now also matches decisions/book-deviations.md). Corpus swept:
CANON x3, README (E2/E3 strata + tables — also fixed the pre-Fase-6
leftover row still calling the guia 'authoritative for any new wrapper'
and the unswept eidos/TSC.md stratum mention), building-a-component D.4,
completion-checklist G-1.1 + header, theming/reference, architecture
x5 (active-architecture, eidos, morfo, overview, sema x2).
docs:check 0 errors, 11-warn baseline.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| G-1.1 | README declares `## Subset` listing which `color` values + which `intent` values the component accepts (per `decisions/guia-semantica-historica.md` §3) | warn | colored | manual |
docs(book): F7.5 (1/2) — checklist + audit guide + demo guide moved to docs/guides/
Three English guides moved verbatim (copy + link touch-ups, no
translation): COMPONENT_COMPLETION_CHECKLIST -> guides/
completion-checklist.md, COMPONENT_AUDIT_GUIDE -> guides/
component-audit.md, DEMO_AUTHORING_GUIDE -> guides/demo-authoring.md.
Tooling moved with its doc in the same pass: docs-check I5 now parses
the checklist at the new path (verified: 0 errors, rule tables found)
and component-audit.ts's two path strings updated. One reconciliation
in the audit guide's s5, applied as link-don't-copy: the copied v1
6-tab template list (stale vs the v2 9-tab guide it links, drawer
canary vs button) is replaced by a pointer to demo-authoring.md; the
domain canaries in s7 stay. demo-authoring's harness links now reach
into web/routes/uix/lib/ (the modules stay with the code); the deleted
pendiente_color_demos.md reference is marked TODO(reconcile). Corpus
swept: building-a-component (frontmatter related: + phases 6/8),
authoring, architecture/soma, theming/reference s5-parity,
testing-and-tooling, getting-started, README (E4 rows + component-audit
row added + two stale F7.3 paths in 'I want to' fixed). Remaining
warns = links to component-guide.md, which lands in F7.5 (2/2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
| 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).