|
|
# AUDIT — contradicciones docs↔código: registro de defectos (ledger)
|
|
|
|
|
|
> **Fuente única del estado de los defectos que salieron del barrido
|
|
|
> docs↔morfo del 2026-08-11.** Hasta hoy vivían sólo en la memoria de la
|
|
|
> sesión que los encontró, que es una superficie de recuerdo del agente y no
|
|
|
> del proyecto: un handoff que no se carga los pierde. Este fichero es la casa.
|
|
|
|
|
|
## Por qué existe este fichero
|
|
|
|
|
|
El 2026-08-11 un barrido de alta precisión sacó **101 contradicciones** entre la
|
|
|
documentación y el morfo. La instrucción inicial fue «borra toda la basura», y
|
|
|
fue corregida en el acto: _«puede ser que la documentación esté desfasada o que
|
|
|
el código esté obsoleto, hay que evaluar primero cuál es el caso»_. Sin esa
|
|
|
corrección los 101 hallazgos se habrían aplanado contra el morfo y **habría
|
|
|
desaparecido la única huella de nueve averías reales**.
|
|
|
|
|
|
De ahí el veredicto por sitio, con evidencia (`git log -S`, docs de decisión, el
|
|
|
componente vivo):
|
|
|
|
|
|
- **DOC-DESFASADA** — la documentación miente sobre código correcto → se arregla.
|
|
|
- **CÓDIGO-OBSOLETO** — el código miente sobre documentación correcta → **se
|
|
|
reporta, no se toca**. Son las filas de abajo.
|
|
|
- **FALSO-POSITIVO** — el script se equivocó.
|
|
|
|
|
|
⚠️ Aun con la instrucción explícita, **dos agentes borraron filas de «Gaps» que
|
|
|
documentaban un defecto abierto**. Repuestas a mano. La lección es la regla 3.
|
|
|
|
|
|
## Las reglas de este registro
|
|
|
|
|
|
1. **Se escribe por `id`.** `D1`…`D14` no se renumeran nunca. Un defecto que
|
|
|
resulta ser dos se parte en `D3a`/`D3b`, no desplaza a los siguientes.
|
|
|
2. **Un `ARREGLADO` lleva su commit.** Sin sha no es un arreglo, es una opinión.
|
|
|
3. **Una fila de gap o de known issue NUNCA es basura**, aunque cite nombres
|
|
|
muertos: es la huella de una avería. Borrarla es borrar el defecto, no
|
|
|
arreglarlo.
|
|
|
4. **«No lo referencia nadie» no es un veredicto.** Es una observación. Antes de
|
|
|
proponer retirar algo hay que abrir TODOS los hits de `docs/` que lo nombren
|
|
|
— `D10` nació precisamente de saltarse esto.
|
|
|
|
|
|
Estados: `CONFIRMADO` (medido, abierto) · `ARREGLADO` (con sha) ·
|
|
|
`PENDIENTE` (sin re-medir) · `DIFERIDO` (confirmado, disposición firmada).
|
|
|
|
|
|
## La tabla
|
|
|
|
|
|
| id | Componente | Estado | Prioridad | Una línea |
|
|
|
| --- | --------------------------- | -------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------- |
|
|
|
| D1 | AlertDialog | `ARREGLADO` `f4e414e2e` | P0 | Action y Cancel cerraban sin causa: mudos |
|
|
|
| D2 | Splitter (eidos) | `ARREGLADO` `6f42eebfe` | P0 | regla CSS enganchada a un evento renombrado en mayo |
|
|
|
| D3 | 5 pickers | `ARREGLADO` `f4e414e2e` | P0 | `commit-reset` declarado y jamás disparado |
|
|
|
| D4 | ColorPicker | `ARREGLADO` `f4e414e2e` | P0 | swatch y eyedropper commitean en silencio |
|
|
|
| D5 | los 5 pickers | `ARREGLADO` | P1 | en `modal`, un cierre que no declara causa es un descarte, y un descarte no commitea |
|
|
|
| D6 | date/time/color-field | `ARREGLADO` | P1 | `Home`/`End` en la familia de segmentos, con una sola acción declarada |
|
|
|
| D7 | 5 componentes de campo | `ARREGLADO` | P1 | el Label renderizaba `<div>` y el morfo declara `label` |
|
|
|
| D8 | los 7 `arrow` | `ARREGLADO` | P2 | `defaultElement` falso en el `arrow` — sistémico, confirmado |
|
|
|
| D9 | chronos | `ARREGLADO` | P2 | el acarreo habla: `handle-drag` y `handle-resize`, anclados y a la cadencia del gesto |
|
|
|
| D10 | sema ↔ `$sound` | `ARREGLADO` (2026-08-12, desde el eje) | P1 | los 3 resolvers de gesto RETIRADOS por la sesión del eje de sonido, con los 8 sitios rancios adjudicados |
|
|
|
| D11 | catálogo | `ARREGLADO` | P1 | `defaultElement` mentía en 28 partes; las 28 dicen ya la verdad y el guard no tiene excepciones |
|
|
|
| D12 | context-menu · link-preview | `ARREGLADO` | P2 | sus flechas componen ya la primitiva compartida: una flecha, una forma |
|
|
|
| D13 | 4 componentes | `CONFIRMADO` | P1 | 7 eventos declarados que nadie emite — enumerados y guardados |
|
|
|
| D14 | chronos | `CONFIRMADO` | P1 | `restoreChipFocus` pierde el foco: el movimiento por teclado muere al primer paso |
|
|
|
|
|
|
---
|
|
|
|
|
|
## Fichas
|
|
|
|
|
|
### D1 · AlertDialog mudo — `ARREGLADO` (`f4e414e2e`)
|
|
|
|
|
|
Action y Cancel llamaban `dialog.handleClose()`, que sólo voltea `open`: nunca
|
|
|
pasaban por `dismissWith` ni disparaban `emerge-close`. Medido: sólo sellaba
|
|
|
`contact-activate`. Escape sí funcionaba, así que **el teclado tenía firma y los
|
|
|
botones no**.
|
|
|
|
|
|
Arreglo: Action llama `dismissWith('save', { intent })` y Cancel
|
|
|
`dismissWith('cancel')`. `dismissWith` gana un `opts.intent` porque la causa es
|
|
|
vocabulario cerrado pero el PESO es por instancia — el mismo eje por el que
|
|
|
`emerge-open` liga con `fromProp`. Medido: Action → commit/risk/saved, Cancel →
|
|
|
emerge/cancelled.
|
|
|
|
|
|
### D2 · Regla CSS muerta del splitter — `ARREGLADO` (`6f42eebfe`)
|
|
|
|
|
|
`splitter.css` enganchaba `data-event='commit-resize'`, evento renombrado a
|
|
|
`commit-set` en `bd2e40366` (2026-05-22). No se arreglaba renombrando: el
|
|
|
`commit-set` de hoy apunta al `provider`, no al trigger.
|
|
|
|
|
|
Arreglo: la regla engancha por familia sobre el provider
|
|
|
(`[data-event-family='commit'][data-event-phase='active']`) y pinta el trigger
|
|
|
con **selector descendente**, más un opt-out del anillo global de commit. La
|
|
|
firma que pinta otro nodo es un selector descendente — no una razón para mover
|
|
|
el sello (doctrina en `docs/architecture/eidos.md` §«descendant-selector
|
|
|
corollary»).
|
|
|
|
|
|
### D3 · `commit-reset` mudo en 5 pickers — `ARREGLADO` (`f4e414e2e`)
|
|
|
|
|
|
Declarado en date / time / date-range / time-range / color y jamás disparado:
|
|
|
`clear()` sólo asignaba.
|
|
|
|
|
|
Al arreglarlo salió **una segunda avería debajo**: date-picker y
|
|
|
date-range-picker apuntaban `commit-reset` a la parte `calendar`, que nadie
|
|
|
registra en el runtime — destino inalcanzable, emit rechazado en silencio.
|
|
|
Retargeteados al `provider`.
|
|
|
|
|
|
⚠️ **Una parte declarada sin registro en runtime es un destino inalcanzable, y
|
|
|
no chilla.** Vale para todo el catálogo, no sólo para estos dos.
|
|
|
|
|
|
### D4 · ColorPicker commitea en silencio — `ARREGLADO` (`f4e414e2e`)
|
|
|
|
|
|
Swatch y eyedropper aplican el color sin disparar nada, pese a que el morfo **y**
|
|
|
el pack de sema declaran que disparan `commit-set`.
|
|
|
|
|
|
### D5 · Escape en `modal` cierra sin revertir — `ARREGLADO` (2026-08-12)
|
|
|
|
|
|
⚠️ **Dos afirmaciones de esta fila eran falsas y se corrigen aquí.**
|
|
|
|
|
|
1. _«`mode='modal'` promete bloquear Escape»_ — la promesa existía sólo en
|
|
|
[`picker-shell-handle.svelte.ts:32`](../../src/uix/soma/components/picker-shell/picker-shell-handle.svelte.ts)
|
|
|
y en un comentario de `date-picker.svelte`. **Era doc rancia**: el 2026-08-11
|
|
|
se adjudicó lo contrario en tres pickers, con razón escrita
|
|
|
([`time-picker/types.ts:42`](../../src/uix/soma/components/time-picker/types.ts)):
|
|
|
Escape descarta en AMBOS modos porque el morfo declara el patrón APG
|
|
|
`combobox` y el framework bloquea Escape en exactamente un sitio,
|
|
|
`alertdialog`; tragárselo dejaría a un usuario de teclado sin salida salvo
|
|
|
que el consumidor recordara componer un Cancel. La adjudicación no se propagó
|
|
|
al handle compartido ni a date-picker / date-range-picker. **Propagada el
|
|
|
2026-08-12.**
|
|
|
2. _«No hay buffer»_ — sí lo hay:
|
|
|
[`date-picker-provider.svelte.ts:153`](../../src/uix/soma/components/date-picker/date-picker-provider.svelte.ts)
|
|
|
captura `valueOnOpen` en la arista de apertura y `cancel()` (:262) lo
|
|
|
restaura.
|
|
|
|
|
|
**Lo que sí es un defecto, medido en navegador el 2026-08-12** (date-picker en
|
|
|
`mode='modal'`): el popover no autocierra al elegir ✓, pero Escape **cierra y se
|
|
|
queda la edición** (05/20 → 05/10). Es un tercer camino de salida que se comporta
|
|
|
como Save, y el contrato documentado del modo sólo tiene dos: Save confirma,
|
|
|
Cancel revierte.
|
|
|
|
|
|
La causa está a la vista: `PickerCloseCause` es `'save' | 'cancel' | 'dismiss'`
|
|
|
([`picker-shell-handle.svelte.ts:28`](../../src/uix/soma/components/picker-shell/picker-shell-handle.svelte.ts))
|
|
|
y **`'dismiss'` no lo produce nadie** — misma clase que D1/D3/D4. Escape cierra
|
|
|
por la vía del Popover sin pasar por `closeWith`, así que ni sella causa ni
|
|
|
revierte. Ningún picker cablea `escapeKeydownBehavior`: los cinco se comportan
|
|
|
igual.
|
|
|
|
|
|
**ARREGLADO 2026-08-12** — y con una tercera corrección de la fila: _«`'dismiss'`
|
|
|
no lo produce nadie»_ también era falso. Lo produce el **Popover**
|
|
|
(`dismissWith('dismiss')` en su manejador de Escape), no el picker; por eso el
|
|
|
picker nunca se enteraba. Grepear sólo el directorio del picker lo escondió.
|
|
|
|
|
|
El arreglo **no intercepta la tecla**, que sería frágil y estaría en la capa
|
|
|
equivocada (el `Content` es el del Popover, y su gancho de Escape es prop del
|
|
|
consumidor). Vigila la **arista de cierre**: un cierre que no declara causa es un
|
|
|
descarte, y en `modal` un descarte revierte al valor con el que se abrió. Eso
|
|
|
cubre además a un consumidor que baje `open` a mano, que tampoco es un Save. En
|
|
|
`inline` no revierte nada — allí la selección se aplicó según se hacía y el
|
|
|
snapshot sólo sirve para `cancel()`.
|
|
|
|
|
|
Vive una vez, en `watchPickerDismiss` (`picker-shell-handle.svelte.ts`), y los
|
|
|
cinco pickers lo consumen: eran cinco copias del mismo `watch` sobre `open`. El
|
|
|
provider declara su causa desde `closeWith`, así que `commit()` y `cancel()`
|
|
|
pasan intactos.
|
|
|
|
|
|
Medido en navegador (date / time / color-picker en `modal`): se edita, Escape
|
|
|
cierra el popover Y el valor vuelve al de apertura. ⚠️ La primera medición dijo
|
|
|
«Escape BLOQUEADO» — era la sonda leyendo a 400 ms, en plena animación de salida;
|
|
|
la traza temporal muestra el popover aún presente a +200 ms y ya cerrado, con el
|
|
|
valor revertido, a +600 ms.
|
|
|
|
|
|
Cinco tests sobre el guard compartido (`picker-shell-handle.svelte.test.ts`),
|
|
|
**vistos fallar** desactivando la reversión. ⚠️ Los tres primeros pasaban _en
|
|
|
vacío_ hasta que se les añadió `flushSync`: `watch` no ve una transición si las
|
|
|
tres escrituras caen en el mismo tick, así que no observaba ninguna arista.
|
|
|
|
|
|
### D6 · `Home` / `End` en la familia de segmentos — `CONFIRMADO` · P1
|
|
|
|
|
|
⚠️ **Otra fila con dos afirmaciones falsas.** Decía «`Home`/`End`/`PageUp`/
|
|
|
`PageDown` documentados en 4 sitios» y «las tres páginas se contradicen sobre qué
|
|
|
harían». `PageUp`/`PageDown` **no aparecen en ninguna parte**, y las tres
|
|
|
auditorías **coinciden** — no se contradicen:
|
|
|
|
|
|
| Documento | Qué especifica |
|
|
|
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| [`audit/components/date-field.md`](../audit/components/date-field.md) F-1 | `{ key: 'Home', action: 'set-min' }` / `{ key: 'End', action: 'set-max' }` en el morfo + implementación en provider, «extensivo a time-field/color-field (misma familia de segmentos)» |
|
|
|
| [`audit/components/time-field.md`](../audit/components/time-field.md) F-2 | «Sin Home/End (APG spinbutton) — pass de familia de segmentos (con date/color)» |
|
|
|
| [`audit/components/color-field.md`](../audit/components/color-field.md) F-2 | «especialmente útil aquí: canales 0-255/0-360 — pass de familia» |
|
|
|
|
|
|
No era una decisión sin tomar: era una **especificación acordada sin construir**,
|
|
|
con la declaración del morfo escrita literalmente en F-1.
|
|
|
|
|
|
**Hecho el 2026-08-12** — y con una tercera corrección de la fila: **color-field
|
|
|
YA lo tenía implementado** (`handleHomeEnd`, desde que se construyó). Su F-2
|
|
|
estaba desfasada. Así que el pase no inventó la conducta: copió la referencia que
|
|
|
ya vivía en el árbol, a date-field y time-field, incluidos sus segmentos
|
|
|
`dayPeriod` (donde `aria-valuemin` es AM y `aria-valuemax` es PM, así que Home /
|
|
|
End son absolutos donde las flechas ALTERNAN).
|
|
|
|
|
|
**Una sola acción declarada.** F-1 proponía `set-min` / `set-max`; color-field
|
|
|
declaraba `first-item` / `last-item`. El tipo obliga a elegir —
|
|
|
_«Semantic action identifier. Keep consistent across components for the same
|
|
|
intent»_— y el catálogo tiene dos vocabularios: el de LISTA (`first-item`, 25
|
|
|
usos, y `menu-dial` lo consume por el mapa de acciones del runtime) y el de VALOR
|
|
|
(`set-min`, que usa `knob`). Un segmento es un control de valor y no tiene ítems,
|
|
|
así que los tres declaran `set-min` / `set-max` y color-field se alineó. Sin
|
|
|
riesgo: los segmentos despachan por tecla, no por el mapa del runtime.
|
|
|
|
|
|
Medido con teclado real en el navegador, y los valores caen exactamente en el
|
|
|
aria que cada segmento publica: date-field mes `End→12 / Home→01` (min/max 1/12),
|
|
|
time-field hora `End→23 / Home→00` (0/23), color-field hex `End→ffffff /
|
|
|
Home→000000` (0/16777215). Home dos veces no envuelve.
|
|
|
|
|
|
Seis tests nuevos, **los seis vistos fallar** contra la conducta vieja
|
|
|
(inyectando el cierre del gate compartido y la retirada de cada rama). Incluido
|
|
|
uno para color-field, que era la referencia y **no tenía ninguno**.
|
|
|
|
|
|
### D7 · El Label de los campos renderizaba `<div>` — `ARREGLADO` (2026-08-12)
|
|
|
|
|
|
El morfo declara `defaultElement: 'label'` y el componente renderizaba `<div>`.
|
|
|
No eran dos componentes sino **cinco**: date-field, date-range-field, time-field,
|
|
|
time-range-field y color-picker.
|
|
|
|
|
|
⚠️ **La otra mitad de esta fila era falsa.** Decía «la doc promete un par
|
|
|
`for`/`id` que nadie emite»; no existe tal promesa en date-field —
|
|
|
[`types.ts:158`](../../src/uix/soma/components/date-field/types.ts) dice lo
|
|
|
contrario y bien: _«Prefer `aria-labelledby` with a `DateField.Label`»_. La fila
|
|
|
había mezclado el hallazgo del **`Field` genérico** (`field.md:19`, que sí emite
|
|
|
`for` condicional) con el date-field. El nombre accesible ya estaba cableado por
|
|
|
`aria-labelledby` desde el `role="group"` — que es el mecanismo correcto, porque
|
|
|
`for=` no puede apuntar a un grupo — y el clic-a-enfocar ya lo implementaba
|
|
|
`DateFieldLabelProvider.onclick` (enfoca el primer segmento).
|
|
|
|
|
|
Riesgo comprobado antes de tocar, no supuesto: `<label>` es `inline` por defecto
|
|
|
y `<div>` es `block`. Dos de los cinco reciben `display: inline-flex` de
|
|
|
`field.css` vía `fieldRole('label')`; los otros tres son ítems de flex o grid, y
|
|
|
el navegador los blockifica. Medido después en los cinco demos: ninguno queda en
|
|
|
`inline`, ninguno arrastra un `for` que no pueda honrar, y la maquetación no se
|
|
|
mueve.
|
|
|
|
|
|
### D8 · `defaultElement` falso en los `arrow` — `ARREGLADO` (2026-08-12)
|
|
|
|
|
|
La sospecha de deuda sistémica era cierta y peor de lo escrito: **los siete
|
|
|
`arrow` del catálogo declaraban `div` y ninguno renderiza un `div`.** Cinco
|
|
|
(combobox, dropdown-menu, popover, select, tooltip) componen
|
|
|
[`components/internal/arrow.svelte`](../../src/uix/soma/components/internal/arrow.svelte),
|
|
|
cuyo elemento de parte es un `<span>`; dos (context-menu, link-preview) rinden un
|
|
|
`<svg>` propio. Cada morfo declara ahora lo que su componente renderiza.
|
|
|
|
|
|
### D11 · `defaultElement` miente en 24 partes más — `CONFIRMADO` · P1
|
|
|
|
|
|
El censo que cerró D8 destapó la clase entera: **nada verificaba
|
|
|
`defaultElement`**, y es un contrato — `assertContract`, las recetas y quien lee
|
|
|
el morfo lo toman como el tag que aterriza en el DOM.
|
|
|
|
|
|
Hay un guard desde el 2026-08-12
|
|
|
([`contracts.test.ts`](../../src/uix/contracts.test.ts), «guards defaultElement
|
|
|
against the tag the soma part actually renders»), con las 24 restantes
|
|
|
enumeradas en `DEFAULT_ELEMENT_DEBT` para que la deuda sea visible y no crezca.
|
|
|
El guard lleva además un detector anti-podredumbre: si una entrada de la lista
|
|
|
deja de estar rota, falla pidiendo que se retire.
|
|
|
|
|
|
**Cerradas 24 de 28 el 2026-08-12** (eran 28, no 24: mi cifra anterior estaba
|
|
|
mal). Dieciséis eran el morfo yendo detrás de un componente que ya renderiza lo
|
|
|
más correcto — edición de declaración, el DOM no se mueve. Ocho más parecían del
|
|
|
grupo contrario y no lo eran: declaran un `role` explícito que carga la semántica
|
|
|
que daría el elemento nativo (`role='list'`, `listitem'`, `treeitem'`, `group'`,
|
|
|
`article'`, `tablist'`) o son decorativas (`aria-hidden`). Ahí la a11y **no está
|
|
|
rota** y la declaración era aspiracional; cambiar el DOM a listas nativas es una
|
|
|
mejora deliberada aparte, no algo que colar en un pase de verdad-del-contrato.
|
|
|
|
|
|
**Las 4 últimas, cerradas el mismo día.** Cada una pedía un cambio con
|
|
|
consecuencias, y la lección es que la pregunta «¿qué lado está mal?» sólo se
|
|
|
contesta mirando:
|
|
|
|
|
|
- `chronos.event-chip` — **el morfo tenía razón**, con el motivo escrito desde el
|
|
|
2026-08-10: el chip CONTIENE el tirador de redimensionado y el modelo de
|
|
|
contenido de un `<button>` no admite un control anidado, así que es `div` +
|
|
|
`role='button'`. Lo que faltaba era el puente: el «`tabindex` propiedad de soma»
|
|
|
que ese comentario promete no existía. Ahora el provider emite `tabindex: 0` y
|
|
|
un `onkeydown` que convierte Enter / Espacio en un clic (con `preventDefault`
|
|
|
sobre el espacio, que si no desplaza la página), y el componente puede ser el
|
|
|
`div` que su contrato declara.
|
|
|
- `tooltip.trigger` — **probé el morfo y me equivoqué; la medición lo corrigió.**
|
|
|
Declaraba `button`, así que rendericé un `<button>`… y la captura mostró el
|
|
|
cromado nativo alrededor del disparador. La receta llama a esta parte _«a
|
|
|
focusable shell»_ y difiere el anillo de foco _«to the wrapped element»_: es un
|
|
|
ENVOLTORIO, el control real del consumidor va dentro, y un `<button>` que
|
|
|
envuelve un `<button>` es el mismo anidamiento inválido del chip. El morfo era
|
|
|
el lado equivocado; declara `div`, y la a11y la dan el `tabindex: 0` del
|
|
|
provider y el `aria-describedby` del morfo, que es lo que el patrón APG pide.
|
|
|
- `stepper.item` — un `div` **sin rol** dentro de un `role='tablist'`. El patrón
|
|
|
es `list (tablist) > item > trigger (tab)`, y un elemento genérico entre un
|
|
|
tablist y sus tabs rompe la relación de posesión. Es `role='presentation'`
|
|
|
ahora — el envoltorio sale del árbol, igual que un `<li>` dentro de un
|
|
|
`role='menu'`; el idioma ya estaba en 8 componentes del catálogo.
|
|
|
- `color-picker.channel-input` — declaraba `input` mientras compone
|
|
|
`ColorField.Input`, que es `div` + `role='group'` (el contenedor de segmentos,
|
|
|
la misma forma que el `input` de date-field). Ninguna composición podía
|
|
|
satisfacer esa declaración: las piezas editables son las partes hermanas
|
|
|
`channel-segment`.
|
|
|
|
|
|
`DEFAULT_ELEMENT_DEBT` está **vacío**, y su comentario explica que una entrada
|
|
|
nueva es la decisión de publicar un contrato que miente.
|
|
|
|
|
|
Las 24 se partieron en dos, y por eso no se arreglaron en bloque:
|
|
|
|
|
|
- **El componente renderiza lo más correcto y el morfo va detrás** —
|
|
|
`calendar.header` / `range-calendar.header` (`header`), `field.helper-text` /
|
|
|
`field.error-text` (`p`), los cuatro de `slider` (`span`), `chronos.event-chip`
|
|
|
(`button`)… Arreglo mecánico: poner al día el morfo.
|
|
|
- **El morfo declara lo más correcto y el componente va detrás, con peso de
|
|
|
a11y** — `tree-view.item` / `.branch` declaran `li` y rinden `div`,
|
|
|
`tree-view.branch-content` declara `ul`, `stepper.list` declara `ol`,
|
|
|
`file-upload.file-list` declara `ul`, `feed.article` declara `article`,
|
|
|
`tooltip.trigger` declara `button`. Rendirlos como `div` pierde la semántica
|
|
|
de lista / landmark / control que APG espera. **Son decisiones, no ediciones.**
|
|
|
|
|
|
⚠️ Notas de método, por si alguien reescribe el guard: (1) el elemento de la
|
|
|
parte es **el que difunde los props**, no «el tag del último `{:else}`» — el
|
|
|
primer borrador usó lo segundo y se fue en FALSO VERDE, porque el `<svg>`
|
|
|
decorativo de `internal/arrow.svelte` no lleva spread; se descubrió inyectando
|
|
|
una regresión antes de fiarse. (2) Hay partes **polimórficas por diseño**
|
|
|
(`media-player.media` rinde `audio` o `video`, `breadcrumb.link` `span` o `a`):
|
|
|
el guard lee TODAS las ramas y da el contrato por bueno si el declarado está
|
|
|
entre ellas.
|
|
|
|
|
|
### D12 · Dos flechas se dibujan distinto que las otras cinco — `CONFIRMADO` · P2
|
|
|
|
|
|
Salió al censar D8. `internal/arrow.svelte` dibuja `<polygon>` **más** un
|
|
|
`<path>` de contorno trazado; las flechas propias de
|
|
|
[`context-menu`](../../src/uix/soma/components/context-menu/components/context-menu-arrow.svelte)
|
|
|
y [`link-preview`](../../src/uix/soma/components/link-preview/components/link-preview-arrow.svelte)
|
|
|
dibujan sólo el polígono. Dos de siete flechas del sistema no tienen contorno.
|
|
|
|
|
|
**ARREGLADO 2026-08-12**: las dos componen ya la primitiva compartida, así que
|
|
|
las siete flechas del sistema son el mismo `<span>` con el mismo `<svg>`, y las
|
|
|
siete declaran `span`. Las recetas pasan a direccionar las formas por dentro
|
|
|
(`polygon { fill }`, `path { stroke }` con `vector-effect: non-scaling-stroke`),
|
|
|
porque un `fill` sobre el envoltorio no puede pisar los atributos de presentación
|
|
|
que polígono y path llevan encima.
|
|
|
|
|
|
⚠️ **Y el arreglo destapó otro defecto por el camino.** Al componer la primitiva,
|
|
|
la flecha desapareció: `<span>` de 0×0 y sin `<svg>` dentro. Causa: los wrappers
|
|
|
de eidos de estos dos pasaban `{@render children?.()}` **incondicionalmente**, y
|
|
|
la primitiva lee CUALQUIER snippet de children como «el consumidor trae su propio
|
|
|
glifo» y se salta el suyo. popover y tooltip no pasan children en absoluto, por
|
|
|
eso nunca les pasó. Los dos wrappers son condicionales ahora — es la regla
|
|
|
«eidos wrappers need conditional children», reaprendida midiendo.
|
|
|
|
|
|
Medido tras el cambio: context-menu y link-preview dan `<span>` 10×5 con `<svg>`
|
|
|
10×5, polígono relleno y `<path>` con trazo — idénticos a tooltip, que es el
|
|
|
control. Antes link-preview no tenía `<path>` **ninguno**. Consola limpia.
|
|
|
|
|
|
### D9 · Eventos de chronos sin emisor — `CONFIRMADO` · P2
|
|
|
|
|
|
El morfo de chronos declara `handle-drag`
|
|
|
([`chronos.ts:131`](../../src/uix/morfo/components/chronos.ts)) y `handle-resize`
|
|
|
(:141), y **ningún provider los dispara**: el barrido de `trigger(` en
|
|
|
`soma/components/chronos/` da `shift-navigate`, `commit-restore`, `commit-apply`
|
|
|
y el trío `commit-save`/`commit-move`/`commit-delete` de `emitOnChip`. El
|
|
|
arrastre y el redimensionado emiten su `commit-move`, nunca la familia `handle`
|
|
|
que declaran. Ya declarado como S-37 en el README de chronos.
|
|
|
|
|
|
⚠️ Corrección de método, 2026-08-12: la primera lectura de esta fila dio «los
|
|
|
tres `commit-*` tampoco se disparan». Era falsa — se despachan **por variable**
|
|
|
en `emitOnChip` (`chronos-provider.svelte.ts:1240`), invisible a un grep de
|
|
|
`trigger('literal')`.
|
|
|
|
|
|
⚠️ Los dos documentos de chronos discrepaban: el README de soma decía que el pack
|
|
|
«ships rules» para ambos, y el SPEC que **se retiraron el 2026-08-07** por código
|
|
|
muerto. El SPEC tenía razón (el pack sólo lleva los tres `commit-*`); el README
|
|
|
quedó al día el 2026-08-12.
|
|
|
|
|
|
**ARREGLADO 2026-08-12 — se cablean.** `handle` pregunta _«¿estoy manipulando
|
|
|
directamente este objeto?»_, y una familia que no responde su propia pregunta ha
|
|
|
fallado en lo único para lo que existe ([CANON §2](../CANON.md)). Chronos es el
|
|
|
componente más manipulable del catálogo: hablaba el suelte (`commit-move`) y el
|
|
|
acarreo entero estaba mudo. El _handle invisible_.
|
|
|
|
|
|
No se autora nada: `handle` ya declara `sounds: { default: 'step' }` y los dos
|
|
|
canales en el mapa, así que el ritmo ES el sonido — un diente por emisión, a la
|
|
|
cadencia del gesto. Por eso las dos reglas del pack se retiraron el 2026-08-07
|
|
|
como código muerto: sobraba la regla, no el evento.
|
|
|
|
|
|
Estrangulado a ~14 Hz como slider y splitter, y **anclado**: los destinos son
|
|
|
`event-chip` y `event-resize-handle`, partes REPETIDAS, así que es una aplicación
|
|
|
directa de la ley de `e5811e422` (una ocurrencia con un sujeto entre muchos se
|
|
|
ancla). Chronos ya tenía el mecanismo en `emitOnChip`.
|
|
|
|
|
|
**Dónde el acarreo NO es de chronos.** Arrastrar un chip con el PUNTERO pasa por
|
|
|
el `DragDrop` compuesto, que posee ese bucle y lo expresa en `handle-pick` y
|
|
|
`handle-drop` — declara tres eventos y emite tres, sin acarreo continuo (CANON
|
|
|
regla 6: la evaluación se concentra en el drop). Añadir una tercera narración
|
|
|
encima serían dos componentes contando el mismo gesto. Así que `handle-drag` es
|
|
|
aquí el acarreo de TECLADO y `handle-resize` cubre las dos vías.
|
|
|
|
|
|
Medido: el arrastre del tirador con puntero da **6 `handle-resize` en 6 pasos**,
|
|
|
todos sobre `event-resize-handle`. El `handle-drag` sella `event-chip` a la
|
|
|
primera flecha y no más — no por el cableado, sino por **D14**.
|
|
|
|
|
|
### D10 · Los 3 resolvers de gesto: migración a medias — `CONFIRMADO` · P1
|
|
|
|
|
|
El 2026-08-06 se reescribió `dragSignalOverrides` (slider y splitter) a
|
|
|
`sound: 'step'` — el gesto pasa a sonar por REPETICIÓN — y eso dejó
|
|
|
`resolveHandleDragSound` / `resolveSliderDragSound` / `resolveSplitterDragSound`
|
|
|
sin un solo llamador. **No se adjudicó ninguno de los cinco sitios que los
|
|
|
describen como vigentes:**
|
|
|
|
|
|
| Sitio | Qué afirma |
|
|
|
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| [`splitter-provider.svelte.ts:435`](../../src/uix/soma/components/splitter/splitter-provider.svelte.ts) | «Pitch/centroid/gain come from… `resolveSplitterDragSound`» — y `dragSignalOverrides`, 30 líneas más abajo **en el mismo fichero**, dice que eso se sustituyó |
|
|
|
| [`sema/components/splitter.ts:15`](../../src/uix/sema/components/splitter.ts) | medio cierto: soma **sí** calcula overrides por emisión, pero no por ese resolver, y «la curva dinámica sobrevive» ya sólo vale para el háptico |
|
|
|
| [`PLAN-sound-engine.md:95`](./PLAN-sound-engine.md) | §4, los tres en la columna **«sema conserva»** de la LÍNEA DE CORTE |
|
|
|
| [`PLAN-audio-player.md:367`](./PLAN-audio-player.md) | D-AP.7, **✅ CONFIRMADO**: el slider adjunta el resolver en cada `handle-drag` |
|
|
|
| [`AUDIT-sema-2026-08-05.md`](./AUDIT-sema-2026-08-05.md) hallazgo 7 | la regla del pack del slider está muda **a propósito** porque las primitivas vienen del resolver — razón hoy sin base |
|
|
|
|
|
|
El slider **no** arrastra el comentario rancio: el suyo describe bien la
|
|
|
sustitución. La contradicción interna es sólo del splitter.
|
|
|
|
|
|
**Decisión abierta del autor:** ¿se retiran los resolvers de gesto, o se
|
|
|
recablean? La tabla de corte de `PLAN-sound-engine.md` §4 hay que actualizarla
|
|
|
en los dos casos. Nada se toca hasta entonces: reescribir los comentarios para
|
|
|
decir «se sustituyó» contesta la pregunta y borra la huella (regla 3).
|
|
|
|
|
|
**RETIRADA EJECUTADA Y REVERTIDA el mismo día (2026-08-12).** El borrado se hizo
|
|
|
en `5571f3bf7` y el autor lo revirtió en el acto, con razón: **la sesión era de
|
|
|
normalizar nombres de evento, no de modificar la librería de sonido**, y la
|
|
|
retirada se ejecutó sin leer primero el handoff vivo del eje
|
|
|
([`CONTINUE-sound-engine.md`](./CONTINUE-sound-engine.md) — la memoria del
|
|
|
proyecto lo marca «PRIMERO») ni `PLAN-audio-player-v2.md` (gate firmado), que ni
|
|
|
siquiera se sabía que existía. La verificación posterior salió limpia — ninguno
|
|
|
de los seis documentos no leídos nombra los resolvers — pero salió limpia por
|
|
|
suerte, no por método. `sounds.ts` está restaurado byte a byte; **no se toca sin
|
|
|
una sesión propia del eje de sonido, que empiece por su handoff.**
|
|
|
|
|
|
**DECISIÓN FIRMADA (2026-08-12): SE RETIRAN.** La ejecuta la sesión del eje de
|
|
|
sonido — la condición procedimental de arriba queda intacta (su handoff
|
|
|
PRIMERO). Con la retirada: actualizar la tabla de corte de
|
|
|
`PLAN-sound-engine.md` §4, adjudicar los cinco sitios de la tabla, y un
|
|
|
**SEXTO** que el re-análisis del 2026-08-12 destapó:
|
|
|
`docs/architecture/sema.md` §«The per-emit payload owns the primitives» aún
|
|
|
describe el payload sonoro de la era resolver («pitch / gain / contour derived
|
|
|
from pointer position and velocity»), cuando lo que soma adjunta hoy es
|
|
|
`sound: 'step'` + curva háptica (`dragSignalOverrides` en
|
|
|
`slider-provider.svelte.ts:233` y `knob-provider.svelte.ts:242`, con el
|
|
|
trade-off del 2026-08-06 citado en el propio comentario). El homónimo
|
|
|
`SoundContour` se disuelve con ellos — nada que renombrar.
|
|
|
|
|
|
**EJECUTADA (2026-08-12, esta vez desde el eje).** Sesión abierta leyendo
|
|
|
ENTEROS `CONTINUE-sound-engine.md` + `PLAN-audio-player-v2.md` antes de tocar.
|
|
|
Retirados los 7 símbolos (3 resolvers · `DragSoundParams` · `SoundContour` de
|
|
|
sema · `clamp`/`lerp` huérfanos) + los 5 re-exports + los 2 tests que sólo se
|
|
|
probaban a sí mismos; el registro (`SEMA_SOUND_VOICE`, `sample`,
|
|
|
`resolveSoundDefinition`…) intacto. Adjudicados los seis sitios — y DOS MÁS que
|
|
|
esta ficha no listaba, hallados al leer el eje (la fila de estado de
|
|
|
`CONTINUE-sound-engine.md`, «conserva su resolver por mecanismo», y D-AP2.7 de
|
|
|
`PLAN-audio-player-v2.md`, «el resolver del drag posee las primitivas»), ambos
|
|
|
con nota fechada. El detalle: comentarios de `splitter-provider` y del pack del
|
|
|
splitter reescritos al modelo vigente · `sema.md` §per-emit payload dice ya la
|
|
|
verdad (nombre + curva háptica) · tabla de corte §4 anotada ·
|
|
|
`book-deviations.md` D.7 gana la nota de que su pasado se hizo verdad hoy ·
|
|
|
`AUDIT-sema-2026-08-05` NO se toca (crónica por cabecera propia). El homónimo
|
|
|
queda resuelto por disolución: sobrevive sólo el `SoundContour` del art (5
|
|
|
valores); los docblocks que contrastaban con el de sema (`sema/types.ts` ·
|
|
|
`arts/motion/types.ts`) apuntan ahora al del art.
|
|
|
|
|
|
El análisis de abajo se conserva porque es evidencia válida PARA esa sesión:
|
|
|
|
|
|
- [`PLAN-sound-engine.md`](./PLAN-sound-engine.md) está **CERRADO** (2026-07-30) y
|
|
|
su columna «sema conserva» miente en 4 de 5 celdas: `SOUND_LIBRARY` no existe,
|
|
|
`SOUND_TUNINGS` se retiró el 2026-08-06 (con guard que lo mantiene retirado),
|
|
|
el sonido salió de la cascada y «qué firma para qué familia × intent» lo
|
|
|
sustituyó la ley del nombre. Es un inventario, no un mandato.
|
|
|
- [`PLAN-audio-player.md`](./PLAN-audio-player.md) está **⛔ GATE RECHAZADO —
|
|
|
SUSPENDIDO**, y su cabecera dice _«nada de lo de abajo se implementa»_. Su ✅
|
|
|
CONFIRMADO es una medición del terreno del 2026-07-31, cierta entonces.
|
|
|
- La fuente que gobierna no es un plan de proceso sino
|
|
|
[`book-deviations.md`](../decisions/book-deviations.md), de **decisiones**, que
|
|
|
el 2026-08-06 escribió en pasado: _«un gesto continuo suena por REPETICIÓN … y
|
|
|
**los tres resolvers se borraron con él**»_. `git log -S` sobre `sounds.ts`
|
|
|
devuelve **un solo commit en toda su vida** (mayo): nunca se borraron. El
|
|
|
registro dio por ejecutado lo que no se ejecutó.
|
|
|
|
|
|
Alcance medido de la retirada (por si esa sesión la retoma): los tres resolvers,
|
|
|
`DragSoundParams`, el `SoundContour` de sema, sus 5 re-exports, 2 tests que sólo
|
|
|
se probaban a sí mismos y los helpers `clamp`/`lerp` huérfanos — 81 líneas, 7
|
|
|
símbolos; el registro (`SEMA_SOUND_VOICE`, `sample`, `resolveSoundDefinition`…)
|
|
|
no se toca. La nota fechada que se añadió al §4 del plan cerrado también se
|
|
|
revirtió con el resto.
|
|
|
|
|
|
**Cae con ellos** el homónimo `SoundContour`: el 2026-08-11 se renombró
|
|
|
`SoundDirection` → `SoundContour` en
|
|
|
[`sema/sounds.ts:34`](../../src/uix/sema/sounds.ts) sin mirar que
|
|
|
[`arts/sound/types.ts:44`](../../src/arts/sound/types.ts) ya exportaba ese nombre
|
|
|
con cinco valores en vez de dos. Cinco sitios a tocar; el nombre no puede volver
|
|
|
a `SoundDirection` (el rename se hizo porque tres cosas distintas se llamaban
|
|
|
`direction`). Propuesto: `GestureContour`, que es la palabra que usa la propia
|
|
|
tabla de corte («los 3 resolvedores de **gesto**»). Si los resolvers se retiran,
|
|
|
el tipo se va con ellos y no hay nada que renombrar.
|
|
|
|
|
|
⚠️ Esta fila nació de un error de método: se propuso **retirar** los tres
|
|
|
resolvers por «cero llamadores», con dos de esos cinco sitios en la salida del
|
|
|
propio grep y sin abrirlos. «No lo referencia nadie» es una observación, nunca
|
|
|
un veredicto (regla 4).
|
|
|
|
|
|
---
|
|
|
|
|
|
## Cola menor · comentarios fósiles
|
|
|
|
|
|
~11 comentarios en código que citan nombres muertos, sin defecto de
|
|
|
comportamiento detrás: color-picker, combobox, nav-tree («stays sema-free» con
|
|
|
dos eventos `emerge` declarados) y aura, que cita el patrón
|
|
|
`close-save`/`close-cancel` que Dialog ya no usa. Sin `id` propio: se limpian
|
|
|
cuando se toque el fichero por otra razón.
|
|
|
|
|
|
### D13 · Siete eventos declarados que nadie emite — `CONFIRMADO` · P1
|
|
|
|
|
|
Salió al cerrar D9: es la CLASE, y la clase ya produjo D1 (AlertDialog mudo), D3
|
|
|
(`commit-reset` en cinco pickers), D4 (ColorPicker) y el propio D9.
|
|
|
`book-deviations.md` tipifica el defecto hermano —declarar una firma de canal que
|
|
|
la activación no incluye— como _«mecánicamente comprobable y merece guard»_. Esto
|
|
|
es la misma forma un piso más arriba.
|
|
|
|
|
|
Hay guard desde el 2026-08-12 (`contracts.test.ts`, «guards declared events
|
|
|
against the ones a provider actually emits»), con los siete en `INERT_EVENT_DEBT`.
|
|
|
|
|
|
⚠️ **Corrección 2026-08-12 (misma jornada): esto NO es «la cola», y el censo no
|
|
|
es nuevo.** [`CONTINUE-sema-audit.md`](./CONTINUE-sema-audit.md) §3.1 ya lo había
|
|
|
medido — _«eventos huérfanos: 14 de 249 en 104 morfos con soma»_— y, sobre todo,
|
|
|
ya los había **clasificado en tres clases que yo metí en un saco**. Verificado
|
|
|
componente a componente, la clasificación de aquella auditoría es la correcta:
|
|
|
|
|
|
| Clase | Cuáles | Quién la cierra |
|
|
|
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| **Contrato muerto** — el único defecto de la clase D1/D3/D4 | `tooltip` ×3 | libre; es el que queda accionable aquí |
|
|
|
| **Punto de extensión documentado** — lo emite la APP, no el componente | `virtual-list.handle-scroll` · `virtual-grid.handle-scroll-row/column` | el morfo **no tiene forma de declararlo**: la auditoría propone un `emission: 'app'`. Es vocabulario de morfo, no cableado |
|
|
|
| **Verbo delegado** — la transacción vive en un componente compuesto | `gradient-picker.commit-reset` (S-14, **1 de 6 casos idénticos**) | `CONTINUE-sema-audit.md`. No se arregla como D3: `GradientPickerProvider` **no tiene `clear()`** — su docblock dice que open/commit/cancel/clear viven en el `PickerProvider` genérico compuesto, y esta clase «sólo posee el runtime del morfo». No hay dónde enganchar; o se retira la declaración, o el externo re-emite |
|
|
|
|
|
|
Lo que este guard añade sobre aquel censo no es el hallazgo, es que **deja de ser
|
|
|
una medición de una vez**: bloquea la regresión. Eso lo demostró D9 al deshacerlo.
|
|
|
|
|
|
**El accionable: `tooltip`, 3 de 3.** Cero llamadas a `trigger(` en todo
|
|
|
`soma/components/tooltip` — abre y cierra **sin firma alguna**, en uno de los
|
|
|
componentes más ubicuos del catálogo. `CONTINUE-sema-audit.md` lo marca como
|
|
|
«HALLAZGO NUEVO, no está entre los 44» y lo tipifica como _contrato muerto_: o el
|
|
|
provider emite los tres, o las tres declaraciones se retiran. La pregunta previa
|
|
|
es cuál de las dos, porque el tooltip **compone Popover** y podría ser otro
|
|
|
`expression: 'delegated'` como los pickers — eso hay que medirlo antes de tocar,
|
|
|
no suponerlo.
|
|
|
|
|
|
⚠️ Nota de método: la prueba **no es** `trigger('literal')`. Chronos despacha por
|
|
|
variable (`emitOnChip`) y un grep literal dio dos falsos negativos durante la
|
|
|
auditoría. Lo que se comprueba es más débil y robusto: que el nombre aparezca
|
|
|
como literal en algún sitio que el provider alcance — su directorio soma **más
|
|
|
las librerías compartidas**. Sin `$libs/selection` en el ámbito, combobox y
|
|
|
select se leen como inertes y no lo son.
|
|
|
|
|
|
### D14 · El movimiento por teclado de chronos pierde el foco — `CONFIRMADO` · P1
|
|
|
|
|
|
Salió midiendo D9, y es anterior a él.
|
|
|
[`restoreChipFocus`](../../src/uix/soma/components/chronos/chronos-provider.svelte.ts)
|
|
|
reenfoca el chip en un `queueMicrotask`, que corre **antes** de que Svelte reponga
|
|
|
el nodo: consulta un elemento que ya no está, `dom.focus(null)`, y el foco cae al
|
|
|
`<body>`.
|
|
|
|
|
|
Medido: se enfoca un chip, se pulsa flecha → el agarre se activa (`data-dragging`)
|
|
|
y el foco pasa a `BODY`; la segunda y la tercera flecha **no llegan al
|
|
|
manejador**, y el Escape tampoco, así que el agarre queda colgado. La interacción
|
|
|
que el propio componente anuncia —_«Grabbed. Arrow keys move, Enter drops, Escape
|
|
|
cancels»_— muere al primer paso.
|
|
|
|
|
|
No se toca aquí: es un defecto de foco, no de firma, y merece su propia medición
|
|
|
(un `raf` o el `tick` de Svelte en lugar del microtask, verificado en navegador
|
|
|
real y no sólo headless).
|