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/fable_audit.md

462 lines
31 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 Words (EV-G) se "resolvió" dejando de emitir el evento —
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.*

Powered by TurnKey Linux.