# Continue — Auditoría de componentes + fase de fixes (P0–P7) > **⛔ PROGRAMA COMPLETO (2026-07-11) — este handoff queda CERRADO como > histórico.** Matriz 134/0/0 · contracts 37/37. Cierre formal: > [`docs/audit/components/_cierre.md`](../audit/components/_cierre.md). > **La continuación viva es** > [`continue-cleanroom-fixes-2026-07.md`](./continue-cleanroom-fixes-2026-07.md) > (los P1 de la auditoría clean-room). No arrancar sesiones desde aquí. **Handoff. Fecha: 2026-07-08 · actualizado 2026-07-10 (S4 lote 1 ✅).** Rama `alpha-0.1-sec-dom`. > **S4 COMPLETO (2026-07-10) → el audit-fix P0–P7 está CERRADO.** > E-2.3 = 0 en el catálogo (29 dossiers eidos + README soma field-langs; > máquina 81→**102** PASS; baseline 57). Los NEEDS-WORK restantes son flags > no-README documentados en los `## Gaps` de cada dossier (S9, demos D-\*, > censos R-1.x). **Pendiente de decisión del usuario**: (a) los one-liners > recopilados abajo; (b) ¿"lote 6" opcional? — 13 READMEs ANTIGUOS con > F-1.x en warn (color-swatch, float-panel, format-date/number, > gradient-\*·metrics [son S9], onion-menu, radio-cards, relative-time, > scroll-frames, timeline, trans) que existen pero no tienen forma dossier; > NO estaban en el plan S4 (solo cubría los ausentes). Después: S9 en > sesiones dedicadas + reconciliación touch-rows↔§37. > **P5 y P6 están CERRADOS**: P5 = S1·S5·depth·touch·S6·S8 ✅ (S6 `53b6f629..d1670caa`; > S8 `83e13be6`). P6 = C6 `3b02fd11` · C7 `444e05eb` · B.11 `0f0feb4f` ✅. > Pende tu decisión touch-rows↔§37. Follow-ups no bloqueantes abajo. Documento de continuación para retomar la fase de fixes de la re-auditoría de componentes. La **fuente de verdad de los veredictos** es [`docs/audit/components/_veredictos.md`](../audit/components/_veredictos.md) (bloques N1–N10 · S1–S11 · C1–C8 + fixes obvios). Los censos de apoyo: [`_naming.md`](../audit/components/_naming.md) y [`_system.md`](../audit/components/_system.md). Las 91 fichas pilotadas viven en `docs/audit/components/{kebab}.md`. ## Estado por paquete | Paquete | Estado | | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **P0** doctrina (sema participation, CANON §3 intent, component-guide §5 + API naming, §32 outline) | ✅ | | **P1** guardas (vocab-check S10/S11d, component-audit A-3.1/A-1.4/S9, eidos lint classHooks) | ✅ | | **P2** contratos morfo (role=application×4, aria-selected fuera del Day, drp translationRef, field data-state, pin-input commit-set, media-player renames) | ✅ | | **P3** packs sema (13 packs nuevos + 12 morfos `family-default`→`'pack'`) | ✅ | | **P4** naming/API N1–N10 | ✅ **COMPLETO** | | **P5** sistema/eidos | ✅ **CERRADO** — S1 ✅ · S5 ✅ · depth ✅ · touch-rows ✅ · **S6 ✅** (composición + poda + de-alias) · **S8 ✅** (`--calendar-range-*` API pública `83e13be6`) · (touch↔§37 pende decisión) | | **P6** C7 textarea-measure + D13 · C6 weekInfo · B.11 splitter | ✅ **CERRADO** — C6 `3b02fd11` · C7 `444e05eb` · B.11 `0f0feb4f` | | **P7** S9 iniciativas · S4 READMEs · S7 papeleo · errores de tipo baseline | ✅ **S4 COMPLETO 2026-07-10** (baseline ✅ · S7 ✅ · S4 29+1 dossiers ✅, E-2.3=0, máquina 102 PASS) — S9 queda como sesiones dedicadas POST-audit-fix | ## ✅ Reconciliación touch-rows ↔ §37 — CERRADA (decisión usuario 2026-07-10) **Opción (a) con nota**: el `::before` hit-slop queda CONFIRMADO como mecanismo para markers desnudos (linaje Material `mdc-touch-target` / Android touch-delegates / iOS hit-test insets). El argumento WCAG-solape del §37 original se resuelve con (1) requisito de spacing en pilas táctiles densas (pitch ≥ `--touch-target`; AA garantizado en el peor caso por la excepción Spacing) y (2) la **fila-label de Field sigue viva como mejora ADITIVA** — la fila será el target AAA real cuando exista (2.5.5 Equivalent) y el pseudo queda de red. Redactado en `changelog.md §37` (reconciliación) + `reference.md §37` (standing "confirmed 2026-07-10"). ## P4 — hecho este bloque (N1–N10) - **N1 `selectionMode`**: select, combobox, toggle-group, **calendar** (unión discriminada; `data-type` como atributo se conserva — contrato eidos; solo prop - opts key → `selectionMode`). date-picker interno migrado; range-calendar N/A. file-upload conserva `multiple` (espejo nativo). - **N2 `onValueCommit`** (norma única callback terminal): pin-input `onComplete`→, search/password `onSubmit`→, date/time/color-field **añaden** el callback disparado en `commit-set` (solo al completar, no al limpiar), **textarea** `onSubmit`→ (hueco de censo cazado). 5 sitios internos de picker/rango pasan `undefined`. - **N3** carousel `value`→`index`/`onIndexChange`. **N5** `deselectable` (calendar migra `preventDeselect` con inversión de polaridad). **N6** tooltip `openDelay`/`closeDelay`, `groupSkipDelay`. **N7** combobox `allowCustomValue`. **N8** poda `defaultValue` (editable/select/grid-list; knob conserva). **N4/N10** = veredictos que ratifican el statu quo (sin código). - **N9 razón tipada + `onInvalid`**: tags-input migra `validate`-boolean→mensaje + `onValueInvalid`→`onInvalid(reason, value, msg?)` con `'duplicate'|'custom'`. **Verificado que date/time/ranges YA cumplían** (razones tipadas via `DateOnInvalid`/`TimeOnInvalid` = `'min'|'max'|'custom'`). **css-field nivelado** (decisión del usuario): `onInvalid(reason, value, msg?)` con `'parse'|'custom'`, reenviando el `message` que ya computaba. (file-upload N/A — `FileRejectionReason[]` ya tipado; words excluido.) ## P5 — hecho este bloque - **S1 codemod 22 class-hooks** → `[data-{c}-{part}]` (plantilla + CSS): cropper 8 (+ estado `.is-zoomed`→`data-zoomed`), button 6 (+ `:is()` simplificado), image-picker 6, toggle 1, image-adjustments 1. eidos-lint: class-hooks 0 / invalid 0 en los 5. - **S5 outline**: eliminado el remanente `box-shadow` (transición muerta) de field.css; el resto del catálogo ya canonizado (comentarios post-migración). - **depth pass**: `data-depth="overlay"` en tooltip-content, toast-item, float-panel-content. **No-destructivo** (probado: la regla propia de cada overlay gana la cascada por orden de fuente; el plano solo aporta identidad frost/Cristal - forced-colors). banner **fuera** (decisión del usuario — es strip inline). - **touch-rows**: token `--touch-target: 44px` (`:root` en archetypes.css). Botones tokenizados. checkbox/switch: `::before` transparente `max(100%, var(--touch-target))` gated a coarse → **crece el ÁREA, no el visual** (modelo React Aria / Material `mdc-touch-target`; suelo único 44 AAA). radio-group row tokenizado. ### Decisiones de método relevantes (para no repetir errores) - **depth**: la disyuntiva "migrar-al-plano vs quitar-shadow" partía de una premisa falsa (el "doble shadow" — `box-shadow` NO es aditivo). El fix correcto era el simple: solo añadir `data-depth`; la regla propia gana. Verificado en vivo. - **touch-rows**: NO anclar en "44px" sin ver que (a) **área ≠ visual** son ejes distintos, y (b) el visual depende de size/fuente. Se comparó con 5 frameworks (React Aria, Radix, Material/MUI, Ark, Base/Bootstrap/shadcn): solo React Aria y Material desacoplan área/visual; **nadie** hace split AA/AAA por talla (el análogo MUI se considera bug). → suelo único 44 AAA sobre el ÁREA. ## P5 — S6 Field composition (HECHO 2026-07-08→09, pusheado) Doctrina del usuario: **todos los fields deben SER fields** (componen `field.css`), **y ningún field concreto declara tokens que aliasen los de Field**. Commits `5eb92132..d1670caa` (12). Detalle en memoria: `compose-base-field-not-replicate`, `no-token-aliases`, `recipe-orphan-test-prune`. - **16 fields componen `field.css`** — cuatro cardinalidades descubiertas y documentadas: 1. **multi-field** (date/time-range-field): montan un ``/`` real por endpoint (soma), tamaño/variant/color via visual context. 2. **single-field picker** (date/time-picker): el root ES el field (`data-field` + `data-{c}-field`), chrome de `field.css`. 3. **single-control text** (search/mask/number/css): colapsan `fieldRole('provider')` - `fieldRole('control')` en la única caja; `field.css [data-field-control]` fija `flex-direction: row` (evita que la columna del provider apile los afijos). 4. **field con contenido debajo** (password: strength meter): estructura Field canónica completa con una parte `` real + el meter como hermano del stack. - **Poda de recipe-tokens** (`a820e5f3` + `d1670caa`, **199 tokens**): mover el chrome a `field.css` dejó los `--{c}-field-height/padding/border/bg/font/...` **huérfanos** (emitidos sin consumidor). El guard **`recipe-css-contract.test.ts` "orphaned"** los caza (167 en la 1ª pasada + 32 tras de-aliasar los pickers). mask-field quedó sin tokens propios → entrada de recipe eliminada. - **De-alias de pickers** (`d1670caa`): date-range/time/time-range-picker aliasaban `--date-field-*`/`--time-field-*` para su parte "field-shaped" → retargeteados a los **tokens públicos de Field** (`--field-control-*`/`--field-*`/`--field-segment-height`) o al primitivo donde Field no tiene equivalente (wrap column-gap = segment gap). Value-preserving (mismo primitivo tras un hop), verificado con revisión adversarial (workflow, 4 agentes) + navegador en los 4 pickers. ### Lecciones de método (S6) — no repetir - **El baseline `svelte-check` NO corre vitest.** Tras cualquier refactor de recipe/CSS, corre además `npx vitest run src/uix/eidos/recipe-css-contract.test.ts` (25 tests: huérfanos, phantom, "todo var resuelve", scope-contract). La regresión de 167 huérfanos fue invisible al baseline durante toda la composición S6. - **`--field-segment-height` solo existe bajo `[data-field][data-size]`.** Un elemento compuesto SIN `[data-size]` cae al default `[data-field]` = **md, fijado**. Antes de cambiar un `calc(--control-height - space-2)` por `--field-segment-height`, verifica en el DOM (xs/sm/lg) que el elemento consumidor lleva `[data-size]`. La revisión adversarial cazó exactamente esto en time-range-picker. ## P5 — CERRADO ✅ (S8 hecho 2026-07-09, `83e13be6`) - **S8 calendar-surface**: 8 tokens públicos `--calendar-range-{start,end}-*` en el recipe `calendar` (API de la capa); range-calendar los consume por el nombre público; drp deja de tunear los `--_calendar-range-*` privados. Value-preserving (25/25, baseline 59, eidos-lint 0, navegador idéntico en 4 colores + drp popover). Asimetría deliberada documentada: start desde `:root` (tema-overrideable), end atado al accent en el host. Detalle en memoria [[recipe-orphan-test-prune]] / [[compose-base-field-not-replicate]] (mismo patrón anti-alias que el de-alias de pickers). ## P6 — CERRADO ✅ (2026-07-09→10) - **C6** (`3b02fd11`): `getWeekInfo(locale)` público en `$libs/days` (convención Intl 1=lun..7=dom; `firstDay` espeja el `getWeekStart` interno; weekend de Intl con fallback sáb/dom documentado, caché por locale). Calendar + range-calendar derivan `weekend` del locale (antes: hardcoded sáb/dom con 'en-US' — mal para ar-SA). Verificado en vivo (ar-SA → vie/sáb). **Hallazgo extra arreglado**: `getSimpleWeekNumber` mezclaba marcos (weekStartsOn absoluto + locale-relativo) → semanas 'simple' desplazadas −1 en es-ES; ancla en marco en-US fijo + test de regresión. - **C7** (`444e05eb`): textarea `measureAutosize` difiere la lectura vía `dom.measure` del contexto (antes: reflow forzado POR TECLA). Censo D13: violaciones arregladas en scroll-area (getters del thumb leían clientHeight en `$derived` → reflow por tick; ahora track medido por RO en $state), virtual-list (×2 viewports), virtual-grid, carousel (seed diferido + fallback vivo en render eliminado). Conformes sin cambio: float-panel, cropper, splitter (lecturas event-time antes de write — D13 es regla de timing). Suites actualizadas al contrato diferido. - **B.11** (`0f0feb4f`): par bindable del splitter — `sizes` (bindable, % por orden de registro: seed + sync + escritura externa clampada) + `onResize`→`onSizesChange` (norma X+onXChange; dos nombres = alias). `onResizeEnd` se queda (momento commit). Verificado en vivo en ambas direcciones. - **Gotcha de entorno registrado** (memoria `hidden-pane-suspends-raf`): el Browser pane oculto (`visibilityState: hidden`) suspende rAF + entrega de RO → sondas de medición "muertas", screenshots en timeout. Chequear la vitalidad de rAF ANTES de diagnosticar; verificar vía suite de navegador de vitest en su lugar. La sonda reflowDetector quedó pendiente por esto (opcional, no bloqueante). ## P7 — estado (2026-07-10) - **Baseline** ✅ (`c0ae3de1`): editable + grid-list arreglados (tipos-solo, cast sobre renderProps) → protocolo ahora **57**. Quedan 9 src/ + ~48 web/ fuera de mandato (inventario arriba en el protocolo). - **S7 papeleo** ✅ (`fe844c62`): doctrina §5 + clasificador + F-1.5 ya existían (P0/P1); añadidos los 2 artefactos que faltaban — `## Passive justification` en color-swatch (F-1.5 verificado verde en la máquina) + README de capa de spin-field. radio-cards = interactive para la máquina (F-1.5 N/A; su cita canónica es el header del morfo, verificada). - **S4 READMEs** ◐ — **decisión del usuario (2026-07-10): dossier completo, por lotes de ~6/sesión, máquina en verde por lote**. Forma real (hallazgo): F-1.x lee el README de eidos como dossier de embarque (`## Baseline` / `## Comparativa` ≥3 refs / `## Decisiones` / `## Gaps` con disposiciones) + puerta del consumidor (plantilla date-field); el grandfather.json existe para que estos dossiers se escriban en P7. Gran parte = trasplante ficha→README (las 91 fichas ya tienen Baseline/Comparativa). **Lotes propuestos** (28 + field-langs): 1. ✅ **forms (2026-07-10, `7b82aa79`)**: button · password-field · time-range-field · textarea · knob (READMEs eidos) + **soma field-langs** (contrato headless). Máquina: 81→85 PASS; los 5 E-2.3 limpios; F-1.x verde en los 6; baseline 57. Método fijado: exceptions `R-1.x exception:` SOLO con evidencia leída (pf R-1.3/R-1.4 → field.css; trf R-1.5/R-1.3/R-1.4 → endpoints TimeField reales, espejo drf) + gaps de ficha aún vivos trasladados a `## Gaps` con disposición. button sigue NEEDS-WORK solo por `D-1.5` (regla de DEMO, fuera del alcance). One-liners detectados y NO ejecutados (decisión pendiente): apg de trf (`spinbutton`) y apg none-con-razón de field-langs (su `A-1.4 exception` en README ya no se honra tras C5); README soma de button drifted (`pending`/`pressed` vs types reales) + docblocks F-2/F-3 de la ficha. 2. ✅ **pickers/nav (2026-07-10)**: range-calendar · natural-time-picker · menubar · navigation-menu · command · listbox. Máquina 85→90 PASS; F-1.x verde en los 6; baseline 57. Verificado pre-escritura el estado post-fixes (los 6 con hallazgos de ficha YA resueltos por P2/P3/S3b/S8: packs de range-calendar y listbox, aria-selected, role application, apg de ntp). **Hallazgo nuevo con evidencia** (README de range-calendar, Gaps): las marcas holiday/event siguen MUERTAS en range-calendar — sus 2 tokens (`--calendar-day-holiday-shadow`, `--calendar-event-shadow`) se emiten bajo `[data-calendar]` en generated/base.css y no resuelven bajo `[data-range-calendar]` (el resto del préstamo va en `:root` y sí). ntp sigue NEEDS-WORK por su paquete S9 "ntp integral" (A-3.1 del Picker genérico sin eventos · R-2.1 los 9 cielos sin anotar · README soma + tests) — todo trasladado a su `## Gaps` con disposición; one-liner `expression: 'delegated'` en ntp pendiente de decisión (diría la verdad y silencia A-3.1). R-1.3 readonly de range-calendar/listbox = gap real (no pinta en ningún sitio) → censo, SIN exception. 3. ✅ **data (2026-07-10)**: table · tree-view · tree-grid · grid-list · feed · virtual-list · virtual-grid. Máquina 90→95 PASS; F-1.x verde en los 7; baseline 57. Los 7 tenían soma README ✓ y suite ✓; varios hallazgos de ficha ya resueltos post-audit y documentados en los dossiers (table pack S3b + sort reclasificado a commit.set; apg de virtual-list en forma C5 "none — rationale"; D13 de virtual-\* vía dom.measure, C7; error de tipos de grid-list en baseline `c0ae3de1`). virtual-list/virtual-grid quedan NEEDS-WORK SOLO por `D-1.5` (regla de DEMO, fuera del alcance — mismo caso que button). **Hallazgo nuevo** (Gaps de tree-grid): asimetría de naming entre gemelos — tree-grid usa `value`+`expanded`, tree-view usa `selectedValue`+`expandedValue` (N4 solo ratificó el segundo; ambos multi-eje) → decisión de espejo pendiente. R-1.3 readonly de tree-grid/grid-list = gap real → censo, SIN exception. 4. ✅ **typography/display (2026-07-10)**: s-text · s-text-virtual-list · badge · card · image · skeleton · spinner. Máquina 95→99 PASS; F-1.x (+ F-1.5 en los 4 pasivos: s-text, image, skeleton, spinner) verde en los 7; baseline 57. Quedan NEEDS-WORK con F-1.x verde y el resto en Gaps: badge (R-4.3 hover del Remove con color-mix hand-rolled → censo state-layer; R-4.3 NO honra exceptions de README), s-text-virtual-list (R-1.5 = ARTEFACTO del `archetype: 'item'` en Row — display de 0 eventos `scope: ['eidos']`; one-liner de morfo pendiente que lo vuelca a passive, el README ya trae la Passive justification preparada) y spinner (D-7.4, demo). apg de badge/card/virtual-list ya estaban en forma C5 "none — rationale" ✓. 5. ✅ **util (2026-07-10)**: link-preview · announce · clipboard + **drag-drop** (el nº29 del censo E-2.3, omitido del plan de 5 lotes e incorporado al cierre — el "28" del plan era 29 en el censo real). Máquina 99→102 PASS. Exceptions con evidencia: announce R-1.5 (cero partes interactivas — eventos programáticos de live region) y clipboard R-1.5 (el Trigger compone `