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

347 lines
48 KiB

---
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),
> [`decisions/guia-semantica-historica.md`](../decisions/guia-semantica-historica.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`.
>
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
> 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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 |
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| A-2.2 | Every part declares `kebab`, `archetype`, `kind: 'public' \| 'private' \| 'virtual'` (`MorfoPartKind`), `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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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 the `{family}-{verb}[-{nuance}]` pattern (`validateMorfo` throws otherwise) | error | 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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)
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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 |
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 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 |
| 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 |
### B3 · Wrapper internals
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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. **`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 |
| E-3.7 | **No silent internal write** (catalogue invariant): every callback reporting writes to a bindable (`onValueChange` beside `value`) fires INSIDE that key's `{ get, set }` setter, and the provider only writes — it never invokes the callback itself (that double-fires). A path that writes the bindable without notifying is a defect: `bind:` and the callback would see different histories. ONE named exception: a coalesced (debounced) notification, which must flush on clear/submit and emit from a single private method — [`component-guide.md`](./component-guide.md) §Callback conventions | error | components with a bindable + change callback | manual |
| E-3.8 | The wrapper builds its bag with `bindProps<XOpts>` (target-typed — the provider EXPORTS its `Opts`) or, when the whole bag is `{ id, ref }`, with `partOpts`. No cast at the call site: a cast silences the whole bag | warn | all | manual |
---
## C. Recipe CSS
### C1 · State coverage
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| 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 |
| 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 |
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 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 |
| 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 |
### C2 · Token discipline
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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`).
feat(theming)!: R-5.1/5.2 a error contra el LEDGER DE DEUDA - el ratchet es por clave La pieza grande del CIERRE, con la forma firmada hoy: la deuda de alcance no se tolera en warn ni se disfraza de excepcion - se REGISTRA, clave a clave, y desde ahi la regresion es imposible y la mejora queda contada. - scripts/theming-census-debt.ts (NUEVO): 1088 claves (763 global + 325 literal, 74 componentes), clave `{clase} - {fichero} - {selector} - {propiedad}` INDEPENDIENTE de linea (mover una regla no fabrica regresion), comparacion MULTISET, generacion reproducible (dos corridas = mismo sha256), nace prettier-limpio. NO es un fichero de excepciones: es deuda registrada, la otra clase de acta - la valvula R-5.x exception de los README sigue intacta y NUNCA ciega el ratchet. Los carriles WIP ENTRAN (palabras 359 + chronos 209 = 568): la deuda es real viva donde viva, y dejarla fuera haria del gate una afirmacion sobre dos tercios del arbol. Con la salvedad MEDIDA de palabras escrita: sus nombres --palabras-* son canal de VALOR del scheme del documento, no contrato de tema - sus 103 "public" del censo estan en cuestion. - theming-census.ts: censusAudit() -> {newDebt, stale} + CLI --debt [--write] que imprime el delta que va a cometer (regenerar en masa borra el ratchet: el escritor grita y la cabecera lo prohibe sin firma). - theming-reach-floor.test.ts (reescrito): newDebt=0 y stale=0 con las claves NOMBRADAS; los techos burdos maxLiteral/maxGlobal RETIRADOS (superseded por el por-clave: 5 regresiones ya no se esconden bajo 5 arreglos); reachPct sube a 69 como ratchet grueso - y cubre el hueco nombrado: los 318 privados no-derivados siguen SIN ratchet por clave (acotado por la firma a literal|global; pendiente de firma propia); atHundred corrige su criterio (public>0, 14 -> 45: los 31 de diferencia eran denominadores vacios, ninguno un avance real). - component-audit.ts: filas R-5.1 y R-5.2 a ERROR consumiendo censusAudit() (dos implementaciones de una medida son dos medidas); R-5.2 honesto sobre los 18 sin-contrato (11 nada-que-declarar all-system/0-knobs; field-langs cubierto POR el ledger - la entrada ES su registro; 3 consumidores de capa calendar; mockup y text-scramble PASS con nota del idioma var(..,fallback) sin contrato - forma real sin nombre, pendiente de decision; palabras fuera del catalogo del audit). R-5.3 YA estaba en error (verificado, --names 0 desviadas). El skip por censo roto ahora GRITA por consola (la leccion del prepareWith: un guard saltado nunca es mudo - y el suelo de vitest queda de red mecanica). - docs: canon/recipe-contract.md SS4 y theming/reference.md SS12 reflejan la ley (gate F3 = censo 100% ADJUDICADO); completion-checklist gana las dos filas (exigido por el guard I5); el stub RECIPE_CONTRACT.md solo actualiza su linea de enforcement. Mutaciones, todas mordiendo: literal nuevo en mark -> newDebt lo nombra, suelo rojo, R-5.1 falla; clave de aura tokenizada -> STALE rojo hasta borrar la linea; literal sin registrar en field-langs -> R-5.2 muerde. Guards en HEAD: component:audit 162 PASS (cero flips; los 4 NEEDS-WORK son R-1.x ajenos), suelo 5/5, docs:check 0/0. BREAKING: los techos maxLiteral/maxGlobal del suelo desaparecen; anadir un literal o un global crudo a una receta exige desde ahora tokenizar, anotar /* literal: */ o firmar la entrada en el ledger de deuda. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 month 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 |
| 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 |
| R-5.1 | Every appearance declaration carries an act: a public `var(--{c}-…)` token, a `/* literal: <reason> */` annotation, or an entry in the debt ledger (`scripts/theming-census-debt.ts`). New debt outside the ledger fails; a ledger entry whose knob is no longer debt is STALE and fails until the line is deleted | error | all | audit |
| R-5.2 | A recipe with themeable knobs declares them in `lib/recipes/base.ts` — unless it has nothing to declare (all `system` / `structural`) or every one of its knobs is registered in the debt ledger | error | all | audit |
| R-5.3 | Recipe token keys follow the naming grammar: the ink slot is `fg` (never `color`), and the interactive modifier goes IN FRONT (`hover-bg`) while size, orientation and context stay behind (`control-height-md`) — recipe-contract §1 | error | all | audit |
---
## D. Demo page (`web/routes/uix/components/{kebab}/+page.svelte`)
### D1 · Template compliance
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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 |
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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)
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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)
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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)
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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 |
| 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 |
---
## F. Documentation completeness (component README)
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| ID | Rule | Severity | Applicability | Enforcement |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------- | ----------- |
| 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 |
| 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
feat(audit): R-5.3 — la gramática de los nombres deja de ser prosa El canon de nombres llevaba meses documentado y sin guard, y la medición del 2026-07-01 ya decía qué le pasa a un canon así: deriva entre el 30 y el 85 %. Había derivado. El codemod de ayer lo normalizó; esto es lo que impide que vuelva. R-5.3 valida una FORMA, no una lista — ahí se separa del guard de eventos, que comprueba pertenencia a un vocabulario cerrado. La forma es: tinta = `fg`, modificador interactivo DELANTE, y detrás lo dimensional y contextual. No reimplementa la gramática: la consume de `theming-census --names`, que es la misma fuente sobre la que corrió el codemod. Dos implementaciones de una gramática son dos gramáticas que acaban discrepando — y este repo ya pagó esa factura con `commit-resize`, un hook muerto tres meses en una receta con todos los tests en verde. Entra en `error` directo, sin rampa `warn`, porque su deuda murió en el mismo pass (precedente R-4.4). Las claves en cola de migración a la capa de estado se reportan APARTE: no son deuda de nombre, son knobs que van a desaparecer, y renombrar lo condenado es churn. Muta-prueba de tres caras, que es lo único que distingue un guard de un guard que pasa sobre el vacío: `-bg-hover` con valor de acento → ROJO `trigger-color` → ROJO `primary-solid-hover` → VERDE (canónica: COLOR_ROLE_SLOTS pone el modificador detrás por construcción) La tercera es la que importa: es el fallo que un codemod ingenuo habría cometido sobre las 47 claves de rol, `button` entero incluido. Doctrina en el mismo pass: recipe-contract §1 gana las dos filas que le faltaban (tinta y estado) más la frase que las gobierna y las dos familias con gramática propia; §4 gana la fila R-5.3; theming §6.7 una nota fechada que acota el principio de plataforma del px/py a los ejes dimensionales. El checklist de cierre declara la regla — lo cazó `docs:check` con su propio guard I5, que exige que toda regla del audit esté declarada allí. Lo que NO entra, y por qué: el tercer muro (el tipo en `defineRecipes`, molde `PhysicalAxisKey`) está escrito y probado, y dispara sobre 17 claves — los hovers neutros que la firma 3 manda migrar. Meterlo hoy rompería `npm run check` a todo el mundo por una deuda que ya tiene dueño y fecha. Entra cuando la migración a la capa de estado las vacíe; son dos líneas entonces. component:audit 163 PASS · 3 NEEDS-WORK (badge, mockup, motion — los tres sin tocar por esto, R-5.3 pasa en los 166) docs:check 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 months ago
| 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.