You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/docs/process/continue-audit-fixes-2026-0...

16 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; 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 (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-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 <DateField>/<TimeField> 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 <Control> 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: button · password-field · time-range-field · textarea · knob · soma field-langs
    2. pickers/nav: range-calendar · natural-time-picker · menubar · navigation-menu · command · listbox
    3. data: table · tree-view · tree-grid · grid-list · feed · virtual-list · virtual-grid
    4. typography/display: s-text · s-text-virtual-list · badge · card · image · skeleton · spinner
    5. util: link-preview · announce · clipboard Criterio de éxito por lote: npx tsx scripts/component-audit.ts → los F-1.x del lote pasan (el informe cae en tmp/component-audit.md).
  • S9 iniciativas ⬜ — decisión del usuario (2026-07-10): sesiones dedicadas POST-S4 (gradient-* · ntp integral · metrics+suite son paquetes de feature-work; gradient tenía trabajo sin commitear según memoria 2026-06-27 — verificar su estado real al arrancarlo). El audit-fix se cierra con S4.

Lo que queda — arrancar aquí: S4 lote 1 (forms)

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<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 de alpha|words|palabras|chronos debe seguir en 57 (era 59; P7 arregló los 2 mandados: editable + grid-list — tipos-solo, cast sobre renderProps). (Comando: npm run check 2>&1 | grep " ERROR " | grep -vaiE "alpha|words|palabras|chronos" | wc -l.) Quedan 9 src/ fuera de mandato (file-upload ×4 aria-label, navigation-menu sideOffset, arts/connection ×4) + ~48 en web/ (demos) — candidatos, no mandato.
  • 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 <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; 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).

Powered by TurnKey Linux.