35 KiB
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:
- Huecos de implementación en la ejecución perceptiva — concurrencia de señales sin
gobernar (el
regimedeclarado 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. - Contrato que promete más de lo que ejecuta — eventos declarados que nunca se emiten
(los 5 pickers),
commitspuramente descriptivo,mode/regime/scopemuertos. El propio LIBRO_VARIACIONES (D.2) reconoce el problema y propone el flagemission, aún sin implementar. - Drift documental significativo — el corpus es enorme y varias fuentes "autoritativas"
contradicen al código (campo
translationsvstexts, 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 destaticAttrsvsdynamicAttrsconAttrPlandiscriminado pormode, 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 Morfocomo patrón obligatorio: los typos de parte/evento son errores de compilación reales. Verificado quecreateAttrsysemaSelectorexplotan 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.
- composition) la cobertura es universal (15/15 componentes con
- Contratos ejecutables (
src/uix/contracts.ts): la tabla de ownership/degradación es dato + tipos (UIX_TYPE_CONTRACTScon checksExtends<>), no prosa. Rarísimo verlo. - Sema ornamental de verdad:
eventEngineopcional 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), nosetTimeoutcrudo.
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 —
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)
- 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-signales la evolución natural. - 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.cssSelectorscomo constantes exportadas por componente. - 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-inholdsPolicy: 'canonical'en el engine evitaría la divergencia silenciosa entre tabla y morfos. - Lint de
intentGuidance— el eje doctrinal (discourageden contact) se declaró "possible lint in the future";morfo:vocabularyya tiene el sitio natural. uix.perf+ presupuesto de emit — medir el coste real deresolveSignature(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.- 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)
EventEngineEmitterpermite fakes sinclear/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) mientraspart()lanza — asimetría de errores razonable pero no documentada.resolveSignatureaceptaSemaMap | SemaResolveOptionscon 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. MorfoElementes 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)
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
intentGuidanceen morfo:vocabulary. - Decisión capa 4 del token stack (deprecate si sigue sin consumers) + consumo real del
bundle
--size-*+ primer casoscope:'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.