# CONTINUE — el hilo del audio: REDISEÑO en curso (`$sound` = orquestador) > **Kickoff para la sesión siguiente**: _"Lee `docs/process/CONTINUE-sound-engine.md` > y sigue por donde toque."_ > **Fecha**: 2026-07-31 (tercera sesión) · Rama `alpha-0.1-sec-dom`. ## Lo que pasó el 2026-07-31 — giro de raíz Se presentó el gate del reproductor (D-AP.1…13) y el usuario lo **rechazó de raíz**: el sistema de sonido es un frankenstein — nació de los diseños de otros módulos (sema, media-player) y se parchea en función de lo que había, en vez de partir de su función: **orquestador de TODOS los aspectos del sonido**, como `motion` / `timers` / `scene`. Los consumidores se adaptan al servicio, no al revés. El rediseño se hizo clean-room y su gate quedó **FIRMADO** el mismo día (aprobación del plan + D-SR.4/D-SR.7 explícitas). ## Estado | Iniciativa | Estado | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`PLAN-sound-engine.md`](./PLAN-sound-engine.md) | ✅ CERRADA (histórica). Su §16.1 («la voz es del art») queda **REVOCADA** por el rediseño | | [`PLAN-sound-redesign.md`](./PLAN-sound-redesign.md) | ✅ **COMPLETA (las dos entregas, 2026-07-31) — sin commitear.** E1: F0–F2 + escucha A/B del usuario («suena igual»). E2: F3 (media citizenship: `sound.media(el)` provee el transporte con la forma del puerto `MediaProvider` + foco + `duckUiWhileContent` + visibilidad content-aware + `attach()` opt-in) · F4 (MediaSession de la fuente activa) · F5 (factoría con `EngineSoundPolicyOptions`; `contracts.ts` y estudio sin tocar, declarado). **AUDITADA (§9 del plan): AU-1…AU-9 resueltas** — duck ahora PERSISTENTE (estado derivado, holder = ducker más reciente; test nacido en rojo) · guards post-dispose · re-registro idempotente por elemento · drift guard soma↔art · bundle medido (16,2 KB min / 5,6 KB gz) · 3 guards vistos fallar por mutación. **28 suites / 305 tests verdes · check 73 = baseline, 0 propios · docs:check 0 · prettier limpio en lo propio.** Pendientes que exigen una página consumidora (llegan con el re-plan del player): medición Chrome del escenario mixto · prueba manual de teclas de medios | | [`PLAN-audio-player.md`](./PLAN-audio-player.md) | 🗄️ ARCHIVO (hechos + análisis que el v2 referencia) | | [`PLAN-audio-player-v2.md`](./PLAN-audio-player-v2.md) | ✅ **GATE FIRMADO 2026-07-31** («ok, continua»; H-9 = CABLEAR · D-AP2.7 = solo duck global). **F1 ✅ · F2 ✅ COMPLETAS (2026-07-31; morfo 8/8 · player 18/18 · check 74=baseline 0 propios)**. Detalle F2 en la fila del plan; el commit de rate cabalga `ratechange` (patrón G-5 de nacimiento). ⚠️ `translations:check` BLOQUEADO POR AJENO (`langs/components/palabras.ts` con `url:` rompe el stripper del script; palabras = excluido, no se toca) — correr cuando se arregle. **F3 ✅ COMPLETA (2026-07-31; player 10/10 · morfo 8/8 · check 74=baseline viva, 0 propios)** — detalle en la fila F3 del plan: transporte por el servicio como default (pin con motor REAL) · `metadata` público (opts+types+wrapper) · `nativeMediaProvider` retirado (media-provider.ts = contrato puro; drift-guard borrado) · G-5 aplicado · `formatDuration` · 6 sub-providers+wrappers+exports · README (tablas al día). **F4 ✅ (docblock del pack = doctrina D-AP2.7: el silencio lo posee el servicio vía `duckUiWhileContent`, receta escrita; la resta-de-gain v1 queda diseñada SIN cablear a propósito; stamp en navegador → F7). F5 EN CURSO — buffer-swap ✅ COMPLETO (29/29 · check 74=baseline 0 propios · eidos-lint limpio)**: el `TimeSlider` soma pasa `secondaryValue={bufferedTime}` y monta `Slider.SecondaryRange`; el div `.mp-buffered` + `--media-buffered` + `--_mp-buffered` + `--_mp-rail` RETIRADOS (css/provider/test/morfo-docblocks/READMEs al día); el look se conserva (42% → `--slider-secondary-bg` en scope). **F5 ✅ COMPLETA** (detalle en la fila F5 del plan: 6 wrappers + AudioLayout + `` con metadata derivada + 4 variantes + tokens; 37/37 contratos · lint 0 inválidos · check 78 = baseline+4 ajenos NUEVOS de `arts/connection`, 0 propios). **D-AP2.7 v2 COMMITEADA (`85b269f74`) + fix composición demos (`63f20f345`: el pack NO estaba en la lista del layout — packs sema son OPT-IN por app) + AMPLIADA a sliders por escucha real (`5d2816a0c`: pick/drag → `channels:['haptic']` por descendientes — estructurales sin intent, tirar su canal no borra carga evaluativa): el transporte del player es SILENCIOSO por defecto** — pack D.5 (resta de gain; `contact.silent` nuevo en sounds.ts) sobre commits de botones + descendientes (contact de Buttons, commit-set de Sliders); `commit-fail`/`commit-complete` audibles; `handle-drag` conserva su resolver por mecanismo (declarado); voz reactivable en capa 5b. **F7-construible ✅ COMMITEADO en `c4d64ba98`** (live badge localizado vía soma · demo con `` + selector de variante + snippet en paridad · README eidos §modo-audio · `component:audit --only media-player` PASS · check 74 = baseline, 0 propios). `smoke`+`perm:check` corridos (2026-07-31): **todas las rutas del hilo PASS** (301/305; los 4 rojos son ajenos — ethereal·heroscrolling·dome-gallery·card, hipótesis: los M vivos de tipografía de otra sesión — y combobox/stepper en perm); la demo del player sin `data-perm-step` aún (opt-in para el dossier). **Waveform: gate D-WF.1…8 FIRMADO («ok») · F1–F5 ✅ HECHAS — F4+F5 el 2026-08-01, SIN commitear** (F1–F3 en `0ab6cc822`; F4 eidos completo: wrapper+Wave+recipe+tokens `waveform:{height-xs…xl, bar-width, color, color-played, playhead-width}` — `playhead-width` añadido sobre la lista firmada, declarado — + guards visual-attrs/lint-allowlist; F5: demo `/uix/components/waveform` con picos REALES de `/sounds/*.wav` vía `decode`+`peaks`, READMEs soma+eidos, `component:audit --only waveform` PASS, `docs:check` 0; **fix F3 declarado**: `dir` no se reenviaba al Slider embebido — completado y verificado en vivo. Detalle en las filas F4/F5 del plan). **QUEDA F6 mirada** (ojos del usuario; la sonda en el 5173 vivo verificó estructura/cascada/interacción, pero el panel oculto no composita → sin captura). Los packs sema de waveform NO existen (delegated ✓ nada que registrar). **QUEDAN — y todos exigen ojos o la máquina del usuario**: F6 verificación VISUAL de las 4 variantes × light/dark × RTL (abrir `/uix/components/media-player`, chip `audio`, recorrer variantes; captura y MIRADA) · medición Chrome del escenario mixto (earcon + media, 1 contexto — patrón `javascript_tool` del §15 del plan del motor) · teclas de medios / pantalla de bloqueo (metadata en el SO) · stamp sema en navegador (`data-event="commit-toggle-play"` aterrizando en el play-button con la regla del pack casando). Nota demo: `duckUiWhileContent` se activa componiendo el app con `defineEngineSound({duckUiWhileContent: 0.2})` — la demo actual no recompone el uix global; se muestra como receta en el README (decisión: no tocar la composición raíz de las demos desde la página del player). Después: F4 sema · F5 eidos (AudioLayout+AudioPlayer+variantes+buffer en `Slider.SecondaryRange`) · F6 formas · F7 demo+medición Chrome+teclas de medios. ⚠️ baseline de `check` fluctúa por la rama viva (~74, 0 propios; filtrar SIEMPRE con `\\\\` e incluir consumidores). Lección F1: claves nuevas de `ActiveProps` van OPCIONALES o rompen todos los call-sites | > ⚠️ **2026-08-12** — donde la fila del plan v2 dice «`handle-drag` conserva su > resolver por mecanismo (declarado)», quedó rancio el 2026-08-06: el rediseño > del sonido pasó los gestos a REPETICIÓN (`sound: 'step'` por emisión) y los 3 > resolvers quedaron sin llamador; hoy están RETIRADOS > ([`AUDIT-docs-code-ledger.md` §D10](./AUDIT-docs-code-ledger.md)). El > silencio por canal de los sliders del player (D-AP2.7 v2) no cambia. > **2026-08-01 — hay un handoff hermano.** Lo que la mirada del usuario abrió ese > día y quedó SIN cerrar (el **Slider roto en RTL**, la disposición de los botones > del player, los iconos de seek, la medición del earcon mal hecha, `Captions` y > el cableado del waveform) vive en > [`CONTINUE-player-rtl.md`](./CONTINUE-player-rtl.md). **Empezar por ahí.** ## Defectos que la MIRADA del usuario encontró (2026-08-01) — arreglados La verificación visual F6 destapó cuatro cosas que ninguna sonda había visto. Se arreglaron el mismo día; todas verificadas, **el guard de cada una nacido en rojo**. 1. **El anillo alrededor de los sliders** («se viene arrastrando desde el principio»). NO era del arquetipo ni del slider: es la **firma global de la familia `commit`** — `BUILTIN_SIGNATURES.commit` (`lib/motion/presets/css.ts`) monta `commit-settle`, cuyo 30% pinta `box-shadow: 0 0 0 3px`. El `commit-set` del Slider apunta al **provider**, así que el anillo enmarcaba la caja entera (medido: 544×40). Un aro lee como feedback en un botón y como borde parásito en un raíl ancho. **Arreglo**: exención acotada en `slider.css`, el patrón que `gradient-builder.css` ya practicaba. Alcanza a todo slider embebido (waveform, media-player, color-picker, time-picker) porque todos llevan `[data-slider]`. 2. **El mismo anillo en el media-player**: `commit-complete` / `commit-fail` apuntan al provider → borde alrededor de los ~700px del reproductor. Misma exención en `media-player.css`. Los botones conservan su aro pequeño (20×36), que es donde el feedback pertenece. 3. **El botón de saltar al final anunciaba una compleción FALSA.** Los botones de salto no tienen evento propio (D-AP2.13 delega en el `commit-set` del scrubber — cierto para el scrubber, **vacío para los botones**, que no tienen Slider detrás). Al saltar más allá del final el elemento dispara `ended` → `commit-complete` en el provider → sonido (es audible por D-AP2.7) + anillo. El de retroceso quedaba mudo: asimetría audible. **Doctrina**: saltar al final NO es completar, es abandonar la obra en el final. `seek()`/`scrubTo()` marcan el salto que aterriza en el final y `ended` no anuncia; un `play` posterior limpia la marca, así que el final natural sí se sigue anunciando. 4. **MediaSession sin overlay del SO**: el modo **audio ya funcionaba** (metadata completa, medida en vivo); el roto era **vídeo**, que es el modo por defecto de la demo. `` deriva su metadata de title/artist/artwork; el `` compositivo no puede leer su hijo `Title` sin volverse data-driven, así que la declara el consumidor — y la demo no lo hacía. **Lección de método**: se propuso al usuario cambiar el keyframe `commit-settle` (doctrina) SIN haber leído el corpus, y el usuario lo paró: _«es una temeridad plantear esto sin haber hecho la lectura de todo el sistema»_. La lectura después lo desmintió — `motion.md` §4 fija que la firma es genérica por decisión estructural («un `commit` asienta igual en todo el sistema»), eidos es el ÚNICO dueño del canal visual, los packs de sema solo transportan `sound`/`haptic`, y el `VisualChannel` no consulta `activeChannels`: **la regla acotada en la receta ES la puerta**, no un olor. Lo único que queda en pie de aquel informe es una observación menor: `commit-settle` escribe `3px` a pelo donde sus hermanos `announce-pulse-*` usan `var(--focus-ring-width)`. Precedente relacionado que YA estaba decidido y no se había leído: `VirtualList` / `commit-set-resize` lleva **`channels: []`** (`design-chat-block.md`) — el override de morfo que salta proyección, dispatch y hold, para eventos que son contabilidad estructural y no gesto del usuario. Está cableado en `engine.ts` (no está latente); lo latente es el silenciado PARCIAL (callar lo visual conservando el sonido), que hoy no tiene palanca declarativa. ~~**Abierto, sin tocar**: los subtítulos nativos se solapan con la barra de controles.~~ **CERRADO 2026-08-04.** El diagnóstico era correcto y el arreglo fue el que predecía: la pista se mantiene en `mode='hidden'` —activa, con `activeCues` y `cuechange`, pero sin pintado del navegador— y el texto se monta en la parte `Captions`, que llevaba declarada desde el principio y nunca se había implementado. Verificado a ojo en Chrome, claro y oscuro: el texto sale **encima** de la barra con 8px de aire. El espejo de una sola dirección de `captionsOn` cerró en el mismo arreglo. Detalle en el README de eidos del media-player, §Captions. ## Qué es el rediseño (resumen; la verdad está en el plan) `$sound` gobierna 7 dominios: sustrato (contexto único — se conserva, verificado) · **mezcla** (master → buses `ui`/`content`; `prefs.sound` ↔ `ui` SOLO) · **voces** (calibración = dato del consumidor; sema registra la suya; default = valores actuales, cero cambio audible) · **ciudadanía de media** (el servicio PROVEE el transporte con la forma del puerto `MediaProvider`) · política/foco (`mixed`/`exclusive`/`duck`; `duckUiWhileContent` sustituye el parche D-AP.7) · percepción (`decode`; analyser/picos v2) · MediaSession (del servicio). ## MediaSession — VERIFICADA POR EL USUARIO (2026-08-01) ✅ El pendiente que arrastraba tres sesiones («prueba manual de teclas de medios / pantalla de bloqueo — cuando exista una página que consuma `sound.media()`») queda **CERRADO**. El usuario abrió el media hub de Chrome con la demo de audio sonando y confirmó: **carátula + título + artista + transporte**, origen `localhost:5173`. Notas de la verificación, para no repetir el camino: - **Las teclas de VOLUMEN no sirven** para esto: muestran el OSD de volumen de Windows y nada más. En Windows 11 Microsoft retiró el panel de medios que antes se pegaba a ese OSD. La superficie fiable es el **botón de nota musical de la barra de Chrome** (solo existe mientras suenan medios), o las teclas de Play/Pause / pista. - **La sospecha de la carátula era infundada**: se temió que `artwork` sin `sizes`/`type` y sin CORS (`media.w3.org` no manda `Access-Control-Allow-Origin`) hiciera que el `MediaImageManager` la descartara. **Carga perfectamente.** No se tocó nada; queda registrado para que nadie «arregle» un no-problema. - **Anterior/siguiente pista salen apagados y es CORRECTO**: `setActive()` registra `play`, `pause` y `seekto` — un reproductor de una sola fuente no tiene lista, y declarar esos handlers mentiría sobre su capacidad. Los ±10s del hub los sintetiza Chrome desde `seekto`. - **Lección de método**: se afirmó «el audio ya funcionaba» apoyándose en una medición JS (`navigator.mediaSession.metadata` poblada) como si zanjara lo que el usuario ve. No es lo mismo: el contrato JS puede cumplirse y el overlay del SO no aparecer (media muteada, modo equivocado, tecla equivocada). Una medición del contrato NO es una verificación de la experiencia. ## Escenario mixto — MEDIDO Y CERRADO (2026-08-04) ✅ El pendiente que arrastraba desde E2 («medición Chrome del escenario mixto — earcon + media, 1 contexto») queda cerrado. Detalle completo en [`CONTINUE-player-rtl.md`](./CONTINUE-player-rtl.md) §4; lo esencial: - **Con media real sonando (sin mutear, vol 1) y 26 earcons encima: UN solo `AudioContext`.** `createMediaElementSource` = 0 → la media nunca entra en el grafo. 0 contextos cerrados, cero churn. - **La receta que los handoffs prescribían no hacía falta.** No hay que instrumentar antes del boot ni navegar client-side: eso resuelve el parche del CONSTRUCTOR, que sólo sabe decir «¿se creó uno nuevo?». Se parchea el **PROTOTIPO** (`createGain` / `createOscillator` / `resume`) y se recoge el `this` en un `Set`: enumera INSTANCIAS, así que un contexto preexistente aparece igual. **Es el patrón a usar la próxima vez.** - Fechado: el contexto nace en **t ≈ 4,1s tras la carga, sin gesto de usuario**, ya `running`. Y **no lo crea el player** — en `/uix/components/button`, sin ningún elemento media, nace igual (t ≈ 4,08s). Es del arranque de la app. ⚠️ ~~**Hallazgo nuevo, de la app de demos y NO tocado**~~ **CABLEADO el 2026-08-06 (`d161cedcc`) y MEDIDO el 2026-08-12**: `+layout@.svelte` pasa ya un `SemaPreferences` VIVO (`$state`) a `events.preferences`, y el toggle escribe `semaPreferences.sound = 'full' | 'off'` además del intent de prefs. A/B en navegador (parche de PROTOTIPO contando instancias, el patrón de abajo): click con Sound ON → **+2 osciladores** (el par del earcon); mismo click con Sound OFF → **+0**. Y desde `fa79cc542` (S-27/S-41) el `off` tampoco abre contexto ni registra listeners en `prepare`. El texto original se conserva como registro de la medición del 2026-08-04: el interruptor **Sound** del topbar **no silencia los earcons**. Con `data-sound="reduce"` la envolvente del earcon es idéntica a con Sound ON (`[0, 0.25, 0.25, 0.0001]`, mismos nodos). El toggle escribe el intent y estampa el atributo, pero `web/routes/uix/+layout@.svelte` no pasa `preferences` al canal de sonido de sema, que se queda en nivel `'full'`. El framework sí lo soporta (`sema/engine.ts` reenvía `soundOptions.preferences ?? opts.preferences`). Medido sobre la envolvente del propio earcon; una ganancia de bus aguas abajo NO se midió, así que el veredicto de «se oye o no» es del oído. ## Stamp de sema en navegador — CERRADO por las dos mitades (2026-08-04) ✅ **Mitad 1 — dónde aterriza.** Medido con un `MutationObserver` sobre el subárbol del player, filtrando `data-event*`. Un clic en play produce, **sobre el propio `[data-media-player-play-button]`** y no sobre el provider: | evento | familia · intent | fase | | -------------------- | -------------------- | -------- | | `contact-activate` | `contact` | `active` | | `commit-toggle-play` | `commit` · `neutral` | `active` | …y a continuación los atributos se limpian, que es la ventana del hold cerrándose. El stamp aterriza en el **botón**, como el morfo promete. **Mitad 2 — la regla del pack CASA.** A/B dentro del propio player, midiendo el pico de la envolvente programada (parche sobre `AudioParam.prototype`): | disparo | pico | veredicto | | -------------------------------- | ---------- | ---------- | | play-button (transporte) | **0.0001** | silenciado | | mute-button (transporte) | **0.0001** | silenciado | | `commit-complete` (fin de pista) | **0.05** | audible | Los osciladores **sí se construyen** (8 parámetros programados) pero la ganancia nunca sube: es la resta de gain de D-AP2.7 / pack D.5, funcionando. Un `