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

52 KiB

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 ✅ CERRADA (histórica). Su §16.1 («la voz es del art») queda REVOCADA por el rediseño
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 🗄️ ARCHIVO (hechos + análisis que el v2 referencia)
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-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. 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 §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: 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.

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 — ✅ card aprobada el 2026-08-04 («la variante card la veo bien»). Quedan row e inline; la matriz dice que están sanas, pero la medición no juzga estética.
  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.
  4. El rojo de audio-player en lint.test.ts — guard compartido, decisión pendiente entre categoría nueva o KNOWN_MISSING_MORFO.

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.

⚠️ 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 §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).

Powered by TurnKey Linux.