73 KiB
Plan de ataque — fixes de la auditoría clean-room (2026-07-10)
Handoff turnkey + plan aprobable. Creado: 2026-07-11. Rama alpha-0.1-sec-dom.
⏭️ ESTADO AL CIERRE 2026-07-12 + PRÓXIMO ARRANQUE
La tabla §9 de la clean-room está prácticamente entera. Fases F0–F4-C +
SEM-4 pusheadas antes de esta sesión (4addfd7d·29e621b1·b55ca6ee·
082b6623·0e75890f). Esta sesión (2026-07-12) commiteó (rama
alpha-0.1-sec-dom, push al cierre):
| Commit | Qué |
|---|---|
05f0031c |
media-player Batch-4 (SOM-4 cerrado, renderProps 35→0) + THM-2 rollout: 14 superficies a la capa compartida + vocab alpha (surface/surface-hover/hover) + 4 props ampliadas a ComponentColor |
cc7e9a16 |
THM-2 avatar ROOT → paleta a la capa compartida + 33 escalas |
0a5aa387 |
THM-2 CERRADO — Avatar.Badge fuera de la paleta-por-escala por diseño (alineado con el ecosistema) |
d9c3d03f·75fa9400·34d1d46b |
THM-4 CERRADO — 5 hotspots adjudicados (4 = 0 deuda; stepper = 1 redundancia de API consolidada). Columna unused = informativa, sin regla |
Método clave de esta sesión (corrección del usuario): verificar SIEMPRE con
el componente real + control real del demo, nunca DOM fabricado
(feedback_verify_with_real_components_not_synthetic_dom).
Queda de la §9 / cola lateral (arranque próxima sesión):
- F4-D · GAP-* (§7 del audit + §F4-D abajo): ADJUDICADO con referencias +
ejecutado en su mayoría 07-12 (bloque «F4-D — adjudicación profesional»
abajo). ✅ selection/caret/cursor + scrollbar · ✅ assets-variables (fuentes
variable + API de ejes,
7d4f9500) · KEEP layer-4 / depth-z / container- queries (ejes reservados deliberados; corregido 1 comentario falso de depth-z) · 4 límites deliberados registrados (LIM-SND/ICON/TOUCH/RECIPES). Queda: scrim (⏳ decisión de diseño) · gap-3 auto-métricas (opcional). - cascada-vs-firmas (motion, cola lateral): ✅ RESUELTO 07-12 — la
dirección estaba zanjada por doctrina (B.2: gana la entrada, la firma se
silencia); ejecutado B (test de regresión + comentario load-bearing + nota
en
motion.md) como monitor de hazard. Endpoint «una firma por evento» /@layerdiferido y trackeado. Detalle en la tabla F4-D abajo. - THM-2 · rol-directo restantes (cola, opcional): las superficies semánticas
ya colapsaron sus roles a la capa; ampliar su
colora escalas NO tiene sentido semántico (decisión tomada). No queda rollout pendiente salvo que se quiera ampliar alguna más. - Cola menor registrada al final (§Cola lateral): SOM-3 (~6 setTimeout), class-hooks preexistentes, Task #31 pickers, etc.
Detalle por iniciativa: bloques «✅ EJECUTADA/ADJUDICADO» abajo (buscar el ID).
Fuente: la tabla §9 de
docs/audit-new-10-july/audit-active-uix.md— leerla ENTERA antes de tocar nada. El programa anterior está COMPLETO (cierre endocs/audit/components/_cierre.md). Este plan cubre todas las IDs de la §9 (P1/P2/P3 + GAP-*); nada queda sin fase asignada — la tabla de cobertura del final es el checklist.
Reconciliación de estado (la clean-room es snapshot del 2026-07-10)
- SOM-5 (P1) ✅ CERRADO post-snapshot: field
commit-submital Enter (33f346ed) + float-panel kb-gestos por handle-events (bdd17f04). - SOM-3 (P2) ◐: float-panel ya en
uix.timers(bdd17f04); media-provider es PUERTO Svelte-agnóstico exento por diseño. Quedan ~6setTimeout(image-provider, safe-polygon, dismissal, datetime/announcer, tags-input, announce/global). - Badge R-4.3 (única violación R-4.x viva en su §5.3) ✅ cerrada en el censo
R-1.x (
ee9786bc). Su matriz (102/32/0) es previa: hoy 134/0/0.
F2 ✅ EJECUTADA 2026-07-11 (lote mecánico completo)
Gates: matriz 135/0/0 · contracts 38/38 · eidos 310/310 (+pin SEC-1) · sema 178 · morfo 94/94 (+9 selectors) · runtime 43 (+3 pins) · props 8/8 (suite nueva) · baseline 57 · vocab exit 0 · accordion verificado en vivo (outline con foco de teclado real, box-shadow none). Los 13 ítems: DEP-2 (ogl fuera) · DEP-1 (clsx →
toClassStringpropio + suite; soma.md §12 cerrado) · THM-7 (los CINCO selectores manuales de sema.md al builder — y los ejemplos[data-toast-root]apuntaban a un part inexistente: la deriva que el builder previene, demostrada en el propio doc) · THM-6 · EID-4 ×2 · MOR-1 (escape isomorfo + validación de attr-names + 9 tests, incluida la prueba matches() real) · MOR-2 (partMarkerAttrúnica fuente + test de paridad) · MOR-3 (no-op borrado, 0 usos) · SOM-2 (keydowncontinue+ fixture) · SOM-1 (censo async = 0 → no-await, contrato V1 cumplido + pin) · SEM-3 (fallback muerto de applyDominance → skip defensivo; timer tope de awaitExpression cancelado al ganar las animaciones) · SEM-2 (trigger pre-attacha catch con logger →void triggersin unhandled rejection, throw intacto para awaiters + pin) · accordion→outline (patrón tabs, tokens de componente).Dos adjudicaciones de auditoría:
- SEC-1 = YA IMPLEMENTADO desde 2026-05-11 (
assertCssVariableValuerechaza;{}en valores — el auditor buscó escape y no vio el reject; ahora hay pin de test del path de VALOR).- SEM-2(a) = el composition root YA inyectaba su logger al engine (
active-uix.svelte.tseventsOpts.logger ?? logger) — los fallos de canal nunca fueron silenciosos en apps reales. Mi primer intento (fallback a console en el engine) lo TUMBÓ el guard de pureza de sema ("no console en producción") — el guard defendió la arquitectura contra el propio fix: revertido y documentada la garantía real en la opciónlogger.
F3 ✅ EJECUTADA 2026-07-11 (censos con guard)
Gates: matriz 135/0/0 (con R-4.7 viva) · contracts 38/38 · eidos 314/314 · attr-audit verde · baseline propio 57 (el árbol marca 58: +1 AJENO en
src/packs/ambient/ambient.svelte, sesión paralela Scene).
- SOM-3 CERRADO: censo real = cropper/tags-input/float-panel YA migrados; dismissal/safe-polygon/announce-global YA con forma sancionada (scheduler-preferred + fallback sin-timers). Migrados los 2 restantes:
datetime/announcer(clear del live-region) eimage-provider(delay de fallback) — consumidores cableados (date/time-field víasoma.uix.timers; avatar/image víaeidos.timers, verificado en vivo: 20 avatares, 0 shells muertos). Guard ENSANCHADO desoma/componentsa TODO soma (layers/ y datetime/ escapaban) y pasado a EVIDENCIA en vez de allowlist: fichero con setTimeout debe contener también.schedule(.- THM-5 CERRADO: censo real = 15 declaraciones (no 17; tabs/select ya no tienen). R-4.7 nueva (error):
!importantexige válvula same-line/* important: <razón> */— escaneo comment-blanked como R-2.1. Las 15 anotadas con su razón verdadera (inline de soma ×3 · fusión de radios ×8 · opt-out de presencia · cursor del grip · kills de reduced-motion ×2). Válvula + regla asentadas endocs/canon/recipe-contract.md§3/§4.- SOM-4 ADJUDICADO (otra sobre-estimación de la clean-room: "sin censo ni guard" — el script
soma-attr-audit.ts+ el guard con 49 componentes pinneados ya existían): backlog real = knob(1)/ mask-field(1)/timeline(2) — overrides de valor conscientes documentados en el call-site, PINNEADOS — y media-player (35 hits / 10 parts, cero renderProps) = el ÚNICO batch de migración restante ("Batch 4"), dimensionado y pendiente de programación (decisión de usuario).- THM-4 DOCTRINADO: criterio en
architecture/eidos.md§unused — "declarado y jamás estilizado" NO es error (attrs de comportamiento, artefactos de composición: css-field→spin-field, collapsible headless, selectores cross-component), la deuda es el attr declarado PARA un eje visual que nadie consume → consumir o podar. Quema de hotspots (media-player 47 · stepper 30 · color-picker 29 · avatar 28 · trp 26) = lotes con dossier, registrados abajo.
DECISIONES F4 TOMADAS (usuario, 2026-07-12)
- THM-2 = MECANISMO UNIVERSAL: una cascada genérica
--palette-*de 33 bloques (~3-4 KB total) que todas las recetas consumen con fallback propio, en vez del rollout per-component (medido: 21.2 KB button / 20.7 KB toggle → ×15 ≈ +315 KB sobre 345 KB). Sesión de diseño TSC propia (migrar button/toggle al slot genérico + convención + guards). - SEM-4 = AHORA, tras F4-C (2 sesiones: close polimórfico ×5 pickers
patrón Dialog + triggers de los 6 menús/árboles con el caveat
branch-vs-item + flag
emissionD.2). - DOC-1 hogares APROBADOS: build-contract §13 → component-guide; intent-assignment → CANON (ejecutado abajo).
- Orden: F4-C primero (ejecutada abajo) → SEM-4 → resto.
F4-C ✅ EJECUTADA 2026-07-12 (corpus documental)
Gates: docs:check 0 err / 0 warn con I6-links subido a ERROR (contramedida: nunca más 15 enlaces muertos tolerados) · matriz 135/0/0 · vocab exit 0.
- DOC-3: los 15 enlaces muertos, resueltos — repoint (CANON + docs/README → edición FINAL trackeada; el puntero HOMOGENEIZADO ya no existía) o des-link histórico (THEMING_AUDIT ×4, sema-findings, 7 rutas del MOTION_RFC que es doc-RETIRADA).
- DOC-2: cerrado — binarios FINAL ya trackeados por
4addfd7d; el único puntero vivo (CANON §Sources → v2_3.md) reapuntado a FINAL.pdf + book-map.- DOC-1: tabla «Build contract» MIGRADA a component-guide.md con estados modernizados (A3–A5 → LIVE con los guards de hoy: R-4.3, §32, size-bundle, §37…); banners de component-guide + eidos/components/README reapuntados; citas de CANON §3 y sema.md §expression historificadas ("THIS section is the canonical..."); lápida-redirect en el §13 del fósil.
- DOC-4: cadena de hold de sema.md → holds.ts/
SEMA_HOLDS_BY_INTENT(el campo SEMA_MAP eliminado 2026-07-06) · FAQevent:*de notes.md marcada SUPERSEDED por las motion signatures ·gradientañadido a los DOS capstones (reference §10 + channels §5, ahora "the sextet" de verdad) · nota de paleta de demo-authoring corregida (universalPaletteDecls + decisión THM-2 como sucesor del tracker).- DOC-5: recuentos fósiles → forma anti-frágil con fecha (≈140 componentes 2026-07; tabla de purge anotada como medición 2026-06; "25 soma"/"66 morfos" datados).
- DOC-6: §4.11 duplicado → §4.12 (0 citas externas verificadas) · "Known gaps" de Layout Batch 1 anotado HISTÓRICO/superseded · TODO(reconcile) ×2 RESUELTOS: N-6/N-7 recuperadas de git (
d68d2c45^) y canonizadas en eidos.md §pickers; tracker de paleta → sucesor F4-A · authoring.md E2 → canon/tsc.md · air-old des-linkado.- EID-3: excepción de placement físico asentada en la fila RTL de la tabla Build contract. AUX-2:
disabledDommixto documentado en la regla 2 de active-uix.md (writes no-op / reads-that-must-return THROW).
SEM-4 · SESIÓN 1 ✅ EJECUTADA 2026-07-12 — pickers (D.11)
Hallazgo de reconciliación: D.11 era snapshot de mayo — los morfos de los 5 pickers YA NO declaran
closepropio (la reconciliación de-dialoged de 2026-06 los pasó aexpression: 'delegated': su firma de apertura/cierre pertenece al POPOVER que componen). El agujero real HOY: los cierres programáticos (commit(),cancel(), auto-close al seleccionar) flipabanopen.current = falsea pelo, bypaseando eldismissWithdel Popover → save/cancel/select eran perceptualmente SILENCIOSOS (solo sonaban Escape/outside/botón-close, los caminos propios del popover).Fix (upgrade coherente con delegated): delegado de cierre por causa.
PickerShellHandleganasetPopoverDismiss?+ tiposPickerCloseCause('save'|'cancel'|'dismiss') /PickerDismissDelegate(bridge soma picker-shell).- UN inyector (norma N-8) en el eidos PickerShell root — monta dentro del subtree del Popover y entrega
popover.dismissWithal provider vía el handle;nullal desmontar.- Los 5 providers (date, date-range, time, time-range, color):
closeWith(cause)con fallback raw (composiciones headless); 14 sitios migrados — select/commit → 'save' (commit.save+fulfill, "seleccionar ES el compromiso"), cancel → 'cancel' (emerge).- El Picker GENÉRICO queda FUERA a propósito: ya dispara su propia firma (
commit-setfulfill /commit-cancel, diseño S9 "celebrate the new state") — añadir emerge encima doblaría señales (doctrina D.6 de repartir intensidad).- Comentario fósil de picker-shell-close.svelte (citaba
close-commitinexistente) corregido.Verificado en vivo (date-picker; los 5 comparten inyector+patrón): Done → stamp
close · commit · fulfill · active· Cancel →close · emerge · (sin intent) · active, en el content del popover, con cierre real (data-state='closed'). Gates: matriz 141/141 (la sesión paralela sumó 6 morfos de texto — todos PASS) · suites pickers/popover verdes · baseline propio 57 (+1 ajenotext-blur.tsde la sesión paralela).
SEM-4 · SESIÓN 2 ✅ CERRADA POR VERIFICACIÓN 2026-07-12 — D.6 ya estaba vivo
D.6 era otro falso positivo por registro stale (como SEC-1 y parte de SOM-4): los 6 providers YA emiten vía
runtime.trigger— dropdown/context (open/close/commit-select×2), menubar/nav-menu (commit-select), trees (emerge-expand/collapse+commit-select) — y el caveat branch-vs-item estaba resuelto de punta a punta (emisión confallbackTarget: branchEl/rowEl+ packs cononBranch/onRow). Verificado en vivo:open · emerge · activeycommit-select · commit · affirmen dropdown-menu. El registro D.6/D.11 de book-deviations quedó RECONCILIADO (notas RESUELTO con fecha y lección: el registro se actualiza en el mismo pase que el cableado).D.2 (flag
emission) — ADJUDICADO: DESCARTAR. Su propósito era hacer auditable la dormancia de D.6, que ya no existe; un check estático de emisión sería frágil (nombres dinámicostrigger(eventName)) y el flag sería feature especulativa sin consumidor (Simplicity First). Si una dormancia real reaparece, la lección anti-stale de arriba es la contramedida barata.Con esto SEM-4 COMPLETO (sesión 1 = pickers
b55ca6ee; sesión 2 = verificación + reconciliación del registro).
THM-2 ✅ MECANISMO + 5 SUPERFICIES 2026-07-12 (paleta universal)
El mecanismo genérico YA existía (
universalPaletteDeclsauto-completaba las 33 escalas para cualquier receta con tokenspalette-*); el hueco real era el prefijo de componente (--button-palette-*) → cada superficie pagaba ~21 KB, por eso solo button/toggle. Colapso ejecutado:
- Capa compartida (
renderSharedPaletteLayer): UNA cascada[data-color='{role|scale}'] { --palette-{slot} }(8 roles + 33 escalas, ~19 KB una vez) reemplaza las copias per-component.- Forward de presencia (
renderRecipePaletteForward): toda receta conpalette-*(público) o_palette-*(privado) recibe[data-{c}][data-color] { --{c}-palette-{slot}: var(--palette-{slot}, host-default) }. La guarda de PRESENCIA es la seguridad de anidamiento (un hijo sin color dentro de un ancestro teal se queda en su default — verificado en vivo).- Generalización a paleta privada: checkbox/radio-group/switch ganan las 33 escalas con CERO cambio de CSS (ya consumen
--_{c}-palette-*).universalPaletteDeclseliminado; 2 guards migrados al nuevo modelo (recipe-css-contract "shared layer + forward"; active-eidos-config contrast en la capa compartida).Verificado en vivo (button + checkbox):
color="teal"→ escala teal exacta ·affirm→ rol exacto ·primary→ primary · nested-nocolor en ancestro teal → primary (sin contaminación). Peso 345K → 329K (−16K) Y las 17 superficies ya PUEDEN usar la capa. Gates: matriz 141/141 · eidos 314 · recipe-css-contract 98/98 · contracts 38/38.
Tracker de rollout THM-2 (sucesor de pendiente_color_demos.md)
El rollout tiene DOS ejes (descubrimiento 2026-07-12 por crítica del usuario
"usa componentes reales, no divs"): (1) el token en la capa compartida
(colapso de peso + resolución CSS); (2) el tipo de la prop color del
componente, que GATEA qué colores son alcanzables. El rename del token hace que
la cascada resuelva CUALQUIER data-color que el componente emita — pero si la
prop está tipada ColorRole, el componente NUNCA emite una escala, así que
color="teal" es error de tipo y las 33 escalas quedan LATENTES. Verificación
correcta = componente REAL + control REAL del demo (el picker de 33 escalas),
nunca <div data-color> fabricado (feedback_verify_with_real_components_not_synthetic_dom).
Capa compartida (eje 1) — DONE (13): button, toggle, checkbox, radio-group,
switch · tags-input, stepper, badge, card, select, file-upload, editable ·
tag-group (2026-07-12, variant-aware: solid/outline→slots canónicos,
soft→slots ALPHA surface(a2)/surface-hover(a3) que AÑADÍ a
PALETTE_SLOT_STEP + capa compartida + forward — extensión aditiva del
vocabulario, aprobada por el usuario; merge outline-fg→text + poda de 8
intermedios huérfanos).
Prop color ampliada a ComponentColor (eje 2) — DONE + verificado real
(3): badge, tag-group, tags-input (2026-07-12, ColorRole→ComponentColor
- picker de 33 escalas en el demo + verificado con el componente real: click
en el chip "teal" del demo → el componente emite
data-color="teal"→ resuelve escala teal exacta, incl. surface alpha translúcido). button/toggle/card ya la tenían. Semánticos, se quedan restringidos (decisión usuario): checkbox/ radio-group (AffirmativeColorRole) · stepper (ProgressiveColorRole) · editable (EditableColorRole) · select/file-upload (ColorRole, accents de form-control). Para éstos, THM-2 = colapso de peso behavior-preserving (roles).
toggle-group ✅ HECHO + verificado real (2026-07-12): colcoreaba vía
composición cross-recipe en la paleta pública de Toggle (4 roles). Migrado a
ruta a la capa compartida: (1) añadí el slot hover(4) a PALETTE_SLOT_STEP
— behavior-preserving para Toggle (--toggle-{role}-hover=--color-{role}-hover)
y de paso arregla el hover-en-escalas de Toggle (estaba latente-roto); (2)
quité el bloque composition del recipe (era su ÚNICO consumidor — el DSL de
composición no puede presence-scope [data-color] sin valor) y hand-authoré
UNA regla en toggle-group.css: [data-toggle-group][data-color] [data-toggle-group-item] { --toggle-palette-{slot}: var(--palette-{slot}) }
(presence-guard = anidamiento seguro; cubre roles + 33 escalas); (3) amplié
ToggleGroupColor a ComponentColor (+ sentinels absent/unknown) + picker.
Test active-eidos-config que pinneaba la composición → actualizado (el DSL
emitComposition queda como infra sin consumidor actual). Verificado real:
chip "teal" → grupo emite data-color="teal" → item (un Toggle) obtiene
--toggle-palette-solid=teal-9, -hover=teal-4, -border=teal-7, -text=teal-11;
Toggle sin regresión (affirm-hover preservado, teal-hover ahora funciona).
avatar ROOT ✅ HECHO + verificado real (2026-07-12): la arruga #1 (default
sutil sin-color) resultó inalcanzable — avatar.svelte hace
color ?? 'neutral', así que el avatar SIEMPRE emite data-color canónico y el
matrix siempre casa (host = fallback muerto). Migración limpia sin borrar
intermedios:
avatar.svelte:isCanonicalColorahora incluyePALETTE_SCALES(una escala va adata-color, no a la vía custom-CSS — clave:teales TAMBIÉN un color CSS, antes caía a--avatar-color-custom).recipes/base.ts:_palette-{solid,surface,contrast,text,border}(solo host fallback; el forward rutea roles + 33 escalas) +_bg/_fg/_borderpasan de matrix (variant×color) a variant-only leyendo--_avatar-palette-*. El matrix helper + los 48 intermedios SE QUEDAN (el badge sigue usándolos → 0 orphans). avatar.css SIN CAMBIOS (lee--_avatar-bg/fg/borderigual).- Verificado real (control del demo + picker de escalas añadido): chip "teal"
→ el avatar emite
data-color="teal"; affirm+soft behavior-preserving (--_avatar-bg=--color-affirm-surface); teal+soft=--scale-teal-a2(surface translúcido), teal+solid=--scale-teal-9.AvatarColorya eraColorRole | (string & {})→ acepta escalas sin cambio de tipo.
Sub-parte Avatar.Badge — FUERA de la paleta-por-escala A PROPÓSITO
(decisión de diseño cerrada 2026-07-12, alineada con el ecosistema): es un
chip de estado DECORATIVO (aria-hidden) con su PROPIO color/default
(affirm-solid), independiente del avatar. Acepta roles/intents (estado:
online→affirm, alerta→threat) + custom-CSS (cualquier string). Eso es
EXACTAMENTE lo que hace la referencia: MUI/Spectrum/Bootstrap → color de badge
= semántico (rol/estado); Chakra bg/Mantine Indicator/Ant color → color
directo/CSS. Ningún framework mete el badge en un sistema de escala de paleta
por instancia — un badge de estado no necesita teal/plum como escala de
marca. Migrarlo (forward hand-authored por-parte + ~36 podas de intermedios)
sería sobre-ingeniería que se aparta de la convención. Por tanto NO es deuda:
es el diseño correcto. THM-2 CERRADO.
REVERSIÓN 2026-07-18 (decisión de diseño del usuario). Los SUBCONJUNTOS por componente que THM-2 dejó (
AffirmativeColorRole,ProgressiveColorRole, narrows porExtract<>) se revierten:color= sistema completo (ComponentColorProp: rol / intent / 33 escalas / valor crudo) en todos los componentes, sin excepciones — incluida la tinta de contenido (unión aditiva que conserva su eje) y los que THM-2 había dejado fuera "por diseño" (Avatar.Badge incluido, abierto en Fase 1 de la iniciativa). Proceso completo
- guard estructural:
open-color-cage-2026-07.mdy el test "keeps every component*Colorprop open" enrecipe-css-contract.test.ts. La doctrina queda registrada entheming/reference.md §25.
Rename limpio (patrón, para los que tenían slots canónicos): renombrar la clave
del token en recipes/base.ts a _palette-{slot} + los consumidores
--_{c}-{slot}/--_{c}-accent-{slot} en el .css; el forward auto-emite
[data-{c}][data-color]{--_{c}-palette-slot: var(--palette-slot, host)}.
Baseline estable = 9 errores AJENOS en src/ (4 connection + 1 nav-menu
sideOffset + 4 file-upload-soma props['aria-label'], todos pre-existentes/
track paralelo, probado con git stash). Registrado en docs/next-features.md §6.
media-player Batch-4 ✅ EJECUTADA 2026-07-13 — SOM-4 CERRADO (attr-dedup completo)
Gates: soma-attr-audit media-player = 0 hits (35→0) · provider test 9/9 · guard soma-attr-audit con
media-player: 0verde · contracts 38/38 · baseline propio 57 restaurado · navegador (dev real): DOM de cada botón con sus attrs de morfo emitidos por renderProps + labels LOCALIZADOS (Reproducir/Silenciar/Avanzar/Pantalla completa), y el camino reactivo verificado en vivo (mute click →data-stateoff→on +aria-pressedfalse→true +aria-label"Silenciar"→"Activar sonido"; play click → paused→playing + "Reproducir"→"Pausar").Hallazgo de reconciliación: el ceiling REAL era 0, no un residuo. El handoff asumía "dejar el
aria-labelcomo override" (patrón carousel), pero carousel guarda su label porque su morfo declara untranslationRefDEFAULT y soma añade la lógica prop-or-translation encima. El morfo de media-player declaraaria-label: propRef('label')a secas — el label ES un prop, así que lo correcto es ALIMENTARLO como source (props: { label: () => this.label }) y dejar que renderProps lo emita. Cero override.Patrón aplicado a las 10 sub-partes (
media-player-provider.svelte.ts): por parte, registrar enruntime.part(...)los sources que el morfo lee —props: { label }(buttons),states: { on }(mute/fullscreen/pip/caption, para el pardata-state+aria-pressedvíastateRef('on')),props: { direction }(seek),props: { type }(time) — y en el.assert()cambiar...runtimePart.propspor...runtimePart.renderProps(), dejando SOLO lo contract-only que soma posee (play-buttondata-statesinvalue;data-disabledde presencia;data-mediade media/aria-hiddende poster/buffering salen de renderProps). Eldata-mediade la partemedialo resuelve renderProps contra el sourcemediade nivel-componente.Ajuste de test: en
media-player-provider.svelte.test.tslos accesos indexadosprops['aria-label']/['aria-pressed']/['data-state']dejaron de tipar (elas constsobre el bag ya no expone esas claves — vienen del spreadrenderProps():Record<string,unknown>) → convertidos atoMatchObject(mismo aserto de runtime, type-safe; estilo del test de carousel). Valores en runtime idénticos byte-a-byte (los 9 tests siguen verdes).Cierre en el guard:
soma-attr-audit.test.tsgana'media-player': 0con dossier de por qué es 0; el comentario "Still UNMIGRATED" retirado.
THM-4 · media-player ✅ ADJUDICADO 2026-07-12 — 0 deuda real
Dossier de los 47 unused (columna de eidos-lint media-player) contra la
doctrina eidos.md §unused (comportamiento/composición = legítimo; "declarado
para un eje visual que nadie consume, sin razón" = deuda). Clasificación
completa — cero deuda silenciosa:
- 21 · estados de botón (play/mute/fullscreen/pip/caption/seek/settings ×
data-state/data-disabled/data-direction) → composición legítima: el cromo del botón (state-layer, disabled, focus) vive en el<IconButton>compuesto;data-stateconduce el swap de glifo en Svelte (JS), no en CSS. - 7 · sliders (time/volume ×
data-orientation/data-disabled/data-seeking) → composición: el visual es el[data-slider]compuesto. - 11 · flags de estado del root (
data-paused/muted/seeking/ended/error/fullscreen/pip/live/can-play/captions+controls='visible') → espejos de comportamiento del elemento media (el morfo declara toda la superficie behavioral;controls='visible'es el estado por defecto — solohiddense estiliza). - 6 · resto (media-part
data-media[lo estiliza el padre], timedata-type='current'/'remaining'[default; solodurationdistingue], captions/buffering/controlsdata-*propios [driven por el padre / superficie opcional]) → legítimo. - 2 (nota) · superficies v2 documentadas:
settings-button(data-state open/closed + disabled) y el customcaptionsNO tienen provider/CSS/render — son roadmap v2 declarado a propósito (ambos READMEs lo marcan v2). No es deuda silenciosa; es superficie futura documentada. Candidatas a poda SOLO si se abandona el v2.
Conclusión: el count alto es porque media-player es un composite grande (10 partes + 10 espejos de engine), no porque tenga deuda. Nada que consumir/podar sin decisión de feature (p.ej. un badge LIVE o un overlay de error serían diseño nuevo, no THM-4). media-player adjudicado, 0 deuda.
THM-4 · stepper ✅ ADJUDICADO 2026-07-12 — mayoría legítima + 1 redundancia de API
De los 30 unused: ~la mitad legítimos (data-state='incomplete' = estado
base; data-orientation='horizontal' = default; estados de content). El
hallazgo: el morfo declara en CADA parte (item/trigger/indicator/separator)
DOS representaciones del mismo estado — data-state (enum complete/current/
incomplete) Y los booleanos data-current/data-complete/data-incomplete
(propRef, severity optional). El propio CSS las usa inconsistentemente:
enum en trigger/indicator, booleano data-complete en el separator. Así que NO
es deuda-sin-consumidor (la forma booleana SÍ se consume en el separator) —
es redundancia: cada parte declara ambas formas y usa una.
CONSOLIDADO ✅ (decisión de usuario 2026-07-12) en data-state (canónico),
API pública unificada. 5 sitios: morfo (podados 12 declaraciones booleanas de
item/trigger/indicator/separator) · provider (podados los sources
current/complete/incomplete de item/indicator/separator + los writes explícitos
del trigger + los getters isComplete/isIncomplete — isCurrent se queda,
lo usan aria-current/tabindex) · provider-test (assertions booleanas fuera) ·
README (10 filas booleanas fuera) · separator.css ([data-complete] →
[data-state='complete']). Verificado real (demo, botón Next): estados por
data-state (complete/current/incomplete), booleanos ausentes, separator
completo pinta --_stepper-palette-solid vía data-state='complete']
(behavior-preserving). Gates: provider-test + contracts verdes · baseline 9 ·
stepper unused 30→18 (total catálogo 1152→1140).
THM-4 — lectura emergente (2 lotes)
media-player (0 deuda) + stepper (0 deuda-real, 1 redundancia de API — consolidada): los hotspots son mayormente legítimos (composites grandes
- superficie behavioral); la deuda-sin-consumidor genuina es rara. Lo accionable son decisiones de diseño (como el dual-API de stepper que el usuario decidió consolidar), no podas mecánicas. Esto CONFIRMA la predicción de la doctrina ("la mayoría son legítimos") y sugiere que la tasa de deuda NO justifica una regla de máquina — THM-4 es sobre todo adjudicación documentada.
THM-4 · color-picker (29) + avatar (28) + time-range-picker (26) ✅ ADJUDICADOS 2026-07-12 — 0 deuda
- color-picker (29) → 0 deuda:
data-state='open/closed'(root/content/ trigger) = composición (de-dialoged,color-picker-content.sveltecomponePopoverContent— el estado lo pinta el Popover) ·data-last-action=*= sema/dismissal (escrito por prewrites close-commit/cancel/dismiss, lo consumen sema packs) ·data-format/data-disabled/invalid/readonly/required- area/channel-slider/swatch = behavioral + composición (Sliders/Field compuestos).
- avatar (28) → 0 deuda:
[data-avatar][data-color='ROLE']×8 = artefacto de capa compartida — mi migración THM-2 movió el color al forward[data-avatar][data-color]presence-guarded, así que los per-rol dejaron de vivir en avatar.css (viven en la capa) → el count SUBIÓ por una MEJORA, no deuda ·data-variant='soft/solid'= lo consume el recipe token (generado, verificado 2 hits) — punto ciego del lint per-fichero (solo ve avatar.css) · badge[data-color='ROLE']×8 = el matrix consume la forma compuesta[variant][color], no la per-rol suelta ·data-status=*= estado de carga behavioral (loading/idle SÍ se usan; loaded/error = default). - time-range-picker (26) → 0 deuda:
data-state='open/closed'= composición (Popover) ·data-last-action/data-starting-style/data-ending-style= sema + motion @starting-style ·*-slider[data-endpoint/disabled]+ day-period-toggle = composición (Sliders/Toggle) + behavioral (endpoint) ·data-disabled/required= form-state.
THM-4 — CONCLUSIÓN (5 hotspots adjudicados)
Los 5 mayores hotspots (156 unused juntos) están adjudicados: media-player,
color-picker, avatar, trp = 0 deuda (composición + behavioral + capa
compartida); stepper = 1 redundancia de API, consolidada (75fa9400).
Deuda real total en los 5 mayores = 1 ítem (arreglado). Esto CONFIRMA la
doctrina ("la mayoría son legítimos") y sella la decisión: la columna unused
se queda informativa, sin severidad/regla de máquina — la tasa de deuda no la
justifica. El resto del catálogo (~1140) es la cola larga de las mismas
categorías legítimas. Hallazgo colateral: migrar un componente a la capa
compartida (avatar) SUBE su unused (los per-rol pasan a artefacto de capa) —
el count alto no es señal de deuda. THM-4 CERRADO.
Cola pendiente (sin orden fijo — algunas requieren decisión de usuario)
- THM-2 rollout — CERRADO (ver §THM-2;
Avatar.Badgefuera por diseño). - THM-4 hotspots — CERRADO: 5 hotspots adjudicados (media-player/color-
picker/avatar/trp = 0 deuda · stepper = 1 redundancia consolidada
75fa9400). Columnaunused= informativa, sin regla. Dossiers arriba. - F4-D GAP-* — menú de personalización (§7): pendiente de SCOPING del usuario (qué se arranca vs qué se registra como límite deliberado).
- cascada-vs-firmas (motion) — cola lateral: las reglas
[data-stagger]sombrean firmas de evento de igual/menor especificidad.
(histórico) ARRANQUE F4 (iniciativas, cada una con decisión)
F0–F3 hechas. F4 espera scoping del usuario por iniciativa (ver §F4 abajo):
F4-A THM-2 paleta-33 (medir peso primero) · F4-B SEM-4 firma dormida
(close ×5 + packs ×6 + flag emission) · F4-C corpus documental
(DOC-1/2 decisiones + barrido DOC-3/4/5/6 + EID-3 + AUX-2 + docs:check a
error) · F4-D menú GAP-*. Lotes ya dimensionados que pueden entrar en
cualquier hueco: media-player Batch-4 (renderProps, 35 hits) · quema
THM-4 de hotspots · cascada-vs-firmas (motion, en cola lateral).
(histórico) ARRANQUE F3 (censos con guard)
F2 hecha (arriba). F3 según la sección de abajo, orden sugerido: SOM-3
(los ~6 setTimeout restantes a uix.timers, patrón float-panel, y retirar
exenciones del guard) → THM-5 (17 !important: anotación
/* important: <razón> */ + regla de máquina para no anotados) → SOM-4
(censo providers re-declarando attrs morfo → tabla + guard WARN + mecánicos;
si el volumen es grande, plan por lotes para aprobación) → THM-4
(doctrina para selectores sin consumidor + quemar hotspots). Tras F3 → F4
(iniciativas, cada una con decisión de usuario).
(histórico) ARRANQUE F2 (lote mecánico)
Estado al cierre 2026-07-11: F0 ✅ + F1 ✅ (commiteadas — 4addfd7d +
el handoff posterior). Gates vigentes que deben MANTENERSE: matriz
135/0/0 · contracts 38/38 · eidos 309/309 · sema 178 · baseline
57 · vocab exit 0. Leer los bloques «EJECUTADA» de F0/F1 (abajo) para
el contexto y arrancar F2 directo — es el lote SIN decisiones de usuario.
Orden sugerido (barato → delicado, no-cascada: ítem → gates → siguiente):
- DEP-2 ogl fuera de package.json (+lockfile) · DEP-1 clsx inlineado (cerrar el "pending" de soma.md §12).
- One-liners: THM-7 (ejemplo sema.md → semaSelector) · THM-6
(menu-dial
--radius-full, 1px→ 9999px) · EID-4 ("31-scale" → 33 ×2). - MOR-1 escape/validación en semaSelector (isomorfo, sin CSS.escape
del DOM) + tests con
"y]· MOR-2 des-duplicar convención de marker · MOR-3_resetCompileCacheimplementar-o-borrar. - SOM-2 keydown
continueen match sin handler + test · SOM-1 censo de handlers async → si 0, quitar el await (si hay, DECISIÓN #3). - SEM-2 logging por defecto (dev) en canales sin logger + vía
void triggersin unhandled rejection (throw intacto para awaiters) · SEM-3 fallback muerto applyDominance + cancelar timer de awaitExpression · SEC-1 serializador renderCssVariables rechaza;/}embebidos + test. - accordion → outline (hallazgo F1:
outline:none+ box-shadow = cero anillo en HCM) — con verificación visual, patrón tabs.css:102.
Después de F2 → F3 (censos SOM-3◐/SOM-4/THM-5/THM-4). Las iniciativas F4 (THM-2 paleta · SEM-4 firma dormida · DOC-1/2 · GAP-*) esperan decisión.
Principios de secuencia
- La máquina primero. Los fixes que amplían el enforcement (THM-1, MOR-4, guard de enumeración) van ANTES que el resto: destapan trabajo que las fases posteriores deben ver en el tablero.
- Rojo = parar la línea. Una suite/guard en rojo (hoy: THM-3) se arregla antes de cualquier otra cosa de la sesión. Ninguna sesión cierra en rojo.
- No-cascada: un fix → gates → el siguiente. Fix sistémico ⇒ guard que lo fije (doctrina del programa 2026-06-26).
- Iniciativas ≠ fixes. THM-2, SEM-4, DOC-1/2 y los GAP-* requieren decisión de alcance del usuario ANTES de ejecutar — este plan las dimensiona y propone, no las arranca en frío.
F0 — La máquina, en verde y completa ✅ EJECUTADA 2026-07-11
Resultado: matriz 135/0/0 (card-group dentro) · contracts 38/38 (guard MOR-4 nuevo) · suite eidos 306/306 · baseline 57 · vocab exit 0. Verificación en navegador: shake
commit-blockvivo (block-shake,--duration-moderate, stampcommit · risk) + chevrons a--opacity-muted(0.65 computado). Extras que destapó la ejecución:
- El spectrum de color-picker había sido creado para evadir los escáneres (su header lo admitía; reescrito con doctrina honesta) y
recipe-css-contract.test.tstenía el MISMO agujero de enumeración — cerrado también (concatena hermanos;color-pickerenFIXED_TONE_COMPONENTS); R-2.1 ahora caza hsl/hwb/lab/lch/oklab/oklch y escanea texto sin comentarios.commit-blockpromovido a firma global deEidosConfig.motion(block-shake+ reduce) — completaba un peldaño ausente (el ladder tenía fulfill/affirm/threat): ahora TODO rechazo de ToggleGroup lleva la firma de denegación en vez del settle de éxito.- Hallazgo sistémico (pre-existente, registrado abajo): las reglas de cascada de entrada (
[data-stagger][data-state='open'] > [data-animation-style], (0,3,0)) retienenanimationpara SIEMPRE y sombrean cualquier firma de evento de igual/menor especificidad — el shake shipped de card-group YA estaba muerto en grupos conmotion. Card-group lo resuelve re-afirmando a (0,4,0); el arreglo sistémico queda en cola.morfo/internal/existe (picker-shell.ts vive ahí); el guard MOR-4 acepta ambas raíces.icon-button(alias puro de Button, 0 css) ysvg(glifos) exentos con auto-vigilancia (si les aparece css, el guard exige morfo);spin-fieldqueda como única capa sin contrato (F1). Gap real registrado en el dossier de card-group:disabledno llega al Collapsible (la tabla de props promete selection+toggle).
| Ítem | Qué | Tamaño |
|---|---|---|
| THM-3 P2 | Voltear el canario vencido: toggle ya lee 0 class-hooks → lint.test.ts:110 a toBe(0). Suite eidos 305/306 → 306/306. |
S |
| THM-1 P1 | Extender component-audit.ts:698,756 a todos los *.css del directorio del componente (hoy solo {kebab}.css). Pagar lo que destape: hsl() ×8 en spectrum (anotar /* literal: physically-fixed */), opacity: 0.6 (calendar-select.css:27), fallback mágico var(--calendar-month-gap, 36px). Mismo espíritu que la válvula R-2.1: máquina primero, luego los fixes, no-cascada. |
M |
| MOR-4 P1 | Morfo para card-group (doctrina morfo-first, scope:['eidos'] si procede): leer CSS+wrapper existentes para derivar parts/attrs, recordar la decisión de cardinalidad 2026-06-27 (ToggleGroup OWNS cardinality) en el docblock. Entra en la matriz (→135) con dossier F-1.x y R-* limpias (incluida su opacidad literal, TOK-3). |
M |
| Guard de enumeración (contramedida) | La matriz enumera por MORFO_DIR — un dir de eidos sin morfo es invisible (así se escondió card-group). Guard nuevo: eidos/components/* ↔ morfos, con exenciones explícitas (picker-shell, spin-field = capas compartidas). |
S |
Salida de F0: matriz 135/0/0 · contracts 38/38 · suite eidos 306/306.
F1 — Coherencia P1: foco y voz ✅ EJECUTADA 2026-07-11
Resultado: matriz 135/0/0 · contracts 38/38 · eidos 309/309 (+3 guards de fundación) · sema 178/178 · soma 1041 verde (2 rojos ajenos: chronos = pista rota pre-existente · attr-audit = timeout de contención, PASA aislado) · baseline 57. Hallazgos de la ejecución:
- EID-1: el
outline: none !importantglobal que justificaba el box-shadow no existe — la única mención en TODO el árbol era el propio comentario de archetypes.css (fósil de la era Tailwind). Y el anillo de fundación pintaba EN PARALELO con los de receta (propiedades distintas, mismos tokens → duplicación invisible, verificado en vivo). Migrado a outline +--focus-ring-*envuelto en:where()(la fundación pierde ante cualquier receta al compartir ya la propiedad);--focus-ring-bgera hook muerto (0 consumidores) — retirado. Verificación en vivo: Close de dialog = UN anillo (el de Button, box-shadow none) · sonda desnudadata-archetype= anillo de fundación · light+dark. Asientos en reference §32 + changelog §32; bloque forced-colors queda como suelo defensivo.- EID-2: disabled
0.5→var(--opacity-disabled)(=0.4 — las recetas YA usaban el token: disabled se veía distinto según la capa) ×3
- close hover
0.85→var(--opacity-hover)(=0.9, token por rol). Verificado en vivo (0.4 computado).- F1.3: 3 guards nuevos en recipe-css-contract ("Foundation CSS discipline": color/typo/opacity sobre
eidos/*.css+lib/*.css, enumeración dinámica) + fallback fantasma#fffpagado en menu-indicator. picker-shell/spin-field ya entraban por dir.- SEM-1: runtime alineado al canal — prioridad = INTENT solo (threat/loss → assertive); pinneado con
liveRegionFixtureMorfo(commit+threat = el caso divergente) en runtime.svelte.test. AUX-1: doctrina "who owns what" en sema.md §Announce (1 sinkuix.announce· 2 emisores ·<Announce>app-level).
| Ítem | Qué | Tamaño |
|---|---|---|
| EID-1 P1 | Anillo de foco de la fundación (archetypes.css:162-167) en box-shadow vs §32 que canoniza outline. Propuesta: retirar el condicionante (el outline: none global de web/routes/layout.css — inversión de dependencia app→fundación), migrar el anillo a outline con --focus-ring-*, conservar la vía forced-colors. Verificación visual obligatoria (focus visible en varios componentes, light/dark; 57/130 recetas caen a este anillo). Alternativa (si el usuario prefiere): anotar la excepción en §32. DECISIÓN #2. |
M |
| EID-2 P2 | Misma pasada, mismo fichero: opacity: 0.5/0.85 literales en la fundación → var(--opacity-disabled) / token o anotación. |
S |
| Fundación bajo R-* (contramedida) | La fundación violaba su propia disciplina porque nadie la escanea. Añadir archetypes.css + capas compartidas sin morfo (picker-shell/*.css, spin-field.css) al alcance del escáner R-2.x/R-4.x o test dedicado. Nota THM-1 (2026-07-11): recipe-css-contract.test.ts ya cubre esas capas para color/typo/phantoms (enumera por directorio y ahora concatena hermanos); lo que falta aquí es la vía component-audit (enumera por morfo). |
S |
| SEM-1 P1 | Unificar la prioridad de live-region: runtime (family==='signal' ∧ intent∈{threat,loss} → assertive, runtime.svelte.ts:725) vs AnnounceChannel (solo intent, announce.ts:55). Propuesta: canónica = intent (lo que sugiere el libro); alinear el runtime + test que fije la política. DECISIÓN #1. |
S–M |
| AUX-1 P3 | Mismo territorio: párrafo de doctrina que ordene los TRES mecanismos de live-region (uix.announce zwsp-toggle · fallback del canal · <Announce> soma) — quién es para qué capa. |
S |
F2 — Lote mecánico P2/P3 (≈1 sesión, sin decisiones de usuario)
Batch de fixes pequeños, cada uno con su test donde aplique; gates al final:
- DEP-2 P2 — eliminar
ogldepackage.json(0 imports; doctrina zero-dependence en su forma más barata). - DEP-1 P2 —
clsxfantasma (soma/props/props.ts:1): inlinear implementación propia mínima (doctrina zero-dependence) y cerrar el "pending decision" dearchitecture/soma.md§12. - MOR-1 P2 —
semaSelector: escape propio de valores (isomorfo, sinCSS.escapedel DOM) + validación de nombres de attr en matchers string (selectors.ts:141-155); tests con"y]. - MOR-2 P3 — des-duplicar la convención de marker (
selectors.ts:121re-implementa lo que el compilador ya posee): exportarla de un solo sitio. - MOR-3 P3 —
_resetCompileCache()no-op: implementar (WeakMap reasignable) o eliminar según uso real. - SOM-1 P2 —
await handler()vs contrato "handler síncrono": censo de handlers async registrados; si 0 → quitar el await (contrato cumplido, timing más limpio incluso para síncronos); si hay async → DECISIÓN #3. - SOM-2 P2 —
keydowncorta en el primer match sin handler (runtime.svelte.ts:562):continueen vez dereturn false+ test con dos planes de la misma tecla. - SEM-2 P2 — política de errores: logging por defecto (dev) en fallos de
canales sin
loggerinyectado; el caminovoid trigger(...)no puede producir unhandled rejection en producción (catch+log en la vía void, throw intacto para llamadores que awaitean). - SEM-3 P3 — motor: fallback muerto de
applyDominance(engine.ts:518)- cancelar el timer tope de
awaitExpressioncuandofinishedgana (callback fantasma ≤1500 ms).
- cancelar el timer tope de
- SEC-1 P2 — endurecer el serializador de
renderCssVariables: rechazar;/}embebidos en valores + test. - THM-7 P2 — ejemplo de
sema.md§packs con selector manual → reescribir consemaSelector(el código real ya está limpio). - THM-6 P3 —
var(--radius-full, 1px)en menu-dial.css:293 →9999px. - EID-4 P3 — comentarios "31-scale" → 33 (
active-eidos.svelte.ts:653,arts/color/README.md:65).
F3 — Censos con guard (≈2 sesiones; SOM-4 puede parir lotes)
| Ítem | Qué | Tamaño |
|---|---|---|
| SOM-3 P2 ◐ | Migrar los ~6 setTimeout restantes a uix.timers.schedule (key+meta, patrón float-panel) y retirar sus exenciones del guard de contracts. |
S–M |
| THM-5 P2 | Doctrina para los 17 !important: anotación en línea (/* important: <razón> */) + regla de máquina R-* nueva que marque los no anotados. color-picker (4) y group (8) ya tienen razón — anotarla; drawer/float-panel/tabs/select: revisar (fix o anotar). |
S–M |
| SOM-4 P2 | Censo de providers que re-declaran role/aria-* en props getters en vez de renderProps() ("most existing providers", guía §Part props). Entregable: tabla censo + guard (WARN) + migración de los mecánicos; si el volumen es grande → plan por lotes tipo S4 con sizing para aprobación. |
M (censo) |
| THM-4 P2 | Doctrina para los 1150 selectores de contrato sin consumidor CSS: criterio en eidos.md (attr de comportamiento puro = legítimo y declarado; resto = consumir o podar del contrato) + quemar hotspots (media-player 47 · stepper 30 · color-picker 29 · avatar 28 · trp 26; words 149 excluido). |
M |
F4 — Iniciativas con decisión de usuario (cada una, su sesión)
F4-A · THM-2 P1 — paleta 33 por instancia (2/17 superficies)
Es un ROLLOUT, no un fix. Triple acción de la clean-room + sizing previo:
- Medir el delta de CSS generado por superficie (33 escalas × 15
componentes en
generated/base.css— el peso decide el alcance). - DECISIÓN #4: rollout completo (15), subconjunto razonado, o
re-redactar la promesa de demo-authoring §6 ("acepta las 33") al estado
real. Mecanismo real:
universalPaletteDecls(render-css.ts:1766) — el doc cita un helper inexistente (paletteScaleDecls): corregirlo. - Tracker con hogar: el registro de rollout borrado renace en
docs/theming/(contramedida: borrar un tracker exige puntero sucesor).
F4-B · SEM-4 P2 — firma perceptiva declarada y no emitida
Deuda "sin urgencia" desde 2026-05/06 — ponerle fecha (DECISIÓN #5):
- close polimórfico inerte (5 pickers togglean
opensin disparar el evento): cablearruntime.trigger('close-*')con el patrón dialog/popover — el intent per-evento ya está declarado. - packs dormant (6 morfos menú/árbol sin emitir): cablear triggers en providers (D.6).
- flag
emission(D.2) en el morfo para que esto sea auditable por la máquina (cumple la regla 2-de-3: sema + auditoría).
F4-C · Corpus documental (DOC-1/2 P1 · DOC-3/4 P2 · DOC-5/6, EID-3, AUX-2 P3)
- DOC-1 — migrar el contenido canonizado FUERA de audit/deprecated:
registro de intent-assignment (CANON §3 ←
_veredictos.md) y build-contract (component-guide ← ARCHETYPE_COHERENCE §13) a E1/E2; los audits quedan como historia. DECISIÓN #6 (hogares exactos). - DOC-2 — ancla editorial: existe edición FINAL sin trackear
(
docs/Disenando_lo_que_ocurre_FINAL.{pdf,docx}, 426 págs.) y los punteros apuntan a ficheros inexistentes (README.md:138 · CANON.md:295). DECISIÓN #7: ¿commitear los binarios (hoy excluidos por norma) o punteros a edición externa? Reconciliar en cualquier caso. - Barrido editorial (una sesión): DOC-3 enlaces muertos ×15 · DOC-4
secciones stale (cadena de hold sin
SEMA_MAP.families[*].hold,event:*obsoleto en notes.md,gradientomitido en reference §10 + channels §5,paletteScaleDecls) · DOC-5 recuentos fósiles (95/62/25/66/31 → reales) · DOC-6 menudencias (§4.11 duplicado, TODO(reconcile) huérfanos, stub TSC, enlace air-old) · EID-3 párrafo de excepción de placement físico en el mandato logical-RTL · AUX-2 documentardisabledDommixto en la tabla de contratos. - Contramedida: tras limpiar,
docs:checkenlaces muertos WARN→error (que nunca vuelva a tolerarse ×15).
F4-D · GAP-* — menú de personalización (§7; el usuario elige)
| Iniciativa | Contenido | Tamaño |
|---|---|---|
| ✅ Selection/caret/cursor | EJECUTADO 07-12 (bloque abajo). Los cursores YA estaban cableados por arquetipo; los huecos reales eran ::selection + caret-color (0 en catálogo). |
M |
| ✅ Scrollbar global | EJECUTADO 07-12 (mismo pase). scrollbar-color/scrollbar-width estándar en :root; webkit descartado (ScrollArea oculta la nativa inline). |
S–M |
| Layer 4 | --{c}-{role}-{slot} sin consumidores; la FAQ la marca candidata a colapso "si en 6 meses nadie la usa" → decidir: consumir o colapsar. |
decisión |
| Jaulas abiertas | scrim sin regla consumidora · --depth-{plane}-z 0 consumidores · container queries 0 consumidores — fecha de consume-o-poda para que no fosilicen. |
decisión |
| Assets variables | Motor de fuentes variables listo, assets aún TTF estáticos (rfc-typography §5). | M |
| Límites a registrar (sin código) | Sonido por tema = app/dev (D.7) · icon-set no temable · touch-target constante (§37) · ThemeDefinition sin recipes (§19). Registrarlos como decisiones en el gap-log y cerrar. |
S |
✅ EJECUTADO 07-12 — selection/caret/cursor + scrollbar global
Sección «Foundation globals» en src/uix/eidos/archetypes.css (no
archetype-keyed: tematizan el DOCUMENTO, no una parte), a especificidad mínima
(bare ::selection, :root, :where(...)) para que cualquier recipe gane:
::selection {
background-color: color-mix(in srgb, var(--color-primary-solid) 24%, transparent);
}
:where(input, textarea, [contenteditable]) {
caret-color: var(--color-primary-solid);
}
:root {
scrollbar-color: var(--color-content-muted) transparent;
scrollbar-width: thin;
}
- Selección = lavado accent translúcido 24 %, solo
background-color(el texto conserva su color → legible en claro y oscuro). - Caret = identidad accent; en
:where()para que un recipe de campo lo pueda cambiar (inválido → threat). - Scrollbars = estándar (Firefox + Chromium ≥ 121). Descartado
::-webkit-scrollbar: ScrollArea dibuja overlay y oculta la nativa inline (scrollbar-width: noneen su viewport, gana). Thumb =content-mutedpara casar con el thumb de ScrollArea. - Cursores = NO era hueco: ya cableados (trigger/close/action/item/option →
pointer · thumb → grab/grabbing · disabled → not-allowed · text zones →
input nativo +
cursor: texten date-field/editable · field-trigger → pointer). Confirmado con grep; sin cambio.
Verificado real (demo tags-input, data-theme=base-dark): caret del
<input> = oklch(0.5556 0.1829 305.86) = --color-primary-solid exacto ·
scrollbar-width: thin aplicado · regla ::selection presente y token
resuelve. Precedencia probada: el harness de docs sobre-escribe el default
:root con su --uix-line (app > fundación). npm run check src/ = 9 baseline,
0 nuevos.
F4-D — adjudicación profesional + ejecución (2026-07-12)
Investigado con referencias (MD3 · Primer · Radix · Tailwind v4 · DTCG · next/font · Fontsource) + pasada adversarial. Constante: casi cero deuda real — la mayoría son ejes reservados deliberados o premisas de audit stale.
| Ítem | Veredicto | Estado |
|---|---|---|
| assets-variables | Ejecutar (era trabajo real) | ✅ HECHO 7d4f9500 — defaults (Instrument Sans/Lora/Azeret Mono, identidad intacta) → variable woff2 + API de ejes arbitrarios (wdth/slnt/custom + variationSettings). Gap-3 (auto-métricas anti-CLS) = ergonomía opcional pendiente. |
layer-4 (--{c}-{role}-{slot}) |
KEEP | Tier deliberado del modelo de 7 capas (reference.md:387-425), config-backed. La premisa del audit («sin consumidores», §9:424) es STALE: lo consumen banner (06-01), toast (10 tokens color-mix sin equivalente en L3), avatar-ring, editable, words + el fallback THM-2. MD3 comp.*/SLDS/DTCG mantienen el tier 1:1. El ataque «slim» sobre-generalizó (lo confundió con la purga de font-size de accordion/Field). |
| jaula depth-z | KEEP (reserved) | Proyección de --z-index-* sobre planos; excluido del bundle a propósito (stacking va por --{c}-content-z). Deuda real = 1 comentario falso en render-css.ts:1170 → ✅ corregido este pase. |
| jaula container-queries | KEEP (open cage) · dogfood declinado | [data-container]{container-type} + emisor @container por-recipe, ya en tsc.md como eje reservado (Tailwind v4 envía CQ en core). Dogfood evaluado 07-12 y declinado: el framework dimensiona por data-size explícito (79 componentes), 0 son container-aware; retrofitear CQ en un recipe shipped introduciría un paradigma que el framework no usa → especulativo/contrived (regla «nada especulativo»). El emisor ya está probado por el recipe sintético widget del test. Se dogfoodea cuando un componente/app tenga una necesidad real de layout relativo-al-contenedor. |
| jaula scrim | ✅ PODADO (07-12) | Analizado con workflow (mapa + 4 disposiciones + estrés): las 4 colapsan en podar vs promover-a-paridad-z; C (cablear) se hunde (contradice el precedente z test-lockeado + consenso MD3/Radix/Vaul), D-reubicar invierte el verbo a retener. Decisión: podar — scrim NO gana el open-cage de z (z varía por-plano + es impintable-para-siempre; scrim ni varía ni tiene valor que sembrar, su jaula solo duplicaría --color-overlay → alias). Quitado de DepthPlane/DEPTH_CUES/validación/emisor + applyDepth doc; RFC enmendado (contrato + Phase-4 + atmósfera). El veil queda en --{component}-overlay-* / --color-overlay (= referencia). Cambio observable: cero (nunca se emitió). |
| cascada-vs-firmas (motion) | ✅ FIJADO + TEST (07-12) | Workflow (mapa + 4 disposiciones + estrés) resolvió la dirección: gana la entrada/stagger, la firma se silencia = doctrinalmente correcto (RFC B.2). La lectura «firma gana» del 2º análisis estaba invertida; C (girar) contradice B.2 y su red channels:[] es ilusoria; D (@layer) se hunde como front-line (pregunta semántica con herramienta global + inversión !important en reduced-motion). Cero bug visible (select = sonido+háptico; ítem en fade-out). Ejecutado B: test de regresión (motion.test.ts) que fija el tie (0,3,0) + orden de emisión + ausencia de firma (0,4,0), como monitor de hazard (no «precedencia intencionada»); comentario load-bearing en render-css.ts; nota en motion.md. Endpoint diferido (trackeado): «una firma por evento» / @layer, gatillado por un canal visual de commit real. |
Límites deliberados registrados (gap-log §7 — sin código, todos confirmados NO-bug):
- [LIM-SND] Sonorización por tema NO es un eje de theming — los packs sema son TS (
SOUND_TUNINGS), no config deThemeDefinition; los samples = recursos de producto/tema/branding fuera del canon (book-deviations D.7). Personalización sonora = app/dev. - [LIM-ICON] El set de glifos no es sustituible por config —
IconPrimitiveSetexpone solosize+strokeWidth; los glifos son componentes del framework. Un tema re-dimensiona/re-traza, no cambia el set. - [LIM-TOUCH]
--touch-target: 44pxes constante ergonómica física (WCAG 2.5.5 AAA / Apple HIG), no valor de tema; token:rootenarchetypes.css, deliberadamente fuera del theming (§37). (Matiz: retunable a nivel:root, pero no es un eje de theming.) - [LIM-RECIPES]
ThemeDefinitionexpone solocolor+shadow; el ajuste per-componente (recipes) vive enEidosConfig(boot de app), no en el tema — un tema distribuible no re-tunea componentes sin código de app (§19, portabilidad perceptual).
Cola F4-D restante: ✅ VACÍA — scrim (podado a52f80ce) · motion (fijado+test d8b984bb) · gap-3 anti-CLS métricas (efc3f4c3, capsize) · container-queries dogfood (declinado — paradigma explicit-size). F4-D completo.
Tabla de cobertura (toda ID de §9 → fase)
| Fase | IDs |
|---|---|
| ✅ hecho | SOM-5 (33f346ed+bdd17f04) · badge R-4.3 (ee9786bc) |
| F0 | THM-3 · THM-1 · MOR-4 (+guard enumeración) |
| F1 | EID-1 · EID-2 (+fundación bajo R-*) · SEM-1 · AUX-1 |
| F2 | DEP-1 · DEP-2 · MOR-1 · MOR-2 · MOR-3 · SOM-1 · SOM-2 · SEM-2 · SEM-3 · SEC-1 · THM-7 · THM-6 · EID-4 |
| F3 | SOM-3◐ · SOM-4 · THM-5 · THM-4 |
| F4-A | THM-2 |
| F4-B | SEM-4 |
| F4-C | DOC-1 · DOC-2 · DOC-3 · DOC-4 · DOC-5 · DOC-6 · EID-3 · AUX-2 |
| F4-D | GAP-* (§7) |
Decisiones que solo puede tomar el usuario
- SEM-1: política canónica de assertive — propuesta: intent ∈ {threat, loss} (el libro), alineando el runtime.
- EID-1: migrar fundación a outline + retirar el reset del app (propuesta) — o anotar excepción en §32.
- SOM-1: solo si el censo encuentra handlers async reales (si 0, se quita el await sin preguntar).
- THM-2: alcance del rollout de paleta (15 / subconjunto / re-redactar promesa), tras la medición de peso.
- SEM-4: prioridad y fecha (close ×5 · packs ×6 · flag
emission). - DOC-1: hogares E1/E2 exactos del contenido migrado.
- DOC-2: destino de los binarios del libro (commit vs puntero externo).
- F4-D: qué iniciativas GAP se arrancan y cuáles se registran como límite deliberado.
- Contramedidas: adoptar rojo=parar · fundación bajo R-* · guard de enumeración · trackers con sucesor · deuda con fecha (book-deviations) · docs:check en error · clean-room periódica al cierre de cada programa.
Protocolo de verificación (invariantes)
npx tsx scripts/component-audit.ts→ 134/0/0 debe MANTENERSE (→135 al entrar card-group; nunca hacia abajo).npx vitest run src/uix/contracts.test.ts→ 37/37 (→38 con el guard de enumeración).npm run checkfuera dealpha|words|palabras|chronos→ 57.npx tsx scripts/morfo-vocabulary-check.ts→ exit 0 (WARN cropper conocido).- Suite eidos → 306/306 tras F0 y se mantiene.
- Cambios observables → verificación en navegador (gesto/tecla/inspección
real). Gotchas de entorno en la memoria del proyecto (rAF congelado en tab
desenfocado, dev server flaky, HMR de
.svelte.ts).
Exclusiones DURAS (sin cambios)
words/**·palabras/**·chronos(ACTIVE_DEV_TRACK) ·web/routes/alpha/**.- Demos: NO son fix — frame nuevo de sistema pendiente de diseño
(decisión 2026-07-10,
e53bf0e8); las D-* son verdict-neutral. - Nada a git sin petición; commits con paths explícitos (excluir
.claude/,rt.css, README stub, binarios del libro — salvo que la DECISIÓN #7 los incorpore).
Cola lateral (no bloqueante, registrada)
- accordion: anillo box-shadow +
outline: none(§32 stray) — el censo S5 no lo vio (accordion.css:165outline: none+ anillo box-shadow con tokens propios). Consecuencia real: en forced-colors/HCM suoutline: none(0,2,0) gana al suelo defensivo generado (0,1,0) → cero anillo en HCM. Migrar a outline en el lote F2 (mecánico, con verificación visual). - Cascada de entrada vs firmas de evento (sistémico, shipped): las
reglas generadas
[data-stagger][data-state='open'] > [data-animation-style='…'](0,3,0) siguen reclamandoanimationtras terminar la entrada (data-state persiste) y sombrean las firmas de evento (0,2,0) en cualquier ítem escalonado — commit-settle/announce-* incluidos. Card-group re-afirma la suya a (0,4,0) como parche local; el arreglo de sistema (¿liberaranimationdurantedata-event-phase='active'? riesgo: re-disparo de la entrada al soltar) es diseño de motion — adjudicar en F2/F4-B. spin-field: única capa compartida sin morfo/contrato (guard MOR-4 la exime con nota) — darle contrato en F1 junto a la fundación.card-group.disabledno alcanza el Collapsible compuesto (promesa de la tabla de props a medias) — dossier## Gapscon «implementar»; toca wrapper eidos de collapsible (reenvío) o corregir el doc.- class-hooks preexistentes: color-picker(2) · time-picker(8) ·
time-range-picker(12) — extensión del codemod S1 cuando cuadre; nota:
.range-time/.picker-time-row(CSS secundarios, ahora escaneados) pertenecen al mismo censo. task_d66356de(trpdata-sizea endpoints) · Task #31 (pickers →PickerProvider<TValue>) ·FieldLangsSnippetPropssin uso.- Hallazgos trascendentes del programa anterior (en
_cierre.md): holiday/event de range-calendar · naming gemelos tree · invalid de rango.