# Continue — Auditoría de componentes + fase de fixes (P0–P7) **Handoff. Fecha: 2026-07-08 · actualizado 2026-07-09.** Rama `alpha-0.1-sec-dom`. > **Arranca mañana aquí →** [§ P5 — lo que queda](#p5--lo-que-queda-arrancar-aquí-mañana). > **S6 Field-family está COMPLETO y pusheado** (`53b6f629..d1670caa`). Lo siguiente > es **S8 calendar-surface**, luego **P6** y **P7**. Dos follow-ups no bloqueantes > registrados 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 | 🟡 **EN CURSO** — S1 ✅ · S5 ✅ · depth ✅ · touch-rows ✅ · **S6 ✅** (composición + poda + de-alias, pusheado) · **S8 pendiente** | | **P6** C7 textarea-measure + D13 · C6 weekInfo · B.11 splitter | ⬜ pendiente | | **P7** S9 iniciativas · S4 28 READMEs · S7 papeleo · errores de tipo baseline | ⬜ pendiente | ## ⚠️ Reconciliar PRIMERO — conflicto touch-rows ↔ §37 El **touch-rows** de hoy cerró checkbox/switch con un `::before` hit-slop (área ≠ visual). Pero el **changelog §37 (2026-06-28, ya commiteado)** había **rechazado explícitamente el pseudo** para markers ("un pseudo de 44px que desborda al vecino NO vale: WCAG excluye el área solapada") y ruteado checkbox/switch a la **fila-label (tarea de Field)** — que es lo que el veredicto de auditoría pedía ("patrón radio-row"). La decisión de sesión (con comparativa de 5 frameworks) se tomó sin tener delante ese rechazo previo. **Decisión pendiente del usuario**: (a) la decisión de hoy supersede el §37 (dejar el `::before`, ya documentado como revisión), o (b) revertir a la vía fila-label/Field. El código y los docs (§37 reference + changelog) están puestos como "revisión que revierte, pendiente de confirmar". Registrado en `changelog.md §37`. ## 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 — lo que queda (arrancar aquí mañana) - **S8 calendar-surface** CONFIRMADO + tokens de rango como **API pública** de la capa (drp deja de tunear `--_calendar-range-*` ajenos). **← EMPEZAR AQUÍ.** ### Follow-ups no bloqueantes (registrados) - **`task_d66356de`** — **TimeRangePicker no propaga `data-size` a sus endpoints** (caja fijada a md en todo tamaño; verificado en DOM 2026-07-08). DateRangePicker SÍ lo hace (via el visual context de date-range-field). Espejar ese patrón; luego el embedded-trigger de time-range-picker podrá volver a `--field-segment-height` (hoy usa el privado `--_time-field-height` a propósito, por este hueco). - **Task #31 (memoria)** — migrar los date/time pickers al `PickerProvider` genérico (deferValue). **POST audit-fix**, no ahora. ## Protocolo de verificación (invariante en toda la fase) - **Baseline de tipos**: `npm run check` → errores **fuera de** `alpha|words|palabras|chronos` debe seguir en **59**. (Comando: `npm run check 2>&1 | grep " ERROR " | grep -vaiE "alpha|words|palabras|chronos" | wc -l`.) - **Vocab guard**: `npx tsx scripts/morfo-vocabulary-check.ts` → exit 0 (el único WARN preexistente es `cropper.commit-crop`, descubribilidad, no mandato). - **eidos-lint**: `npx tsx scripts/eidos-lint.ts ` → class-hooks 0 / invalid 0. - **Suites por componente** en verde antes de pasar al siguiente (no-cascada: UN componente, verificar, luego el siguiente). - **UI**: check en navegador con los `preview_*` (no screenshot — flaky; usar `preview_eval`/`preview_inspect`). Limitación conocida: no se puede forzar `pointer: coarse` en el preview headless (el resize a móvil no cambia el tipo de puntero) — verificar coarse por CSSOM + gating en fine. ## Exclusiones DURAS (no tocar / no commitear) - `words/**`, `palabras/**`, `chronos` — el rename N1 los rompió; **decisión del usuario: dejarlos rotos** (excluidos del baseline y del commit). - `web/routes/alpha/**` — sitio de docs TERMINADO. - Nada a git sin petición explícita; al commitear, **paths explícitos** excluyendo lo de arriba + `.claude/` (settings locales).