47 KiB
| title | type | audience | authority | status | método |
|---|---|---|---|---|---|
| Auditoría clean-room del ecosistema active-uix — theming y tokens en foco | audit | human + agent | snapshot de análisis (2026-07-10) — registra hallazgos, no doctrina; los docs E1/E2 siguen siendo la verdad | clean-room 2026-07-10 | lectura completa del corpus (sin auditorías previas) + auditoría de código por capas + guards del repo + barridos propios |
Auditoría clean-room del ecosistema active-uix (2026-07-10)
Encargo: auditar todo el ecosistema active-uix buscando debilidades, correcciones, vulnerabilidades y carencias, con doble foco en (a) la coherencia del sistema de theming y los tokens de componentes y (b) las carencias de personalización. Clean-room: no se ha leído ninguna auditoría previa ni documento de trabajo pendiente (
docs/audit/**,docs/old-deprecated/**,docs/process/**,next-features.md,theming/changelog.md). Sin agentes.web/fuera del análisis (solo se verificó la existencia de rutas que los docs citan).words/·palabras/·chronos= pista activa separada, fuera del alcance de hallazgos.
1. Resumen ejecutivo
El ecosistema está en un estado de coherencia estructural notablemente alto donde hay guard mecánico, y con deriva concentrada exactamente donde no lo hay — la tesis que el propio repo defiende ("canons defended by a mechanical guard drift ~0%") se confirma en frío. Los datos duros de esta pasada:
- Disciplina de tokens casi perfecta en el catálogo mantenido: 0 hex sin
anotar, 0
var(--scale-*)/var(--primitive-*)en CSS de componente, 0 prefijos prohibidos, 0z-indexcrudos, 0 ejes físicos en claves de receta,box-shadow100 % tokenizado, 0data-soma-*. - Drift morfo↔CSS = 0:
eidos-lint-allclasifica 0 selectores inválidos en todo el catálogo; los 63 packs sema construyen sus 244 selectores consemaSelector(0 strings manuales). - Matriz de aceptación: 134 componentes → 102 PASS · 32 NEEDS-WORK · 0 BROKEN. El grueso de lo pendiente es documental/demo (README F-1.x, chips D-7.x, trazas D-1.5/D-4.3), no de contrato visual.
- La suite eidos corre 305/306 (1 canario de censo desfasado).
Las debilidades reales no están en la disciplina de valores sino en cuatro frentes:
- Huecos de enforcement — el escáner de recetas solo lee
{kebab}.css(los 6 CSS secundarios escapan a R-*, y dentro hay violaciones reales); un componente sin morfo (card-group) es invisible para toda la matriz; la propia fundación (archetypes.css) usa literales de opacidad que prohíbe a las recetas. - Contrato sema declarado pero no ejecutado — deuda ya registrada en
book-deviations.md(D.6/D.11) pero sustantiva: 6 superficies de menú/árbol con packs dormant y 5 pickers conclosepolimórfico inerte; a lo que esta pasada añadefield(interactivo con 0 eventos, error A-3.1) yfloat-panel(28 acciones de teclado vs 6 eventos, A-3.7). - Incoherencias puntuales de a11y/theming — dos políticas divergentes de
prioridad de live-region (runtime vs AnnounceChannel); el anillo de foco
de la fundación sigue en
box-shadowcuando §32 canonizóoutlinepara todo el catálogo hace 3 días — condicionado por unoutline: nonede la capa de aplicación. - La promesa de personalización más visible a medias: la paleta de 33
escalas por instancia (
<X color="teal">) solo está cableada en 2 de las 17 superficies condata-color(button, toggle), y su tracker de rollout fue borrado sin nuevo hogar.
En vulnerabilidades de seguridad clásicas el resultado es tranquilizador: no
hay {@html}/innerHTML inyectable en el núcleo, el clipboard es
write-only, la persistencia de config valida contra contrato versionado, y
setCssVariables es transaccional y validado. La única superficie de
inyección observada es de nivel autor (matchers de semaSelector sin
escape), no de usuario final.
2. Alcance y método
| Fase | Qué se hizo |
|---|---|
| 1 | Lectura completa del corpus: E0–E4 + RFCs ×7 + book-deviations.md + READMEs in-code (uix, eidos/components, arts del grafo: color, prefs, active-app, mapa arts). ~45 documentos. |
| 2 | Auditoría de código del núcleo: morfo (compile, selectors, tipos), sema (engine, canales visual/sound/announce, holds), soma (runtime completo, Soma), active-uix (boot dual, impl, services), eidos (ActiveEidos, archetypes/events.css, themes/base, spot-checks de render-css). |
| 3 | Guards del repo (eidos-lint-all, component:audit, vitest src/uix/eidos, docs:check) + barridos grep propios sobre los 130 CSS mantenidos + matriz transversal por componente. |
| 4 | Gap analysis de personalización por eje (síntesis en §7). |
Límites declarados: morfo:check, perm:check y smoke no se
ejecutaron (requieren dev server activo); no hubo verificación visual en
navegador. Los hallazgos de comportamiento provienen de lectura de código y
tests existentes, no de reproducción en vivo.
Inventario real del ecosistema: 140 morfos · 65 packs sema · 139 ficheros CSS eidos (131 recetas principales; 130 mantenidas tras excluir pistas) · 134 componentes enumerables por la matriz de aceptación · 22 arts.
3. Lo que está sano (verificado, no asumido)
Merece constar porque delimita dónde NO hay que gastar esfuerzo:
- TSC operativa: álgebra de scopes + detección cross-axis en
render-css.ts(scopeCovers, fase 3 de colisiones), guard de palette-derived-at-root en tests. El bug clase "toggle pre-TSC" no puede volver silenciosamente. - Pipeline de color de referencia: OKLCH default-on con fallback hex
(
appendColorScaleDeclarations), on-solid APCA con doble suelo y un solo criterio para rol e instancia (eidos/lib/on-solid.ts), alpha compositing-inverse,deriveSchemeM3 +temperpara intents. Coincide con lo prometido enrfc-color-engine§6–8. - Runtime de theming completo y seguro:
setCssVariablesvalida contragetCssContract()y solo comita si el render valida (transaccional,active-eidos.svelte.ts:626-639);resolveTokenresuelve color en JS puro singetComputedStyle(497-517); los 7 builders (applyColorScheme/applyTypeScale/applyDepth/applyShape/applySpacing/applyGradients/applyTheme) existen con susclear*()y el capstone escribe atómico. - Teardown disciplinado:
EngineSemantic.disposelimpia persistentes + timers de frecuencia/dominancia + canales;SoundChannelcierraAudioContexty su listener de unlock;AnnounceChannelretira sus regiones;ActiveUixImpl.disposedesmonta en orden inverso y solo lo que posee (attach no toca el app, salvo el motion-fallback propio). - SSR/degradación coherente con la tabla de contratos: sema es ornamental
de verdad (el runtime salta el emit sin engine),
dom:falseinyecta undisabledDomcompartido,applyDom:falsedeja a Eidos como generador puro. - A11y de fundación:
@media (forced-colors: active)emite outline de foco (generated/base.css:6488) yprefers-contrast: morerefuerza bordes y texto — tal como promete §28. - Hold-como-suelo implementado:
VisualChannel.awaitExpressionespera las animaciones del target con topeMAX_EXPRESSION_WAIT_MS = 1500— el antipatrón de truncado (D.12) está efectivamente muerto en código.
4. Hallazgos por capa
Formato: ID · severidad (P0 rompe contrato/usuario · P1 incoherencia con
impacto real · P2 deuda/corrección · P3 menor/observación). Evidencia en
ruta:línea.
4.1 morfo
- MOR-1 · P2 —
semaSelectorno escapa ni valida los matchers de string.state.attr/value,aria.attr/value,eventNamePrefix,ancestorypseudose interpolan tal cual (src/uix/morfo/selectors.ts:141-155). Un valor con"o]produce un selector inválido que revienta entarget.matches()durante el emit (excepción dentro de la cascada sema). Es superficie de error de autor (no de usuario final), el propio doc lo marca "future iteration", pero el builder existe precisamente para que el compilador ataje esto: faltaCSS.escape/validación de nombre de attr. - MOR-2 · P3 — duplicación de la convención de marker.
selectors.ts:121re-implementa la reglakebab === 'provider' → data-{c}que ya posee el compilador (el comentario lo admite: "Mirrors what compileMorfo emits"). Dos fuentes para la misma convención = deriva futura. - MOR-3 · P3 —
_resetCompileCache()es un no-op con nombre engañoso (compile.ts:274-277). O se implementa (reasignando el WeakMap) o se elimina. - MOR-4 · P1 — un componente público sin morfo:
card-group.eidos-lint-alllo salta ("no morfo file — skipped") ycomponent:auditni lo lista (enumera porMORFO_DIR,scripts/component-audit.ts:192) → invisible para toda la maquinaria de aceptación, con violaciones dentro (opacidad literal, ver TOK-3). Contradice la doctrina "morfo first para todo componente, inclusoscope: ['eidos']" (guía §5 / regla F-1.5).picker-shellyspin-fieldtambién se saltan, pero esas son capas compartidas y la doctrina las exime;card-groupno es una capa.
4.2 sema
- SEM-1 · P1 — dos políticas divergentes de prioridad de live-region. El
runtime marca
assertivesolo sifamily === 'signal' ∧ intent ∈ {threat, loss}(soma/runtime.svelte.ts:725-728);AnnounceChannelmira solo el intent (sema/chans/announce.ts:55-56). Uncommit.fail + threatse anuncia polite por la víasources.announcey assertive por la vía canal. Mismo concepto, dos reglas — hay que unificar (y decidir cuál es la canónica; el libro sugiere intent). - SEM-2 · P2 — política de errores frágil en los bordes. (a) Los fallos
de canales no visuales son silenciosos si no se inyecta
logger(engine.ts:79-80, documentado); (b) un rechazo del canal visual envoid runtime.trigger(...)es unhandled rejection ("política consciente"); (c)triggerlanzaSomaRuntimeTargetErrorsi el target no está montado con engine presente (runtime.svelte.ts:670-671) — unvoid triggersobre una parte recién desmontada produce un unhandled rejection real en producción. Merece al menos un logging por defecto. - SEM-3 · P3 — restos menores en el motor.
applyDominancetiene un fallback muertosignal.id ?? sig-N++(engine.ts:518) que, de alcanzarse, desalinearía los ids;awaitExpressionno cancela el timer de tope cuandofinishedgana (callback fantasma de hasta 1500 ms enuix.timers,chans/visual.ts:113-126). - SEM-4 · P2 — deuda declarada pero perceptualmente relevante: los packs
de menú/árbol están dormant (los 6 morfos no emiten vía
runtime.trigger;book-deviations.mdD.6 caveat) y los 5 pickers tienen elclosepolimórfico declarado pero los providers solo toggleanopen(D.11 "eventos inertes"). Resultado: la firma perceptiva de ~11 componentes existe en el contrato y no en runtime. Está registrado "sin urgencia" desde 2026-05/06; a fecha de hoy sigue igual — el flagemission(D.2) que lo haría auditable tampoco existe aún.
4.3 soma
- SOM-1 · P2 —
triggerespera handlers que el contrato declara síncronos.EventHandlerdocumenta "Returning a value is allowed; it is ignored" pero el código haceawait handler()(runtime.svelte.ts:743,753): un handler async SÍ se espera, cambiando el timing (a11y yTriggerResultse retrasan). O se cumple el contrato (no-await) o se corrige el contrato. - SOM-2 · P2 —
keydowncorta en el primer match sin handler. Si el primer plan que casa la tecla no tiene handler registrado,return falseaborta el bucle y nunca se prueban otros planes de la misma tecla con distinta condición/acción (runtime.svelte.ts:562-564). Deberíacontinue. - SOM-3 · P2 — 8 desviaciones de la doctrina de recursos gestionados: 7
setTimeoutcrudos (image-provider, safe-polygon, dismissal, datetime/announcer, tags-input, float-panel, announce/global) y 1addEventListenerdirecto (media-player/media-provider.ts) frente a la regla "timers via uix.timers / listeners via dom.listen". Con cleanup o sin él, quedan fuera de la observabilidad/cancelación del scheduler. - SOM-4 · P2 — backlog reconocido de re-declaración de attrs morfo. La
guía lo dice textualmente ("Most existing providers still do this — a
documented migration backlog", component-guide §Part props): providers que
re-declaran
role/aria-*en suspropsgetters en vez derenderProps(). Es duplicación morfo↔soma latente en gran parte del catálogo, sin censo ni guard. - SOM-5 · P1 — contrato sema infra-declarado en dos componentes
(evidencia fresca de
component:audit):fieldes interactivo con 0 eventos (error A-3.1) yfloat-paneldeclara 6 eventos para 28 acciones de teclado mutantes (error A-3.7). Junto a SEM-4, son los huecos de contrato perceptual más claros del catálogo.
4.4 active-uix
- AUX-1 · P3 — tres mecanismos de live-region conviven: el de
ActiveUixImpl.announce(zwsp-toggle,active-uix.svelte.ts:444-492), el fallback propio deAnnounceChannel(clear-then-set) y el componente soma<Announce>. La capa está razonada (uix = low-level compartido), pero las técnicas de re-anuncio difieren y la prioridad diverge (SEM-1). Vale un párrafo de doctrina que los ordene. - AUX-2 · P3 —
disabledDommixto: escrituras no-op silenciosas peromeasure/raf/getDocumentlanzan (active-uix.svelte.ts:242-329). Coherente con "reads that must return throw", pero no está documentado en la tabla de contratos — un componente que mida endom:falsemuere conActiveUixDomDisabledErrorsin que la tabla lo avise. - AUX-3 · OK verificado — el resto de la tabla de degradación (attach
exige langs+dom, opcionales fallan explícito, dispose selectivo) se cumple
tal cual la documentan
contracts.tsyarchitecture/active-uix.md.
4.5 eidos (núcleo)
- EID-1 · P1 — el anillo de foco de la fundación contradice el §32
canonizado.
archetypes.css:162-167implementa el anillo universal conbox-shadow, con esta justificación en comentario: "the project's layout.css setsoutline: none !importantglobally". Perotheming/reference.md§32 canonizó (2026-07-07) outline como modelo único de todo el catálogo (HCM-safe, sin flicker de segmentos, paridad con referencias). El agujero de HCM está parcheado (forced-colors emite outline, generated/base.css:6488), pero: (a) la fundación es ahora la excepción del modelo que el canon declara único; (b) el condicionante es CSS de la capa de aplicación (web/routes/layout.css) — una inversión de dependencia: la fundación del framework moldeada por un reset del app. Decisión pendiente: migrar el anillo de fundación a outline y retirar el reset, o anotar la excepción en §32. - EID-2 · P2 — la fundación viola su propia disciplina de opacidad.
archetypes.cssusaopacity: 0.5(líneas 52, 111, 222) yopacity: 0.85(129) sin token ni anotación — exactamente lo que R-4.2 declara error en recetas ("nunca 0.4/0.5/0.6 a mano;var(--opacity-disabled)"). Además ignora--opacity-disabledexistiendo el token. - EID-3 · P3 — ✅ RESUELTO 2026-08-15. La excepción se cerró con doctrina
explícita en vez de con una nota: hay DOS rejillas nombradas en
eidos/lib/types.ts—Position(física, no espeja) yLogicalPosition(start/end, espeja) —, ambas generadas acanon/vocabularies.md, y la regla de elección es de comportamiento: ¿tiene que voltearse para un lector de derecha a izquierda? El hallazgo original, para el registro: - EID-3 · P3 —
data-sidefísico vs mandato "logical RTL". dialog (24), drawer (16), toast (6), box (6) y media-player (4) usanleft:/right:físicos como implementación de APIs de colocación por lado físico de viewport (convención floating-ui). No es deuda de flujo de contenido (paddings/margins están en lógicos), pero convendría que el mandato "logical RTL — never left/right" (cabecera de component-guide) explicitara la excepción de placement. - EID-4 · P3 — comentarios con recuento de paleta desactualizado: "the
31-scale palette" (
active-eidos.svelte.ts:653,arts/color/README.md:65) vs las 33 canónicas dePALETTE_SCALES/docs:check.
4.6 arts y dependencias
- DEP-1 · P2 —
clsxes dependencia fantasma. Importada ensrc/uix/soma/props/props.ts:1y ausente depackage.json(resuelve como transitiva). Deuda ya documentada enarchitecture/soma.md§12 ("pending decision") — sigue sin decidirse: declararla o inlinear las ~30 líneas. - DEP-2 · P2 —
ogles dependencia runtime muerta. Declarada enpackage.json:60y cero imports ensrc/yweb/(solo un comentario en la demo aurora la menciona como convención). Contradice la doctrina "zero dependence" en su forma más barata de arreglar: eliminarla. - ARTS-OK —
$colorcumple lo prometido (puro, isomorfo, sin DOM);prefsmantiene la partición theme/mode/density fuera del preset core tal como el doc;motioncomo servicio con gating JS en Presence coincide con motion.md §9.
5. Theming: coherencias e incoherencias
5.1 Lo coherente (contrastado doc↔código)
- Las 7 capas de token existen como se documentan; los slots son los 12
de
DEFAULT_COLOR_ROLE_SLOT_STEPSconborder = 7(§28) y el retiro deborder-hoverestá reflejado en RFC y código. THEME_BASE_COLOR_ROLEScoincide con §4 (primary purple · tertiary indigo reservado y anotado como no-muerto · loss plum · risk orange) — las colisiones históricas (primary≡loss, tertiary≡neutral) están corregidas.- Densidad ⊥ tipografía y scaling por mapa de participación
(
STATIC_SCALING_PARTICIPATION) están implementados como el RFC de scaling; la reconciliación del drift de radius (commit845d6579) quedó registrada en el propio RFC. - Los guards de valor-referencia de 2026-07-07 (no phantom refs, privates
con scope cubriente) están en
recipe-css-contract.test.tsy la suite los ejercita. - El gap trigger→panel, el state layer y el radius-factor son config data
(
primitives.floating/state) como declara §36/§38/§40.
5.2 Incoherencias y huecos (los que importan)
- THM-1 · P1 — enforcement parcial: los CSS secundarios no se escanean.
component-auditlee únicamente{kebab}.css(scripts/component-audit.ts:698,756). Quedan fuera de TODAS las reglas R-_:calendar/calendar-select.css,color-picker/color-picker-spectrum.css,date-range-picker/date-range-picker-time.css,field/field-control-trigger.css,field/field-segment-state.css,picker-shell/picker-time-row.css. Y no es teórico: dentro hayhsl()×8 sin anotar (spectrum — físicamente fijo, pero la regla exige anotación),opacity: 0.6(calendar-select.css:27) y un fallback mágicovar(--calendar-month-gap, 36px)(date-range-picker-time.css:9). El escáner debería iterar_.cssdel directorio. - THM-2 · P1 — la paleta por instancia está al 12 % del catálogo. De las
17 superficies que emiten cascada
data-color(avatar+badge, badge, button, card, checkbox, editable, file-upload, radio-group, select-trigger/content, stepper, switch, tag-group, tags-input, toggle-group, toggle), solo button y toggle generan la cascada de las 33 escalas ([data-{c}][data-color='teal']en generated/base.css). El resto acepta únicamente roles. La promesa "elcolorprop acepta cualquiera de las 33 escalas" (demo-authoring §6, vocabularies) es hoy una capacidad de 2 componentes; su tracker de rollout fue borrado sin sustituto (TODO(reconcile) en demo-authoring §6) y el helper citado (paletteScaleDeclsen recipes/base.ts) no existe — el mecanismo real esuniversalPaletteDecls(render-css.ts:1766). Triple acción: decidir el rollout, restaurar el registro, corregir el doc. - THM-3 · P2 — la suite eidos está en rojo por un canario vencido.
lint.test.ts:106-111exige que toggle siga teniendo ≥1 class-hook "hasta que aterrice el codemod P5, y CERO después" — toggle ya lee 0 y el guard no se volteó atoBe(0). Trivial, pero significa quenpm testdel scope eidos no está verde en la rama. - THM-4 · P2 — 1150 selectores de contrato sin consumidor CSS (columna
unusedde eidos-lint-all; hotspots: words 149 — excluido —, media-player 47, stepper 30, color-picker 29, avatar 28, time-range-picker 26). No son errores per se (muchos attrs son de comportamiento puro), pero el volumen merece un criterio: o el attr tiene consumidor previsto (documentarlo), o sobra del contrato. Hoy no hay severidad ni doctrina para "declarado y jamás estilizado". - THM-5 · P2 —
!importantsin doctrina. 17 usos en el catálogo mantenido. Los de color-picker (4) están justificados en comentario (inline styles de soma) y los de group (8) implementan la fusión de radios; drawer/float-panel/tabs/select van sin anotación. No hay regla R-* que los gobierne — conviene o anotarlos como los literales o darles guard. - THM-6 · P3 — fallback sospechoso:
var(--radius-full, 1px)enmenu-dial/menu-dial.css:293(sus hermanos usan9999px; 1px como fallback de full huele a errata). - THM-7 · P2 — el ejemplo del doc contra su propia regla:
sema.md§packs ilustra el pack de dialog con un selector manual ('[data-dialog-content][data-event-intent="threat"]') cuando la regla del mismo documento exigesemaSelector. El código real está limpio; el ejemplo enseña el antipatrón.
5.3 Matriz transversal (130 componentes mantenidos)
Lectura correcta: heredar de la fundación es cumplimiento — la columna mide uso propio en la receta, no cobertura efectiva.
| Sistema | Recetas que lo consumen en propio CSS | Lectura |
|---|---|---|
state-layer (var(--state-*)) |
20/130 | El resto lo hereda de archetypes.css vía arquetipo; el único hover fuera de contrato que queda es badge (R-4.3, color-mix(currentColor)), confirmado por la matriz de aceptación. |
| depth/shadow tokens | 10/130 | Coherente con el modelo data-depth (el bundle lo pinta la fundación); no hay sombras literales. |
| focus propio | 73/130 | El resto cae al anillo de fundación (ver EID-1). 7 warns R-1.5 pendientes en la matriz. |
data-size en CSS |
78/130 | Consistente con los subsets declarados; 10 fallos de paridad de chips (D-7.4) son de demo, no de receta. |
| motion tokens propios | 37/130 | El resto usa presets/signatures generados — el diseño esperado. |
| props físicas left/right | 5/130 | Todas son APIs de colocación (EID-3). |
Estado por reglas de la matriz de aceptación (fresco, 2026-07-10):
R-4.x prácticamente limpio (1 sola violación viva: badge R-4.3) y
R-2.7 con 2 warns (format-date font-size 1em ×2 · relative-time
line-height 1.2 — probablemente identidades anotables). El backlog real de
los 32 NEEDS-WORK es demo/README: D-4.3 ×22 (botones ▶ del tab Sema),
F-1.1/F-1.4 ×10 (secciones Baseline/Gaps), D-7.4 ×10 (paridad de chips),
D-1.5 ×10 (traza MutationObserver), R-1.3 ×9 (data-readonly sin estilo),
R-1.5 ×7, D-3.1 ×7, F-1.2 ×6, F-1.5 ×5, D-1.2 ×4, R-1.4/R-1.2/A-1.4 ×3.
6. Vulnerabilidades (lectura de seguridad)
No se encontró superficie explotable por usuario final en el núcleo:
- Inyección: sin
{@html}/innerHTMLdinámicos en morfo/sema/soma/ active-uix core; los live-regions escribentextContent. La única interpolación sin escape es MOR-1 (selectores construidos por el AUTOR; se vuelve relevante solo si una app construye cascade rules desde datos de usuario — p. ej. un editor de temas sonoros: conviene cerrar conCSS.escapeantes de que exista ese consumidor). - CSS injection vía theming runtime:
setCssVariablesvalida nombres contra el contrato (strict por defecto) y es transaccional; los VALORES no se sanean (un valor podría cerrar el bloque}... — mitigado porque el render pasa por el serializador propio; no se observó escape explícito de}en valores: recomendación de endurecer el serializador derenderCssVariablesrechazando;}embebidos, mismo rango P2). - Persistencia:
EidosConfigDocumentvalida kind/version/options sin compat silenciosa — hidratar JSON manipulado degrada a error de validación, no a estado a medias. - Unhandled rejections (SEM-2) son la debilidad de robustez más real en
producción (pueden tumbar procesos en SSR/node si un trigger
voidfalla con engine presente y target desmontado).
7. Carencias de personalización (gap analysis por eje)
Veredicto global: la superficie de personalización es excepcionalmente ancha (pocas librerías exponen color+tipografía+espacio+forma+profundidad+ gradientes con builders runtime y contrato introspectable). Las carencias son periféricas pero reales:
| Eje | Estado | Carencia concreta |
|---|---|---|
| Color (escalas/roles/slots/alphas) | ✅ completo + runtime + CSS-only themes | THM-2: paleta 33 por instancia solo en 2/17 componentes. Layer 4 (--{c}-{role}-{slot}) sigue sin consumidores — la propia FAQ la marca candidata a colapso "si en 6 meses nadie la usa": toca decidir. |
| Tipografía | ✅ fluid + tracking/leading/features/measure + fonts config | Motor de fuentes variables listo pero assets aún TTF estáticos (capacidad no ejercida, rfc-typography §5); --size-{k}-font-letter-spacing emitido y sin consumir por recetas de control (pendiente declarado); divergencia de 2 px en label lg segmentado (pendiente documentado en §5). |
| Espaciado / densidad / scaling | ✅ 3 ejes compuestos + applySpacing | Densidad = 3 niveles fijos (retunables en magnitud, no ampliables en número); cambiar la participación de scaling exige config+regeneración (no runtime) — ambas son decisiones de diseño declaradas, no bugs. |
| Forma | ✅ smoothing/familias/nesting + applyShape | Nesting a full solo esquinas superiores (límite geométrico documentado). |
| Profundidad | ✅ planes config-driven + frost + applyDepth | Token scrim emitido sin regla que lo consuma (backdrop sigue per-component); --depth-{plane}-z expuesto con 0 consumidores (open cage declarado — vigilar que no fosilice). |
| Motion | ✅ dos momentos + presets tipados extensibles + loops + reduce | Presets JS no serializables (registro directo, documentado). Sin carencia real. |
| Sonido / háptica | ✅ tunings + packs + preferences reduce/off | Un tema no puede re-sonorizar sin código: los packs son TS, no config del tema (coherente con D.7 "samples = recursos", pero significa que la personalización sonora es de app/desarrollador, no de theme distribuible). |
| Focus ring | ✅ --focus-ring-* parametrizado |
EID-1 (modelo dividido fundación vs canon §32). |
| State layer | ✅ config primitives.state |
— |
| Touch target | Constante 44 px | No temable por diseño declarado (§37: "hard ergonomic constant"). Coherente; anotar en gap solo como decisión. |
| Z-index / bandas | ✅ config + banda overlay | — |
| Breakpoints / container | ✅ config vía ActiveDom → tokens + @container |
Container queries: eje temable con 0 consumidores ("open cage", tsc.md §container) — mismo riesgo de fosilización que scrim/depth-z. |
| Gradientes | ✅ tokens + angles + applyGradients + capstone | Solo deriva documental: reference §10 y channels.md §5 omiten el eje gradient que ThemeSeed ya compone (active-eidos.svelte.ts:219-232). |
| Iconografía | ✅ --icon-size-*/stroke + glifos spin-field temables |
No hay noción de "icon set" temable por config (el tema no puede sustituir el set de iconos; decisión razonable, registrar como límite). |
| Scrollbars | Parcial | scroll-area tiene receta propia, pero no hay theming sistémico del scrollbar nativo global (colores de scrollbar-color/webkit fuera del contrato). |
| Selección de texto / caret / cursor | ❌ | La fundación no emite ::selection, caret-color ni tokens de cursor; los cursores están cableados por arquetipo (pointer/grab/not-allowed). Si "todos los aspectos" es la vara: son los tres huecos sistémicos visibles. |
| Dark/light/HCM/contraste | ✅ | — |
| RTL | ✅ lógico en flujo + getDirectionalKeys |
Position física / LogicalPosition lógica) con regla de elección en sus typedoc y en el canon generado. |
| Tema distribuible | ✅ CSS-only vía contrato | Límite deliberado (§19): ThemeDefinition no expone recipes — un tema no puede ajustar tokens per-component (solo el app en boot). Está razonado (portabilidad perceptual); consecuencia práctica: un "marketplace de temas" no podría re-tunear componentes sin pasar por el app. |
8. Documentación: deriva y arquitectura del corpus
El corpus es de una calidad inusual, y precisamente por eso sus grietas son sistemáticas y vale la pena listarlas:
- DOC-1 · P1 — el canon depende de material de auditoría/deprecated.
CANON.md(§3 intent-assignment) ysema.md(§expression) citandocs/audit/components/_veredictos.mdcomo registro canónico de veredictos;component-guide.md(cabecera) ysrc/uix/eidos/components/README.mddeclaran que la fuente de verdad del build-contract es el §13 dedocs/old-deprecated/ARCHETYPE_COHERENCE_AUDIT_2026-06-19.md. La regla del propio corpus es "process/audit ≠ source of truth": el contenido canonizado debe migrar a E1/E2 y dejar el audit como historia. - DOC-2 · P1 — el ancla editorial no existe en el repo.
docs:check(WARN I6):README.md:138→Disenando_lo_que_ocurre_HOMOGENEIZADO.pdfno resuelve, yCANON.md:295citav2_3.mdque su propio frontmatter declara inexistente. En el working tree haydocs/Disenando_lo_que_ocurre_FINAL.{docx,pdf}sin trackear — hay una edición FINAL (426 págs., la que book-map.md ya referencia) sin reconciliar con los punteros del corpus. - DOC-3 · P2 — enlaces muertos tolerados:
THEMING_AUDIT_2026-06-01.mdcitado 4 veces (reference.md:1206,1418 + changelog ×2) y borrado;sema.md:616→audit/sema-findings.md; 7 rutas de demos de animación enMOTION_SERVICE_RFC.md. El guard existe pero a severidad WARN — nunca bloquea. - DOC-4 · P2 — secciones desfasadas puntuales: la cadena de resolución de
hold de
sema.mdsigue citandoSEMA_MAP.families[*].hold(eliminado;sema-map.ts:38-43lo confirma);notes.mdFAQ defiende y deja como "pendiente" el scopeevent:*que reference §13/§14 y motion.md declaran obsoleto; reference §10 y channels.md §5 omitengradienten el capstone; demo-authoring cita el helper inexistentepaletteScaleDecls. - DOC-5 · P3 — recuentos fósiles: "~95 components" y "62 exhaustive" (reference §11), "all 25 soma components" y "66 morfos" (component-guide), "31 scales" (color README / comentario ActiveEidos) frente a los reales (134 auditables, 140 morfos, 33 escalas). La regla anti-números-frágiles de authoring.md no se aplicó retroactivamente.
- DOC-6 · P3 — menudencias: numeración duplicada
§4.11encomponent-audit.md; su audit-log congelado en 2026-05-22 con "Known gaps" pendientes dentro de una guía binding; dosTODO(reconcile)huérfanos (normas N-6/N-7 de PENDIENTES.md borrado; trackerpendiente_color_demos.mdborrado);authoring.mdtabla E2 apunta aeidos/TSC.md(stub) en vez decanon/tsc.md;eidos/components/READMEenlaza../../air-old/(rama muerta).
9. Tabla de severidades
| ID | Severidad | Resumen | Dónde |
|---|---|---|---|
| MOR-4 | P1 | card-group sin morfo → invisible a toda la maquinaria de aceptación | eidos/components/card-group/ · component-audit.ts:192 |
| SEM-1 | P1 | Dos políticas de prioridad de live-region divergentes | runtime.svelte.ts:725 vs chans/announce.ts:55 |
| SOM-5 | P1 | field 0 eventos (A-3.1) · float-panel 28 acciones/6 eventos (A-3.7) | matriz de aceptación 2026-07-10 |
| EID-1 | P1 | Anillo de foco de fundación en box-shadow vs §32 outline canónico; condicionado por reset del app | archetypes.css:138-167 |
| THM-1 | P1 | 6 CSS secundarios fuera del escaneo R-* (con violaciones dentro) | component-audit.ts:698,756 |
| THM-2 | P1 | Paleta 33-escalas por instancia solo en button+toggle (2/17) | generated/base.css (census) |
| DOC-1 | P1 | Canon E1/E2 dependiente de docs audit/deprecated | CANON §3 · component-guide cabecera |
| DOC-2 | P1 | Ancla editorial (PDF del libro) inexistente; edición FINAL sin reconciliar | docs:check I6 · git status |
| SEM-2 | P2 | Errores de canales silenciosos sin logger; unhandled rejections en void trigger |
engine.ts · runtime.svelte.ts:670 |
| SEM-4 | P2 | Packs dormant (6) + close polimórfico inerte (5) — firma declarada, no emitida | book-deviations D.6/D.11 |
| SOM-1 | P2 | await handler() contradice el contrato de handler síncrono |
runtime.svelte.ts:743 |
| SOM-2 | P2 | keydown corta en match sin handler |
runtime.svelte.ts:562 |
| SOM-3 | P2 | 7 setTimeout + 1 addEventListener fuera de la doctrina gestionada | soma layers/providers |
| SOM-4 | P2 | Backlog de providers re-declarando attrs morfo, sin censo ni guard | component-guide §Part props |
| MOR-1 | P2 | semaSelector sin escape/validación en matchers string | selectors.ts:141-155 |
| EID-2 | P2 | Literales de opacidad en la fundación sin token/anotación | archetypes.css:52,111,129,222 |
| THM-3 | P2 | Suite eidos en rojo (canario censo toggle sin voltear) | lint.test.ts:110 |
| THM-4 | P2 | 1150 selectores de contrato sin consumidor CSS, sin doctrina | eidos-lint-all |
| THM-5 | P2 | 17 !important sin regla que los gobierne |
catálogo |
| THM-7 | P2 | Ejemplo de sema.md con selector manual contra su propia regla | sema.md §packs |
| DEP-1 | P2 | clsx fantasma (importado, no declarado) | soma/props/props.ts:1 |
| DEP-2 | P2 | ogl declarado como dependencia y sin un solo import | package.json:60 |
| SEC-1 | P2 | Endurecer serializador de valores en renderCssVariables (;} embebidos) |
lib/ render de variables |
| DOC-3/4 | P2 | Enlaces muertos ×15 (WARN tolerado) + secciones stale (holds, event:*, gradient, paletteScaleDecls) | docs:check · sema.md · notes.md |
| MOR-2/3 · SEM-3 · AUX-1/2 · EID-3/4 · THM-6 · DOC-5/6 | P3 | Menores: duplicación marker, no-op cache, timer fantasma, triple live-region, disabledDom mixto, placement físico, recuentos fósiles, radius-full 1px, §4.11 duplicado, TODOs huérfanos | ver secciones |
| GAP-* | — | Carencias de personalización de §7 (selection/caret/cursor/scrollbar global, sonido por tema, layer-4, container/scrim/depth-z sin consumidores, assets variables) | §7 |
Sin P0. Nada de lo encontrado rompe el contrato público ni expone al usuario final; los P1 son incoherencias internas con impacto real en a11y, enforcement o promesas de personalización.
10. Nota final de método
Todo lo afirmado tiene evidencia en ruta:línea o en salida literal de los
guards ejecutados el 2026-07-10 (component:audit 102/32/0 ·
eidos-lint-all 0 inválidos · vitest src/uix/eidos 305/306 · docs:check
0 errors/15 warns). Este documento analiza y no prescribe: la
priorización de qué corregir, en qué orden y con qué alcance es decisión del
autor del framework.