14 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 aquí → P7 (S9 iniciativas · S4 28 READMEs · S7 papeleo · errores baseline). P5 y P6 están CERRADOS: P5 = S1·S5·depth·touch·S6·S8 ✅ (S6
53b6f629..d1670caa; S883e13be6). P6 = C63b02fd11· C7444e05eb· B.110f0feb4f✅. 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
(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 | ✅ 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 28 READMEs · S7 papeleo · errores de tipo baseline | ⬜ pendiente — empezar aquí |
⚠️ 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 — CERRADO ✅ (S8 hecho 2026-07-09, 83e13be6)
- S8 calendar-surface: 8 tokens públicos
--calendar-range-{start,end}-*en el recipecalendar(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;firstDayespeja elgetWeekStartinterno; weekend de Intl con fallback sáb/dom documentado, caché por locale). Calendar + range-calendar derivanweekenddel locale (antes: hardcoded sáb/dom con 'en-US' — mal para ar-SA). Verificado en vivo (ar-SA → vie/sáb). Hallazgo extra arreglado:getSimpleWeekNumbermezclaba marcos (weekStartsOn absoluto + locale-relativo) → semanas 'simple' desplazadas −1 en es-ES; ancla en marco en-US fijo + test de regresión. - C7 (
444e05eb): textareameasureAutosizedifiere la lectura víadom.measuredel 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).onResizeEndse 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).
Lo que queda — arrancar aquí: P7
- P7 — S9 iniciativas · S4 28 READMEs · S7 papeleo · errores de tipo baseline.
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).