feat(eidos): recipe contract R-4.x + motion-channel migration 15/15 + theme-builder fixes + inventory decisions
Fable audit follow-through (fable_audit.md + fable-eidos-audit.md):
- RECIPE_CONTRACT.md (E2 canon): the transversal systems every recipe must
consume, enforced by component-audit R-4.1-4.6 (all at error; escape
valves /* literal */ + /* functional */; WIP tracks excluded). Stale
audit rules fixed against the current architecture (E-2.2 wrapper
imports, D-1.2 v2 9-tab union, D-3.1 single snippet, TabsVariant
mirror) - verdicts went 0/117/15 -> 75/50/5.
- Motion migration 15/15: recipes off local @keyframes onto the channel -
preset stamps (dropdown/context/select/combobox/tooltip/link-preview/
clipboard), new expand/collapse + value-flash signatures, shared-axis
reverse pair, delayed-open open-alias in the preset trigger
(PRESET_STATE_ALIASES), materials pattern for irreducible triggers
(card/timeline/tabs/nav-menu/metrics). Duration/scale hooks keep every
recipe's tuned values.
- buildScheme (fase C): full a1..a12 alpha ramp per role (was a2/a3 -
stale alphas after applyColorScheme), intentSeeds so temper starts from
the ACTIVE theme's intent mapping (risk stays orange), alpha background
self-derived from the scheme's own neutral step 1 (was hardcoded
#fff/#111); mode now forces the donor variant.
- Inventory decisions (fase D): semanticTracking axis removed (all-zero),
border-hover slot dropped (0 consumers), separator slot adopted across
line dividers (step 6, Radix divider tone), size-bundle consumption
pilot on toggle (canonical coordinates consumed, deliberate deviations
kept visible).
- Docs: building-a-component.md (the one door, 9-phase route + known
traps), PLAN-docs-reconciliation.md (fase 6 kickoff for a fresh
session), THEMING wiring updates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
# Auditoría Fable — sistema activeUIX (morfo · soma · sema · eidos)
> **Fecha**: 2026-07-01 · **Rama**: `alpha-0.1-sec-dom` · **Auditor**: Claude Fable 5
> **Método**: lectura directa (sin agentes) de la documentación troncal (docs/README, CANON,
> active_architecture, READMEs de capa, GUIA_IMPLEMENTACION_SEMAUIX, LIBRO_VARIACIONES, TSC,
> THEMING_NOTES, comparison, decisions, active-uix) y del código fuente núcleo de las 4 capas
> (morfo: types/compile/schema/resolver/selectors · sema: types/engine/resolver/sema-map/
> stamp/projection/durations/chans · soma: runtime.svelte.ts · eidos: active-eidos.svelte.ts +
> lib/types), más verificaciones puntuales por grep sobre los 136 morfos, 48 packs sema y
> package.json. Se excluyeron pendientes, CONTINUEs y auditorías previas, según encargo.
>
> Cada hallazgo indica dónde se verificó. Severidades: **P0** corrección/riesgo real ·
> **P1** deuda de diseño con impacto · **P2** mejora clara · **P3** higiene.
---
## 1. Resumen ejecutivo
El sistema de cuatro capas es **arquitectónicamente sólido y genuinamente original** : el
contrato declarativo (morfo) consumido por compilación tipada, la capa perceptiva (sema) con
cascada CSS-like sobre tokens `data-event-*` , el runtime headless (soma) que transcribe el
morfo, y la capa visual (eidos) con el Token Scope Contract validado en generación. Ninguna
librería mainstream tiene ese conjunto. Las fronteras de capa se respetan casi siempre en el
código real (verificado: 188 usos de `semaSelector` y **cero** selectores manuscritos en los
packs sema; providers migrados a `renderProps` /`syncAttrs`; eidos no importa internals de soma).
Los problemas encontrados **no son de concepto sino de tres tipos** :
1. **Huecos de implementación en la ejecución perceptiva** — concurrencia de señales sin
gobernar (el `regime` declarado no lo consume nadie), `sequence: 'coincident'` sin
implementar, precedencia invertida del fallback de reduced-motion, y un default
(`sequence: 'pre'`) que ya causó dos clases de incidentes documentados.
2. **Contrato que promete más de lo que ejecuta** — eventos declarados que nunca se emiten
(los 5 pickers), `commits` puramente descriptivo, `mode` /`regime`/`scope` muertos. El
propio LIBRO_VARIACIONES (D.2) reconoce el problema y propone el flag `emission` , aún sin
implementar.
3. **Drift documental significativo** — el corpus es enorme y varias fuentes "autoritativas"
contradicen al código (campo `translations` vs `texts` , 24 vs 26 archetypes, familia del
Dialog, tabla de holds, dependencia de @floating -ui ya retirada, numeración de capas de la
cascada). Irónico en un framework cuyo argumento central es "el contrato no drifta".
Valoración global: **arquitectura 9/10 · implementación 8/10 · coherencia doc↔código 6.5/10
· madurez de producto 6/10**. Detalle en §6.
---
## 2. Fortalezas verificadas (lo que está bien y hay que proteger)
- **Morfo como contrato compilado** (`compile.ts`): pre-resolución de `staticAttrs` vs
`dynamicAttrs` con `AttrPlan` discriminado por `mode` , deps por parte, keyboard parseado,
contrato CSS (`contracts.cssSelectors`) derivado — cache por identidad (WeakMap) con HMR
gratis. Diseño de compilador limpio y bien comentado.
- **`as const satisfies Morfo`** como patrón obligatorio: los typos de parte/evento son
errores de compilación reales. Verificado que `createAttrs` y `semaSelector` explotan los
literales.
- **Disciplina del typed builder cumplida al 100%**: 47 packs sema, 188 llamadas a
`semaSelector` , 0 strings manuscritos (grep). La defensa anti-drift funciona donde se aplicó.
- **TSC (Token Scope Contract)**: el álgebra de scopes con validación transitiva y detección
de colisiones cross-axis resuelve un bug real de CSS (substitución eager de custom
properties) que ningún sistema de referencia cierra estructuralmente. Con v2.2 (multi-part
+ composition) la cobertura es universal (15/15 componentes con `data-color` ). Es la pieza
más defendible del framework junto a sema.
- **Contratos ejecutables** (`src/uix/contracts.ts`): la tabla de ownership/degradación es
dato + tipos (`UIX_TYPE_CONTRACTS` con checks `Extends<>` ), no prosa. Rarísimo verlo.
- **Sema ornamental de verdad**: `eventEngine` opcional en el runtime, canales sound/haptic
opt-in, errores de canal absorbidos con logger, prepare-time priming del AudioContext dentro
del gesto. La degradación SSR/headless está pensada.
- **Persistence + a11ySemantic + polymorphic close**: los tres ítems del libro (§6, §9, §5.3)
están implementados con tests y consumidores reales — no es vaporware doctrinal.
- **Cobertura de tests**: todos los providers soma tienen test directo (guardia
`NO_MISSING_PROVIDER_TESTS` ); ~1900 tests verdes según el último hand-off; motores puros
extraídos a `$libs` (datagrid/forms/strings) — buena separación testable.
- **Tooling de contrato**: `morfo:check` , `morfo:vocabulary` (hard-fail en verbos fuera de
canon), `translations:check` , `component:audit` , `eidos-lint` , `recipe-css-contract.test.ts`
(guardias bidireccionales alias↔CSS). La doctrina "cada fix sistémico lleva guard" se cumple.
- **Timers perceptivos gestionados**: hold/delay/earcon corren en `uix.timers` (cancelable,
determinista bajo fake-clock), no `setTimeout` crudo.
---
## 3. Hallazgos por capa
### 3.1 SEMA
**S1 · P0 — Race de señales solapadas sobre el mismo target.**
`DomSignalProjector.cleanup()` (projection/dom.ts) llama `unstampEventAttrs` , que borra los
**cinco** `data-event-*` sin comprobar `data-event-id` . Si el mismo target emite una segunda
señal antes de que expire el hold de la primera (toggle spam, `handle-drag` continuo,
re-warn de validación), el cleanup de la señal vieja **borra la proyección de la nueva a
mitad de hold**: eidos pierde la animación y cualquier cascade que se resuelva después
matchea contra un DOM vacío. El mismo defecto afecta a señales persistentes (`untilFix`)
re-emitidas sin `clearTarget` previo (hoy es convención de provider, no está forzado).
*Fix mínimo*: guard en cleanup — solo unstamp si `target.getAttribute('data-event-id') ===
signal.id`. *Fix completo* : ver S2.
**S2 · P1 — `regime` / `mode` / `scope` son contrato muerto.**
Declarados en `MorfoEvent` , compilados a `ActionPlan` (compile.ts:620), y **ningún consumidor
en runtime** (grep exhaustivo). Los overlays declaran `regime: 'lock'` que no hace nada. El
vocabulario `replace | collapse | lock | queue` es exactamente la política de concurrencia
que S1 necesita — implementarlo mataría dos pájaros. Si no se va a implementar a corto plazo,
marcar los campos como reserved/no-op en types.ts para que el contrato no mienta.
**S3 · P1 — `sequence: 'coincident'` no está implementado.**
runtime.svelte.ts:749 trata `coincident` idéntico a `pre` (emit awaited con hold completo
ANTES del handler). La doctrina (GUIA §5.2) lo define como "señal durante el proceso"
(sustain) — que es precisamente el caso donde bloquear el handler ~600ms (hold `noticed` de
sustain) es incorrecto. Es la misma clase de bug que el checkbox-lag, latente en la tercera
rama del enum. *Propuesta* : `coincident` = fire-and-forget del emit (no await) + handler
inmediato.
**S4 · P1 — El default `sequence: 'pre'` es un footgun demostrado.**
Dos clases de incidente ya documentadas (checkbox 244ms de lag; apertura de overlays gateada
tras el hold) provienen del mismo patrón: default `'pre'` + estado fijado en el handler. La
doctrina correctiva existe pero vive en notas de sesión. *Propuesta* : lint en
`morfo:vocabulary` — WARN cuando un evento `'pre'` (explícito o por default) tiene handler
registrado en el provider y no es de familia emerge-close; o cambiar el default por familia
(commit → `post` ).
**S5 · P2 — Precedencia del fallback reduced-motion invertida.**
runtime.svelte.ts:690: `opts.channels ?? action.semantic.channels ?? a11yChannelsOverride` .
Un morfo que declara `channels: ['sound']` para silenciar haptic **derrota silenciosamente**
el `reducedMotionFallback: 'state'` (que fuerza `channels: []` ). El comentario lo declara
deliberado ("authors can still force motion"), pero mezcla dos intenciones: afinar canales ≠
anular una preferencia de accesibilidad del usuario. La preferencia del usuario debería
ganar (intersección, no fallback): `channels = a11ySilence ? [] : (opts ?? morfo)` .
**S6 · P2 — El sound base de `contact` contradice la doctrina de fatiga.**
`SEMA_MAP.families.contact.base.sound.gain = 0.25` hace audible por defecto la familia más
frecuente del sistema. El incidente Palabras (EV-G) se "resolvió" dejando de emitir el evento —
feat(eidos): recipe contract R-4.x + motion-channel migration 15/15 + theme-builder fixes + inventory decisions
Fable audit follow-through (fable_audit.md + fable-eidos-audit.md):
- RECIPE_CONTRACT.md (E2 canon): the transversal systems every recipe must
consume, enforced by component-audit R-4.1-4.6 (all at error; escape
valves /* literal */ + /* functional */; WIP tracks excluded). Stale
audit rules fixed against the current architecture (E-2.2 wrapper
imports, D-1.2 v2 9-tab union, D-3.1 single snippet, TabsVariant
mirror) - verdicts went 0/117/15 -> 75/50/5.
- Motion migration 15/15: recipes off local @keyframes onto the channel -
preset stamps (dropdown/context/select/combobox/tooltip/link-preview/
clipboard), new expand/collapse + value-flash signatures, shared-axis
reverse pair, delayed-open open-alias in the preset trigger
(PRESET_STATE_ALIASES), materials pattern for irreducible triggers
(card/timeline/tabs/nav-menu/metrics). Duration/scale hooks keep every
recipe's tuned values.
- buildScheme (fase C): full a1..a12 alpha ramp per role (was a2/a3 -
stale alphas after applyColorScheme), intentSeeds so temper starts from
the ACTIVE theme's intent mapping (risk stays orange), alpha background
self-derived from the scheme's own neutral step 1 (was hardcoded
#fff/#111); mode now forces the donor variant.
- Inventory decisions (fase D): semanticTracking axis removed (all-zero),
border-hover slot dropped (0 consumers), separator slot adopted across
line dividers (step 6, Radix divider tone), size-bundle consumption
pilot on toggle (canonical coordinates consumed, deliberate deviations
kept visible).
- Docs: building-a-component.md (the one door, 9-phase route + known
traps), PLAN-docs-reconciliation.md (fase 6 kickoff for a fresh
session), THEMING wiring updates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 months ago
perdiendo telemetría — porque el base suena aunque no haya pack. D.7 dice "en eventos
frecuentes la prioridad es evitar fatiga; el silencio es una firma válida". *Propuesta* :
contact base gain ≈ 0 (o `activeChannels: ['haptic']` ) y que los packs opt-in suban el sonido
— invierte la carga de la prueba y elimina la clase entera de workaround.
**S7 · P3 — `safeMatches` matchea también por `closest()` .**
resolver.ts:200: una rule cuyo selector matchea un **ancestro** del target también aplica.
Útil para scoping (`#dialog [data-x]`), pero significa que una rule sin constraints
`[data-event-*]` (solo state attrs) puede capturar señales de cualquier descendiente. No
está documentado en el README de sema; documentarlo o restringir closest a selectores con
combinador.
**S8 · P3 — Numeración de la cascada inconsistente entre docs.**
README sema: "cascada de 5 capas" (1– 5). engine.ts: capas 1– 5 con otra asignación (runtime=4,
cascade=5) y luego "Layer 5/6" en comentarios. CLAUDE.md: 6 capas con 5a/5b. resolver.ts:
"layers 4 + 6". Elegir UNA numeración canónica (sugerencia: 1 family · 2 intent · 3 morfo ·
4 runtime · 5a packs · 5b app-cascade) y barrer.
**S9 · P3 — Higiene menor**: `stamp.ts` /`durations.ts`/`projection/dom.ts` usan estilo
sin punto y coma, distinto del resto (Prettier no lo unifica); `cssSpecificity` no modela
combinadores/`:is()` (documentado, aceptable); `applyOverride` clona con `structuredClone`
por rule matcheada — coste O(rules·señal) tolerable pero medible en handle-drag continuo.
### 3.2 SOMA
**SO1 · P1 — Doctrina "handlers síncronos" vs `await handler()` .**
Regla dura nº 9 ("los handlers de events son síncronos; async va antes del trigger") vs
runtime.svelte.ts:742/752: `await handler()` . El runtime soporta silenciosamente handlers
async, y en `post` el emit espera a que terminen — comportamiento razonable pero
contradictorio con la doctrina escrita. Decidir: o el runtime NO awaita (enforcement), o se
actualiza la regla (los hand-offs sugieren que el await de `post` es deseado).
**SO2 · P2 — `opts.semantic` sobre evento no-polimórfico se ignora en silencio.**
El JSDoc de `TriggerOptions.semantic` promete "pass-through warns via logger if present";
la implementación no loguea nada (runtime.svelte.ts:622 — el branch solo entra si
`isPolymorphicSemantic` ). Un provider que pasa semantic a un evento concreto cree haber
sobreescrito la familia y no lo ha hecho. Implementar el warn (hay `sources.logger` ).
**SO3 · P2 — JSDoc desactualizado respecto al diseño aditivo.**
El mismo JSDoc habla de `allowedFamilies` + `defaultSemantic` — `defaultSemantic` no existe
(D.11 eligió el shape aditivo). Igual en morfo/types.ts comentarios. Barrer referencias.
**SO4 · P3 — `prewrite` se aplica antes de validar el target del emit.**
Si el target no está en DOM, `SomaRuntimeTargetError` se lanza DESPUÉS de haber escrito los
prewrite (`data-last-action`) — estado a medias sobre el DOM. Reordenar validación o revertir
prewrites en el throw.
**SO5 · P3 — `runA11y` recalcula `snapshotRootProps()` /`resolveIntent` ya resueltos** en
`runEmit` — micro-duplicación; y la indentación de runtime.svelte.ts:667-668 está rota
(formato).
**SO6 · P3 — Docs de soma citan dependencias retiradas.**
README §3 y SOMA_ARCHITECTURE §6/§12 declaran dependencia de `@floating-ui` (hoy devDep;
el positioning es el motor propio `$ethereal` desde 56eb9bd2) y de `clsx` (no está en
package.json). También la tabla de layers omite `popper` /`stacking`/`manipulation`/`zoom-pan`
que existen en `layers/` . Actualizar — es la puerta de entrada de la capa.
### 3.3 MORFO
**M1 · P1 — Eventos declarados que nunca se emiten (contrato que miente).**
Los 5 pickers declaran `close` polimórfico que ningún provider dispara (hallazgo propio de
D.11: "eventos inertes"); `commits` es descriptivo sin validador activo más allá del smoke.
El autor ya aceptó la solución (D.2: flag `emission: 'runtime' | 'declared-only' | …` ) pero
sigue pendiente. Mientras no exista, la tabla de eventos de los READMEs y el contrato público
prometen percepción que no ocurre. *Acción* : implementar `emission` en `MorfoEvent` +
lint que exija `declared-only` explícito donde no haya `runtime.trigger` correspondiente, o
cablear los triggers de pickers.
**M2 · P2 — `expression` cubre solo la mitad del catálogo.**
82 morfos declaran `events` , solo 41 declaran `expression` (grep). El campo se creó para que
"la ausencia de pack nunca se confunda con olvido" — con un WARN de lint y 50% de cobertura,
la ambigüedad que quería eliminar sigue viva. Subir a FAIL tras una pasada de anotación.
**M3 · P2 — Excepción no-reconocida a la regla 2-de-3.**
`parts[].keyboard` es soma-only (la propia tabla del README lo marca "✅ (was already
there)"). O se documenta como grandfathered explícito o se argumenta el segundo consumidor
(docs lo consume para tablas — sería 2-de-3 si docs cuenta como capa, pero entonces la regla
necesita redefinirse). Tal cual, la regla estrella tiene un asterisco silencioso.
**M4 · P2 — `enumPair` infiere mal enums de 3+ valores.**
compile.ts:508: para `data-state-derived` , `falseLabel = enumValues.find(v => v !== trueLabel)`
— con `['checked','unchecked','indeterminate']` y stateRef booleano, el false-label depende
del ORDEN de declaración. Hoy no muerde (los stateRef booleanos usan enums binarios), pero es
una inferencia frágil sin validación en schema. Añadir check: stateRef+enum exige exactamente
2 valores, o mapeo explícito.
**M5 · P3 — `semaSelector` puede tipar `state` ya.**
El matcher `state` /`aria` acepta strings sueltos "porque el vocabulario data-attr no está
derivado del morfo" — pero `compiled.contracts.dataAttrsByPart` YA contiene attr + enum por
parte. La tipificación prometida es implementable hoy sin nueva infraestructura.
**M6 · P3 — Higiene**: `_resetCompileCache()` es un no-op vacío con nombre engañoso
(eliminar); aliases deprecados `IntentExpectedFamily` /`SemaEventLabel` siguen importados por
código nuevo (morfo/types.ts importa `IntentExpectedFamily` , no el canónico
`IntentRequiredFamily` ); el doble `collectSourceDeps` con cast `as never` (compile.ts:409)
merece el helper real.
### 3.4 EIDOS
**E1 · P1 — `ActiveEidos` es una god-class con `apply()` de fuerza bruta.**
1.254 líneas: 6 builders runtime + persistencia + render + proyección de attrs + resolución
de tokens. Cada `apply()` re-renderiza **todo** (incluido `renderStaticCss()` completo, que
genera la foundation entera) aunque solo cambió un axis; `applyTheme` construye cada builder
**dos veces** (una en `apply()` →`#renderXCss`, otra para el resultado devuelto). Un theme
editor con slider (caso de uso anunciado: "vivacidad P3") paga regeneración total de CSS por
evento de input. *Acciones* : (a) memoizar `renderStaticCss` por identidad de config +
breakpoints; (b) que `apply()` acepte un scope (`'scheme' | 'type' | …`) y solo reescriba ese
bloque; (c) reutilizar el resultado ya construido en `applyTheme` . Separar los builders a un
`EidosRuntimeBuilders` colaborador reduciría la clase a su rol de runtime/contexto.
**E2 · P2 — `createSystemColorSchemeSource` viola la regla DOM del propio framework.**
active-eidos.svelte.ts:1232-1254 usa `globalThis.matchMedia` + `addEventListener` crudos. La
regla dura ("listeners de document/window pasan por ActiveDom") y el precedente
(`ActiveDom.prefersReducedMotion` como tracker) exigen que el mode-source por defecto viva en
adom (p.ej. `dom.prefersColorScheme` ) o al menos use `dom.listen` .
**E3 · P2 — `#renderSchemeCss` traga errores en silencio.**
Si `#buildSchemeResult()` lanza durante un re-derive (cambio de modo con seed inválido para
las donor scales), el catch devuelve `''` y el bloque scheme **desaparece sin log** . Al menos
`logger.warn` con la causa; idealmente conservar el bloque anterior (transaccionalidad que
`setCssVariables` sí tiene y aquí falta).
**E4 · P2 — El bundle `--size-*` sigue sin consumidores (63 tokens huérfanos).**
Reconocido en THEMING_NOTES (⚠️ "canon + guard, no consumo") y en el inventario
reference-grade. El guard evita deriva, pero la promesa "map global coordinado" del README es
hoy solo emisión. Decidir: refactor de recipes a consumir `--size-{k}-*` o degradar la
promesa en docs.
**E5 · P3 — Capa 4 del token stack (component-color) sin veredicto.**
THEMING_NOTES la marca "candidata a deprecate si en 6 meses nadie la usa" — el plazo va
corriendo desde ~2026-05. Revisar consumo y decidir.
**E6 · P3 — Pendientes declarados del propio doc**: primer caso real de `scope: 'event:*'`
(integración TSC↔sema aún sin consumidor), migración de los 5 palette-consumers a
`declarations[]` . Son las dos costuras donde la "integración con sema" del theming es
aspiracional.
### 3.5 Transversal / documentación
**D1 · P1 — `morfo.translations` → `texts` : los docs de la capa no migraron.**
El tipo `Morfo` solo tiene `texts` (97 morfos lo usan; 0 usan `translations` ). Pero
morfo/README.md (secciones "What morfo contains", Step 4.5, anatomía, pitfalls),
SOMA_ARCHITECTURE §7 y soma/README §4 documentan `translations:` con ejemplos completos que
**no compilan**. Es el drift más grave porque afecta al onboarding directo de la capa
contrato. (active_architecture §0.1 sí registra el rename — el barrido quedó a medias.)
**D2 · P1 — GUIA_IMPLEMENTACION_SEMAUIX (declarada "autoritativa") diverge del código en
al menos 5 puntos**: (1) §4.1 Modal/Dialog = `shift.enter-mode` ; el morfo real usa `emerge`
(open y close; `allowedFamilies` sin `shift` ). (2) §6.2 tabla de holds (contact 120 /
emerge 180 / shift 240 / commit por-intent) ≠ `SEMA_MAP` (emerge 240, shift 600, commit plano
240) ≠ `SEMA_HOLDS_BY_INTENT` (holds.ts). Tres tablas, tres verdades. (3) §2 declara "sistema
de 8 tokens" sin `tertiary` ; eidos tiene 9 roles canónicos. (4) §2.4 usa naming
`--color-{role}-element` pre-actual. (5) §5.3 documenta el shape `defaultSemantic` que D.11
descartó. Dado que CLAUDE.md la señala como fuente autoritativa "cuando haya dudas", esto
produce decisiones incorrectas en cada sesión nueva. *Acción* : pasada de reconciliación o
degradar su autoridad a favor de CANON.md + código (que ya es la regla en CANON §sources).
**D3 · P2 — Archetypes: 24 en docs, 26 en código.**
`ARCHETYPE_VOCABULARY` tiene 26 (se añadieron `field-trigger` y `footer` );
active_architecture §6 y morfo/README dicen "24" y listan sin los nuevos. Ejemplo canónico de
por qué CANON.md prohíbe copiar listas — estas dos copias sobrevivieron.
**D4 · P2 — Doctrina "zero dependence" vs realidad.**
La doctrina del proyecto (memoria de feedback: "framework depends on NOBODY") convive con
runtime deps reales: `runed` , `tabbable` , `ogl` , `csstype` , `esm-env` . runed/tabbable son
razonables pero contradicen la doctrina enunciada; `ogl` (WebGL) es pesada y merece
justificación explícita o aislamiento en el art que la use. Documentar la política real
(p.ej. "cero deps en uix/*; arts pueden declarar deps justificadas") y alinear soma/README.
**D5 · P3 — Volumen del corpus como riesgo estructural.**
El proyecto ya diagnosticó el patrón ("7 families sobrevivió en tres docs") y creó CANON.md.
Esta auditoría encontró la misma clase de drift en 6 sitios más (D1– D4, S8, SO6). La regla
"link, don't copy" existe pero no hay guard mecánico. *Propuesta* : `docs:check` — script que
verifique invariantes copiables (conteos de vocabularios, nombres de campos de tipos citados
en docs, deps citadas vs package.json) igual que `translations:check` hace con idlangrefs.
---
## 4. Ampliaciones recomendadas (no defectos)
1. **Slots semánticos V2** (GUIA §8.2): hoy una señal por target; las composiciones
simultáneas del libro (`shift.enter-mode + signal.alert + threat`) no son expresables.
Con S1/S2 resueltos, `data-event-frame` / `data-event-signal` es la evolución natural.
2. **Builder tipado para recipes CSS** — el propio eidos/README lo anuncia ("cuando los
recipes migren a un builder, el lint podrá retirarse"). Cerraría la última superficie de
drift no tipada del sistema. Candidato: generar los selectores de estado desde
`compiled.contracts.cssSelectors` como constantes exportadas por componente.
3. **Aplicación automática de `SEMA_HOLDS_BY_INTENT`** — hoy la tabla canónica es
"referencia" y cada morfo re-declara persistence a mano (decisión conservadora razonable
en su día). Tras estabilizar, un opt-in `holdsPolicy: 'canonical'` en el engine evitaría
la divergencia silenciosa entre tabla y morfos.
4. **Lint de `intentGuidance`** — el eje doctrinal (`discouraged` en contact) se declaró
"possible lint in the future"; `morfo:vocabulary` ya tiene el sitio natural.
5. ** `uix.perf` + presupuesto de emit** — medir el coste real de `resolveSignature`
(structuredClone por rule) bajo handle-drag continuo; si aparece en LoAF, cachear la
signature por (family,intent,channels-hash) cuando no hay cascade dinámica.
6. **Docs: guía de adopción incremental** — comparison.md admite "more to learn"; falta el
camino "usa solo soma headless sin sema/eidos" como tutorial de primera hora, que es como
compiten Radix/Ark.
---
## 5. Incoherencias menores registradas (lista rápida)
- `EventEngineEmitter` permite fakes sin `clear` /`clearTarget` → señales persistentes que
"lingeran" en tests: documentado, OK, pero un fake-helper oficial evitaría 40 fakes ad-hoc.
- `partProps()` devuelve `{}` para partes no registradas (silencioso) mientras `part()` lanza
— asimetría de errores razonable pero no documentada.
- `resolveSignature` acepta `SemaMap | SemaResolveOptions` con type-sniffing
(`isResolveOptions`) — API histórica; unificar en options-only en la próxima major.
- eidos `README` "Estado actual (2026-05-17)" está congelado (la lista de wrappers migrados
es ~20; el catálogo real es 137 dirs) — sección fechada dentro de un doc atemporal,
contradice docs/authoring.md.
- `MorfoElement` es unión cerrada de ~45 tags: cada elemento nuevo (p.ej. `dialog` ,
`canvas` , `picture` ) exige tocar el tipo; valorar `(string & {})` escape con lint.
---
## 6. Valoración del framework frente a los existentes
### Posicionamiento
El campo se divide en headless (Radix Primitives, Ark UI, bits-ui, React Aria) y styled
systems (Mantine, Chakra, Radix Themes, shadcn). activeUIX mantiene la separación estricta de
los headless y añade dos capas que **nadie más tiene** : un contrato declarativo por encima
del comportamiento (morfo) y una capa de percepción por debajo de lo visual (sema). Esa
lectura de comparison.md es honesta y esta auditoría la confirma contra el código.
### Dónde gana (diferenciadores reales, verificados)
| Dimensión | activeUIX | Mejor referencia | Veredicto |
|---|---|---|---|
| Contrato estructural único (parts/ARIA/keyboard/events) | morfo compilado + tipado literal | nadie (disperso en provider+docs+CSS) | ** Único**. El rename-rompe-en-compile es real. |
| Capa perceptiva (familias/intents/verbos + sound/haptic/hold) | sema + cascada CSS-like | nadie | ** Único**. Fundamentado en un libro propio; con packs por componente. |
| Scope de tokens validado en generación | TSC v2.2 (álgebra + colisiones cross-axis) | Panda (parcial, build-time) | **Mejor de clase** . Cierra un bug de CSS que los demás dejan al azar. |
| Motion de dos momentos (`--event`/`--state`) | integrado con la firma perceptiva | Chakra (solo data-state) | ** Único** en la integración. |
| Theme = retintar lo fijo (variants/roles canon) | `EIDOS_VARIANTS` + 9 roles + lint | Radix Themes (accents) | Más portable; menos libre — trade-off deliberado y coherente. |
| Builders runtime (color APCA/OKLCH-P3, type, depth, shape, space desde 1 seed) | `applyTheme` atómico | Material You (color solo) | Muy por delante de las libs de componentes. |
| Degradación (SSR / sin audio / sin DOM) | contratos ejecutables por módulo | React Aria (buena) | Comparable o mejor, y además *verificable* (contracts.test). |
| a11y semántica por evento (live region / focus / reduced-motion fallback) | declarada en morfo, honrada por runtime | React Aria (imperativa) | Enfoque más declarativo; con el matiz S5 a corregir. |
| Bundle (purged) | 13.6 KB gz | Tailwind 10 / Panda 12 | Competitivo — sorprendente para lo que expresa. |
### Dónde pierde (costes honestos)
1. **Carga conceptual** : 4 capas + 8 familias + 6 intents + verbos + archetypes + TSC + el
vocabulario del libro. Radix se aprende en una tarde; esto exige leer un corpus. El
glossary/getting-started mitigan, pero el precio existe y comparison.md lo admite.
2. **Ecosistema y battle-testing** : un codebase, un autor-equipo, sin comunidad, sin años de
issues cerrados. Los headless mainstream llevan ventaja de miles de apps en producción.
3. **Svelte 5 only** : el morfo y sema son portables en teoría (TS puro + DOM), pero soma está
casado con runes. Frente a Ark (universal via Zag) es una limitación de mercado.
4. **Promesas aún no forzadas** : eventos declarados-no-emitidos (M1), regime muerto (S2),
holds de referencia no aplicados — el framework vende "contrato = verdad" y en esos puntos
todavía no lo es. Es exactamente el flanco que un evaluador externo atacaría.
5. **Coste de mantenimiento documental** : el corpus (docenas de docs E0– E5 + READMEs por
componente) drifta más rápido de lo que se barre (sección 3.5). Los frameworks de
referencia tienen menos doctrina que mantener sincronizada.
6. **La capa sema solo paga si se usa** : para un consumidor que quiere "widgets accesibles
bonitos", el sistema entrega lo mismo que los demás con más conceptos por delante.
### Veredicto
Como **arquitectura de investigación aplicada** (separar contrato / comportamiento /
significado / materia visual), activeUIX está por delante de todo lo publicado: es la única
implementación seria de una *gramática perceptiva* sobre componentes, y el TSC + morfo son
contribuciones exportables por sí solas. Como **producto adoptable hoy** , está en alpha
honesta: la ejecución de las 4 capas es de calidad alta (mejor de lo habitual en proyectos de
esta ambición), pero la concurrencia perceptiva (S1– S4), el contrato de emisión (M1) y la
higiene documental (D1– D2) tienen que cerrarse antes de que la promesa central — "el contrato
no puede mentir" — sea cierta sin asteriscos.
**Puntuaciones** (escala honesta, no de marketing):
originalidad **10** · arquitectura **9** · implementación **8** · testing/tooling **8.5** ·
docs (coherencia) **6.5** · a11y **8** (8.5 tras S5) · rendimiento **7.5** (E1, S9) ·
adoptabilidad externa **5** · madurez global **7** .
---
## 7. Plan de acción priorizado
### Fase 0 — Corrección (1– 2 sesiones)
| # | Acción | Cierra | Verificación |
|---|---|---|---|
| 0.1 | Guard por `data-event-id` en `DomSignalProjector.cleanup` (unstamp solo si el id sigue siendo el propio) + test de solape (emit B durante hold de A) | S1 | test nuevo en `projection/dom.test.ts` + `emit.test.ts` |
| 0.2 | Precedencia a11y: `reducedMotionFallback:'state'` interseca (no cede ante) `channels` de morfo/opts | S5 | test en runtime.svelte.test |
| 0.3 | Warn de logger al pasar `opts.semantic` a evento no-polimórfico (cumplir el JSDoc) | SO2 | test |
| 0.4 | `#renderSchemeCss` : log del error + conservar bloque anterior en fallo de re-derive | E3 | test |
### Fase 1 — Contrato honesto (2– 4 sesiones)
| # | Acción | Cierra |
|---|---|---|
| 1.1 | Implementar `emission: 'runtime' \| 'declared-only'` en `MorfoEvent` + lint FAIL cuando un evento sin `emission` no tiene trigger localizable; anotar los 5 pickers (o cablear sus triggers) | M1, D.2 del LIBRO |
| 1.2 | Decisión sobre `regime` : implementar `replace` (mínimo viable: nueva señal sobre el mismo target cancela la anterior — resuelve S1 estructuralmente) y marcar `mode` /`scope`/resto de regimes como reserved en types | S2, S1 |
| 1.3 | Implementar `coincident` (emit no bloqueante) + lint `'pre'` -con-handler | S3, S4 |
| 1.4 | Resolver doctrina handlers sync: no-await o actualizar regla escrita | SO1 |
| 1.5 | Subir `expression` a FAIL tras anotar los ~41 morfos restantes | M2 |
### Fase 2 — Barrido documental (1– 2 sesiones, mecánico)
| # | Acción | Cierra |
|---|---|---|
| 2.1 | `translations:` → `texts:` en morfo/README, SOMA_ARCHITECTURE, soma/README (ejemplos compilables) | D1 |
| 2.2 | Reconciliar GUIA_IMPLEMENTACION con código (dialog/emerge, holds, 9 roles, naming actual, shape polimórfico aditivo) o marcar §§ afectadas como superseded-by-CANON | D2 |
| 2.3 | Archetypes 24→26 en active_architecture + morfo/README; numeración única de la cascada sema en README/engine/CLAUDE.md; retirar @floating -ui/clsx de docs soma; limpiar JSDoc `defaultSemantic` | D3, S8, SO6, SO3 |
| 2.4 | Documentar política real de dependencias (uix cero-dep; arts justificadas: runed/tabbable/ogl) | D4 |
| 2.5 | Script `docs:check` para invariantes copiables (conteos de vocabulario, campos citados, deps citadas) | D5 |
### Fase 3 — Rendimiento y ergonomía (según demanda)
| # | Acción | Cierra |
|---|---|---|
| 3.1 | Memoizar `renderStaticCss` ; `apply(scope?)` por bloque; eliminar doble build en `applyTheme` | E1 |
| 3.2 | Mode-source de eidos vía ActiveDom (`dom.prefersColorScheme` análogo a prefersReducedMotion) | E2 |
| 3.3 | Tipar matcher `state` de `semaSelector` desde `dataAttrsByPart` | M5 |
| 3.4 | Contact base silencioso + packs opt-in de sonido | S6 |
| 3.5 | Schema check: stateRef+enum exige 2 valores | M4 |
| 3.6 | Limpieza: `_resetCompileCache` , aliases deprecados, formato stamp/durations, indentación runtime:667 | M6, SO5, S9 |
### Fase 4 — Ampliaciones estratégicas (backlog)
- Slots semánticos V2 (`data-event-frame`/`data-event-signal`) — tras 1.2.
- Builder tipado para recipes CSS → retirar eidos-lint.
- `holdsPolicy: 'canonical'` opt-in en el engine.
- Lint de `intentGuidance` en morfo:vocabulary.
- Decisión capa 4 del token stack (deprecate si sigue sin consumers) + consumo real del
bundle `--size-*` + primer caso `scope:'event:*'` en TSC.
- Guía "adopción incremental: solo soma" para competir en onboarding con Radix/Ark.
---
*Verificado al cierre: los hallazgos S1– S9, SO1– SO6, M1– M6, E1– E6 y D1– D5 se contrastaron
contra el código fuente en la rama `alpha-0.1-sec-dom` a 2026-07-01. Los conteos (136 morfos,
82 con events, 41 con expression, 48 packs, 188 usos de semaSelector, 97 con `texts` , 0 con
`translations` ) provienen de grep directo. No se ejecutó la suite de tests en esta sesión;
las cifras de tests citadas provienen de los hand-offs registrados.*