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/AUDIT-docs-code-ledger.md

39 KiB

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 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): 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 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) 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 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 F-2 «Sin Home/End (APG spinbutton) — pass de familia de segmentos (con date/color)»
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 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, 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, «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 y link-preview 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) 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). 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 «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 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 §4, los tres en la columna «sema conserva» de la LÍNEA DE CORTE
PLAN-audio-player.md:367 D-AP.7, ✅ CONFIRMADO: el slider adjunta el resolver en cada handle-drag
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 — 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 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 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, 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 sin mirar que arts/sound/types.ts:44 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 §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 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).

Powered by TurnKey Linux.