11 KiB
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. 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
(bloques N1–N10 · S1–S11 · C1–C8 + fixes obvios). Los censos de apoyo:
_naming.md y
_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-typecomo atributo se conserva — contrato eidos; solo prop- opts key →
selectionMode). date-picker interno migrado; range-calendar N/A. file-upload conservamultiple(espejo nativo).
- opts key →
- N2
onValueCommit(norma única callback terminal): pin-inputonComplete→, search/passwordonSubmit→, date/time/color-field añaden el callback disparado encommit-set(solo al completar, no al limpiar), textareaonSubmit→ (hueco de censo cazado). 5 sitios internos de picker/rango pasanundefined. - N3 carousel
value→index/onIndexChange. N5deselectable(calendar migrapreventDeselectcon inversión de polaridad). N6 tooltipopenDelay/closeDelay,groupSkipDelay. N7 comboboxallowCustomValue. N8 podadefaultValue(editable/select/grid-list; knob conserva). N4/N10 = veredictos que ratifican el statu quo (sin código). - N9 razón tipada +
onInvalid: tags-input migravalidate-boolean→mensaje +onValueInvalid→onInvalid(reason, value, msg?)con'duplicate'|'custom'. Verificado que date/time/ranges YA cumplían (razones tipadas viaDateOnInvalid/TimeOnInvalid='min'|'max'|'custom'). css-field nivelado (decisión del usuario):onInvalid(reason, value, msg?)con'parse'|'custom', reenviando elmessageque 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(:rooten archetypes.css). Botones tokenizados. checkbox/switch:::beforetransparentemax(100%, var(--touch-target))gated a coarse → crece el ÁREA, no el visual (modelo React Aria / Materialmdc-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-shadowNO es aditivo). El fix correcto era el simple: solo añadirdata-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:- multi-field (date/time-range-field): montan un
<DateField>/<TimeField>real por endpoint (soma), tamaño/variant/color via visual context. - single-field picker (date/time-picker): el root ES el field
(
data-field+data-{c}-field), chrome defield.css. - single-control text (search/mask/number/css): colapsan
fieldRole('provider')fieldRole('control')en la única caja;field.css [data-field-control]fijaflex-direction: row(evita que la columna del provider apile los afijos).
- field con contenido debajo (password: strength meter): estructura Field
canónica completa con una parte
<Control>real + el meter como hermano del stack.
- multi-field (date/time-range-field): montan un
- Poda de recipe-tokens (
a820e5f3+d1670caa, 199 tokens): mover el chrome afield.cssdejó los--{c}-field-height/padding/border/bg/font/...huérfanos (emitidos sin consumidor). El guardrecipe-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-checkNO corre vitest. Tras cualquier refactor de recipe/CSS, corre ademásnpx 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-heightsolo existe bajo[data-field][data-size]. Un elemento compuesto SIN[data-size]cae al default[data-field]= md, fijado. Antes de cambiar uncalc(--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 propagadata-sizea 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-heighta propósito, por este hueco).- Task #31 (memoria) — migrar los date/time pickers al
PickerProvider<TValue>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 dealpha|words|palabras|chronosdebe 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 escropper.commit-crop, descubribilidad, no mandato). - eidos-lint:
npx tsx scripts/eidos-lint.ts <componente>→ 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; usarpreview_eval/preview_inspect). Limitación conocida: no se puede forzarpointer: coarseen 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).