# 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.*