|
|
# 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 + `<AudioPlayer>` 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 `<AudioPlayer>` + 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. `<AudioPlayer>` deriva su metadata de title/artist/artwork;
|
|
|
el `<MediaPlayer>` 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 `<Button>`
|
|
|
corriente fuera del player mide **0.25** — 2500× el transporte.
|
|
|
|
|
|
⚠️ **Trampa en la que caí**: comparé ese 0.25 de un Button de otra página con el
|
|
|
transporte y creí ver una contradicción con D-AP2.7. No la había — son
|
|
|
poblaciones distintas. **El A/B tiene que ser dentro del mismo componente.**
|
|
|
|
|
|
## F6 — matriz visual del modo audio: MEDIDA, 1 defecto (2026-08-04)
|
|
|
|
|
|
Las **16 combinaciones** (4 variantes × claro/oscuro × LTR/RTL) recorridas
|
|
|
programáticamente, buscando partes colapsadas, botones estrujados y desbordes:
|
|
|
|
|
|
- `card` y `row`: limpias en las 4. Sólo se oculta `live-indicator`, que es lo
|
|
|
correcto (la receta lo esconde si no hay directo).
|
|
|
- `inline`: oculta artwork/title/artist/time/rate/mute/volume — **por diseño**,
|
|
|
es lo que la receta declara para la nota de voz.
|
|
|
- **`bar`: colapsa, pero SÓLO por debajo de ~800px.** Barrido de anchos del
|
|
|
contenedor, con la variante verificada en la misma medida:
|
|
|
|
|
|
| ancho | scrubber | mute |
|
|
|
| -----: | -------: | -----: |
|
|
|
| 720px | **0** | **20** |
|
|
|
| 900px | 147 | 36 |
|
|
|
| 1100px | 347 | 36 |
|
|
|
| 1800px | 1047 | 36 |
|
|
|
|
|
|
De 900px en adelante está **sano**. Los 720px son el ancho al que **la demo**
|
|
|
encierra al player, no un ancho propio de una barra de app persistente (la
|
|
|
referencia declarada es Spotify, que ocupa la ventana entera). **Visto a
|
|
|
720px**: el scrubber queda reducido a un punto ámbar entre `0:00` y `6:12`
|
|
|
mientras el volumen conserva sus 64px. Ningún desborde en ninguna combinación.
|
|
|
|
|
|
### El diagnóstico correcto — corrección del usuario (2026-08-04)
|
|
|
|
|
|
La pregunta que yo había planteado, **«¿qué cede en `bar`?», es la pregunta
|
|
|
equivocada a nivel de framework**. El usuario lo zanjó: _«¿no es un diseño por
|
|
|
composición? debe de ser por composición y eso lo elegirá el diseñador»_. Y
|
|
|
encaja con la doctrina que el propio `audio-player.css` ya declara — un variant
|
|
|
sólo decide **qué se muestra y cómo fluye**, nunca comportamiento — y con la
|
|
|
salida que ya existe: componer `<MediaPlayer media='audio'>` a mano mantiene el
|
|
|
flujo sin opinión, porque todo el CSS de variantes cuelga de `[data-variant]`,
|
|
|
que **sólo estampa `<AudioPlayer>`**. Quien quiera otra mezcla a 720px, compone.
|
|
|
|
|
|
Lo que NO se disuelve en composición: que un preset **colapse en silencio**. El
|
|
|
diseñador no recibe ninguna señal de que está por debajo del ancho viable — el
|
|
|
control simplemente desaparece.
|
|
|
|
|
|
### ARREGLADO — el usuario fijó el criterio (2026-08-04)
|
|
|
|
|
|
Aclaración del usuario: no era `bar` lo que estaba mal, era **el track dentro de
|
|
|
`bar`**, «reducido a un punto». Y con ella, las dos decisiones de diseño, suyas:
|
|
|
|
|
|
> _«no puede ser que el slider del tracking sea más pequeño que la del
|
|
|
> volumen»_ · _«lo que sí quitaría del bar es lo que aparece ahora con
|
|
|
> Vicen…, sólo dejaría el título»_
|
|
|
|
|
|
**La causa era de una línea.** `media-player.css` le da a todo scrubber un suelo
|
|
|
(`min-width: var(--space-8)`) y la regla de `bar` lo **anulaba explícitamente**
|
|
|
con `min-inline-size: 0` y mayor especificidad — por eso flex podía encogerlo
|
|
|
hasta la nada y dejar sólo el pulgar.
|
|
|
|
|
|
Aplicado:
|
|
|
|
|
|
- El ancho del volumen sale a token (`--_mp-volume-width`) y el scrubber de
|
|
|
`bar` **se apoya en él** como suelo, así que los dos no pueden divergir cuando
|
|
|
alguien retoque uno.
|
|
|
- El **artista se oculta** en `bar` (`card` y `row` lo conservan: tienen sitio).
|
|
|
|
|
|
Medido tras el arreglo, con la variante verificada en la misma pasada:
|
|
|
|
|
|
| ancho | scrubber | volumen | artista | mute |
|
|
|
| -----: | -------: | ------: | ------: | ---: |
|
|
|
| 560px | **64** | 64 | oculto | 20 |
|
|
|
| 720px | **64** | 64 | oculto | 25 |
|
|
|
| 900px | 229 | 64 | oculto | 36 |
|
|
|
| 1200px | 529 | 64 | oculto | 36 |
|
|
|
|
|
|
Sin desborde en ninguno. **Visto**: los dos sliders se leen ya del mismo tamaño
|
|
|
y el título gana el espacio del artista.
|
|
|
|
|
|
⚠️ **Queda un estrujado que NO toqué**, porque arreglarlo sí arbitra: el
|
|
|
`mute-button` baja a **20–25px** por debajo de 800px (su tamaño es 36). Ponerle
|
|
|
`flex: none` empujaría el problema a otro control, y eso es decisión de diseño.
|
|
|
|
|
|
Lo que sigue sin resolverse en composición y queda vivo:
|
|
|
|
|
|
- ~~documentar en el README el ancho mínimo de `bar` (~900px), que es un valor de
|
|
|
diseño de la variante, como ya lo son los tamaños de carátula.~~ **HECHO
|
|
|
2026-08-12** (README de eidos, sección de variantes, con la disposición del
|
|
|
orden de descarte firmada).
|
|
|
|
|
|
## Pendiente que solo puede hacer el usuario
|
|
|
|
|
|
- ~~Escucha A/B de F2~~ ✅ **hecha: «suena igual» (2026-07-31)**.
|
|
|
- Prueba manual de teclas de medios / pantalla de bloqueo — **cuando exista una
|
|
|
página que consuma `sound.media()`** (el re-plan del player la traerá).
|
|
|
|
|
|
## Por dónde sigue (reescrito 2026-08-04 — lo anterior estaba cumplido)
|
|
|
|
|
|
⚠️ **Higiene del documento**: la tabla de Estado de arriba dice «sin commitear»
|
|
|
del rediseño y de las fases F4/F5 del waveform. **Es rancio y está verificado**:
|
|
|
el rediseño es `2f872ef86`, el waveform `0ab6cc822` + `905012507`, y esas rutas
|
|
|
están limpias en el árbol. El «re-plan del reproductor sobre el servicio» que
|
|
|
esta sección pedía **también está hecho** (F3: el player consume
|
|
|
`uix.sound.media(el, { metadata })`), igual que sus dos pendientes de cierre —
|
|
|
escenario mixto y teclas de medios.
|
|
|
|
|
|
De este hilo quedan **cuatro cosas, y ninguna es código a ciegas**:
|
|
|
|
|
|
1. ~~**La mirada del waveform (su F6)**~~ ✅ **APROBADA por el usuario
|
|
|
(2026-08-04): «waveform … las veo bien».** Cierra el pendiente que arrastraba
|
|
|
desde el 2026-08-01.
|
|
|
2. ~~**La mirada sobre el resto del modo audio**~~ ✅ **LAS CUATRO APROBADAS.**
|
|
|
`card` el 2026-08-04 («la variante card la veo bien») y **`row` e `inline` el
|
|
|
2026-08-05** («ya te las di por buenas row e inline»). El eje visual del modo
|
|
|
audio queda CERRADO — no volver a listarlas como pendientes.
|
|
|
3. ~~**`bar` por debajo de su ancho**~~ ✅ **ARREGLADO** (ver §F6): el scrubber
|
|
|
ya nunca se dibuja más estrecho que el volumen y el artista sale de `bar`,
|
|
|
las dos por decisión del usuario. **Queda vivo** el `mute-button` estrujado a
|
|
|
20–25px bajo 800px — no lo toqué porque arreglarlo empuja el problema a otro
|
|
|
control, y eso es diseño.
|
|
|
**Disposición FIRMADA (2026-08-12)**: el ancho mínimo (~900px) queda
|
|
|
documentado en el README de eidos como valor de diseño de la variante; el
|
|
|
arreglo real firmado es ORDEN DE DESCARTE — bajo el umbral cae el slider de
|
|
|
volumen y el mute recupera sus 36px — con container query como mecanismo
|
|
|
honesto: el primer caso concreto que justifica reabrir D-AP2.9.
|
|
|
4. ~~**El rojo de `audio-player` en `lint.test.ts`**~~ **RESUELTO — la decisión
|
|
|
ya estaba tomada en el código** (verificado 2026-08-12, 18/18 verde): existe
|
|
|
la categoría `COMPETING_ROOTS` (`lint.test.ts:70`) con la razón escrita,
|
|
|
separada a propósito de `KNOWN_MISSING_MORFO`, que «prometería una
|
|
|
construcción que no debe ocurrir jamás».
|
|
|
|
|
|
## Sesión 2026-08-04 (tarde) — cuatro entregas, todas verificadas en navegador
|
|
|
|
|
|
Commits `ee51172d8` · `5cbb03904` · `dbd239bcc` (+ el waveform, `127c4c95b`).
|
|
|
El detalle está en los mensajes y en el README de eidos del componente; aquí
|
|
|
sólo lo que cambia el mapa.
|
|
|
|
|
|
1. **El waveform ya se VE en la demo.** No se veía por dos causas encadenadas:
|
|
|
el scrubber compone el Waveform de _soma_, así que su envoltorio de eidos
|
|
|
nunca montaba y **su receta no se importaba** (onda sin estilar, 255px de
|
|
|
alto); y la muestra local era `whoosh.wav`, de **0,18 s**. El player carga
|
|
|
ahora `waveform.css` igual que ya cargaba `slider.css`, y la demo usa
|
|
|
`/music/t2.wav` (2:44). ⚠️ **Lección reutilizable**: componer un primitivo
|
|
|
desde soma deja su receta fuera — la carga el componente que lo compone.
|
|
|
2. **La velocidad ya se puede BAJAR** (`RateFloat`). El `RateButton` sólo subía
|
|
|
y recortaba en `2×`. Ahora `AudioLayout` compone un `DropdownMenu` +
|
|
|
`RadioGroup`; el disparador sigue siendo el `RateButton` real, con su ciclado
|
|
|
anulado vía `preventDefault()` en la costura. ⚠️ Dos defectos del panel
|
|
|
portado, los mismos que el float de volumen: el suelo de `12rem` del menú, y
|
|
|
**la etiqueta redimensionaba el botón y desplazaba la botonera** (ancho fijo
|
|
|
`4.6em`, medido 64·74·88·121px).
|
|
|
3. **API de eventos para la app** — `onPlay` · `onPause` · `onEnded` ·
|
|
|
`onError`. Antes no había NINGUNA: la única vía era enchufar un
|
|
|
`MediaProvider` entero. Dos divergencias deliberadas respecto a la capa
|
|
|
perceptual, las dos en el test: el `pause` del final natural no se reporta, y
|
|
|
`onEnded` SÍ dispara para un final por salto (que `commit-complete` exime).
|
|
|
4. **Transporte de lista** — `RestartButton` + `SkipButton` (morfo **25** partes,
|
|
|
las dos opt-in, sin eventos sema nuevos por decisión del usuario). El player
|
|
|
**no tiene cola a propósito**: `onPrevious`/`onNext` piden y la app cambia el
|
|
|
`src`. Una dirección sin manejador se pinta deshabilitada, que es justo el
|
|
|
aspecto de los extremos de una cola.
|
|
|
|
|
|
5. **Selector de pista de subtítulos** (`CaptionFloat`, `21d817c1f`).
|
|
|
`toggleCaptions()` sólo alcanza la PRIMERA pista — a propósito, es un toggle,
|
|
|
y las demás quedan en `disabled` porque **`hidden` obliga al navegador a
|
|
|
descargar el VTT**. Con dos idiomas no había forma de llegar al segundo.
|
|
|
Añadidos a soma: `selectCaptionTrack(track|null)` y los espejos reactivos
|
|
|
`captionTrackList` / `activeCaptionTrack` (el `TextTrackList` vivo NO
|
|
|
notifica, así que un chrome que LISTA pistas no veía las que llegan tarde).
|
|
|
⚠️ Cambiar de idioma **no** dispara `commit-toggle-captions`: sólo lo hace una
|
|
|
transición real de encendido/apagado.
|
|
|
6. **La calidad, especificada sin implementar** (`e0206f6b2`).
|
|
|
`docs/decisions/design-media-quality.md`. Grep del puerto: **cero** campos de
|
|
|
calidad, y no es descuido — los niveles los aporta un motor HLS/DASH que este
|
|
|
framework no vendoriza. Por eso `settings-button` **sigue sin implementar**:
|
|
|
la velocidad tiene lista propia, la pista también, y el tercer eje no existe.
|
|
|
Un engranaje con un solo cajón sería el `CaptionFloat` disfrazado.
|
|
|
|
|
|
## Cola de la revisión adversarial — CERRADA (2026-08-05)
|
|
|
|
|
|
Una revisión adversarial (5 lentes × 3 escépticos) devolvió 15 supervivientes de 19. Los míos están arreglados en `aaced3ebe` (portal + `dir` + paridad de la demo
|
|
|
|
|
|
- tabla de eventos) y los cuatro que dependían del provider, en cuanto la otra
|
|
|
sesión commiteó (`5983b00d5`), quedaron así:
|
|
|
|
|
|
1. ✅ **`toggleCaptions` ya no descarta la pista elegida.** Recuerda la última
|
|
|
elegida (`preferredCaptionTrack`) y la RESTAURA al volver a encender; cae en
|
|
|
la primera sólo si nadie ha elegido, que es el caso de una sola pista y el
|
|
|
comportamiento de siempre. Medido: elegir Español, `c`, `c` → sigue Español.
|
|
|
2. ✅ **Fuga de teclas desde el disparador.** La guarda de propiedad se ensanchó
|
|
|
a `[data-slider],[role='menu'],[aria-haspopup='menu']`, que es justo lo que su
|
|
|
propio comentario ya predicaba («the exemption is by ownership, not by tag»).
|
|
|
Medido: con el foco en el disparador, cuatro teclas y nada se mueve; con el
|
|
|
foco en el player, `ArrowDown` sigue bajando el volumen — no se ensanchó de
|
|
|
más.
|
|
|
3. ⚠️ **`aria-pressed` + `aria-keyshortcuts='c'` sobre el disparador del
|
|
|
`CaptionFloat`: NO arreglado, y no se puede desde ahí.** Se intentó anularlos
|
|
|
en la costura y **se ignoran en silencio**: soma compone
|
|
|
`mergeProps(restProps, state.props)`, así que los atributos que posee el morfo
|
|
|
ganan a lo que le pases — que es el sentido del contrato. El override muerto
|
|
|
se retiró para que nadie crea que funciona.
|
|
|
|
|
|
**Queda como DECISIÓN DE MORFO** (necesita tu criterio): el `caption-button`
|
|
|
declara `aria-pressed` incondicionalmente. Dentro del float se comporta como
|
|
|
disparador de menú (`aria-haspopup` + `aria-expanded`) y anunciarse además
|
|
|
como alternancia pulsada es contradictorio. Opciones: (a) dejarlo —una parte
|
|
|
compuesta de dos formas no puede tener dos ARIA—, (b) hacer `aria-pressed`
|
|
|
condicional en el morfo, (c) declarar una parte aparte para el disparador de
|
|
|
lista. **No lo decido yo.**
|
|
|
|
|
|
**FIRMADA Y EJECUTADA (2026-08-12): la (b).** `aria-pressed` es condicional
|
|
|
en el morfo (`prop-falsy menuTrigger` — el patrón `fieldLabelled`,
|
|
|
`morfo.md` Step 4); el provider publica la fuente por parte y el
|
|
|
`CaptionFloat` pasa `menuTrigger`. Test visto fallar: compuesto como
|
|
|
disparador de lista el attr queda genuinamente AUSENTE; solo, sigue
|
|
|
anunciando su estado. `aria-keyshortcuts='c'` se queda — la tecla alterna
|
|
|
los subtítulos desde cualquier foco, así que el anuncio es verdad.
|
|
|
|
|
|
4. ✅ **`aria-keyshortcuts='c'`** deja de ser un problema de comportamiento en
|
|
|
cuanto el punto 1 está arreglado: la tecla ya no descarta nada. Queda sólo
|
|
|
como parte del punto 3 (qué se ANUNCIA sobre ese botón).
|
|
|
|
|
|
## Lo que NO he podido verificar (pendiente de tus ojos)
|
|
|
|
|
|
⚠️ **El teclado real sobre los tres floats.** El panel de navegador embebido dejó
|
|
|
de componer frames a media tarde (`document.visibilityState === 'hidden'`), y con
|
|
|
él **no llegan las teclas**: instrumenté un listener y una pulsación real de
|
|
|
Escape registró **cero** eventos. Llegué a creer que había un defecto —«Escape no
|
|
|
cierra el float dentro del player»— y **era falso**: lo roto era mi vía de
|
|
|
entrada, no el componente. Misma familia que las transiciones CSS congeladas.
|
|
|
|
|
|
Verificado igualmente con eventos despachados: un `keydown` de Enter/Espacio abre
|
|
|
el menú, y el `click` que el navegador sintetiza detrás **no** toca los
|
|
|
subtítulos (el `preventDefault()` de la costura aguanta). Falta comprobar con
|
|
|
teclado de verdad: abrir con Enter desde cerrado, moverse con flechas, y cerrar
|
|
|
con Escape.
|
|
|
|
|
|
⚠️ **Otra sesión commitea en esta misma rama** (hilo de dirección): verificar
|
|
|
`HEAD` antes de dar nada por perdido, y `git reset -q` + `add` sólo lo propio.
|
|
|
|
|
|
El siguiente ítem de CÓDIGO del componente no es de este hilo sino del handoff
|
|
|
hermano: **`settings-button`**, lo único que separa al morfo de 25/25 — y su
|
|
|
alcance ENCOGIÓ: la velocidad ya tiene su lista, así que al menú de ajustes sólo
|
|
|
le quedan calidad y pista.
|
|
|
Ver [`CONTINUE-player-rtl.md`](./CONTINUE-player-rtl.md) §0.
|
|
|
|
|
|
## Trampas de este repo (verificadas a base de perder tiempo)
|
|
|
|
|
|
- **NO arrancar un dev server si el del usuario está vivo** (`vite dev --force`
|
|
|
reescribe `deps` → dos runtimes → `lifecycle_outside_component`). Acoplarse al
|
|
|
puerto 5173. Recuperación: parar node → borrar `node_modules/.vite/deps` →
|
|
|
`npm run dev` → Ctrl+Shift+R.
|
|
|
- **El panel del navegador no pinta** (viewport 0×0); `javascript_tool` sí — con
|
|
|
eso se midieron los contextos. Matiz 2026-08-01: con el panel oculto las
|
|
|
CSSTransitions quedan `running` CONGELADAS → `getComputedStyle` devuelve el
|
|
|
valor intermedio del from, no el target de la cascada. Para verificar estilo,
|
|
|
leer las custom props (`getPropertyValue`), no los valores usados.
|
|
|
- **MUTEAR antes de reproducir, y PARAR al terminar.** Medir el escenario mixto
|
|
|
y los earcons exige reproducir de verdad, pero el Chrome que suena es el DEL
|
|
|
USUARIO: dejar una pista de 6 minutos sonando a volumen 1 mientras se hacen
|
|
|
otras cosas es una molestia real, y pasó (2026-08-04). `el.muted = true` basta
|
|
|
para casi todo lo que se mide —contextos, stamps, envolventes, geometría—
|
|
|
porque nada de eso depende de que se oiga; el volumen sólo hace falta para la
|
|
|
escucha, que es del usuario. Y al acabar cada medición, `el.pause()`.
|
|
|
- **Un guard que no has visto fallar no vale** — quitar el cableado y ver el rojo.
|
|
|
- **Una medición no es un diagnóstico** — README → CANON §8 → book-deviations →
|
|
|
código, antes de acusar a una pieza.
|
|
|
- **Filtrar `npm run check` con cuidado**: rutas con `\\` doble; un patrón con `/`
|
|
|
no casa y da falsos «0 propios».
|
|
|
- **El árbol está compartido con otras sesiones** — `git reset -q` + add solo lo
|
|
|
propio, en un bloque atómico. `contracts.test.ts` viene modificado de otra
|
|
|
sesión: **no tocarlo en E1** (su fila `sound` no pinea métodos y sigue verdadera).
|
|
|
|
|
|
## Ajenos y preexistentes detectados (NO tocados)
|
|
|
|
|
|
- `contracts.test.ts` 3 rojos ajenos: `aura`, `menubar`, `radio-group`.
|
|
|
- `docs:check` 1 error ajeno: `eidos/components/callout/README.md:23` («8 roles», son 9) — **ya no aparece el 2026-08-01 (docs:check 0)**; alguien lo arregló.
|
|
|
- `temas/sema/_lib/audition.ts`: 2 errores de tipos preexistentes.
|
|
|
- **`lint.test.ts` 1 rojo: `audio-player` sin morfo** — deuda de NUESTRO F5 del
|
|
|
player (`c4d64ba98`): es la capa de variantes GATED sobre el morfo del
|
|
|
media-player (su css engancha solo `data-media-player-*` + `data-live`), no
|
|
|
una violación morfo-first, pero el test no tiene categoría para «variant
|
|
|
layer sobre morfo hermano». Decisión pendiente (¿nueva categoría con
|
|
|
comentario vs KNOWN_MISSING_MORFO?) — es un guard compartido, no se tocó.
|
|
|
- Demo del slider: `sliderMorfo.parts[2].keyboard` roto por el desplazamiento
|
|
|
de índices al nacer SecondaryRange (G-2) — 1 error de `check` preexistente;
|
|
|
chip de tarea lanzado con el fix (seleccionar la part por kebab, patrón de la
|
|
|
demo del waveform).
|
|
|
- Prettier: `component-visual-attrs.test.ts`, `lib/recipes/base.ts` y
|
|
|
`scripts/eidos-lint-all.ts` ya fallaban `--check` EN HEAD (¿CRLF de otra
|
|
|
sesión?); mis líneas siguen el estilo del entorno y no se reformateó el
|
|
|
fichero entero (sería cascada).
|