| D-AP.5 (MediaSession, riesgo singleton) | **Del servicio** (D-SR.6 firmada): el player pasa `metadata` y no toca `navigator.mediaSession`. El riesgo de dos players pisándose lo arbitra el servicio (fuente activa + promoción) |
| D-AP.11 (¿el player consume `$sound`?) | **Disuelta** (D-SR.4 firmada): el transporte ES el handle del servicio (`uix.sound.media(el)`, forma exacta del puerto `MediaProvider` — drift-guard ya en verde). `attach()` existe y NO se usa en v1 |
| D-AP.7 (silencio sema en audio, ¿quién restaura el master?) | El dueño existe: **`duckUiWhileContent`** (bus `ui` con retención refcount). Queda UNA decisión de alcance → D-AP2.7 |
| §7.6 invariantes (prefs vs volumen de la obra; silencio alcanza descendientes) | **Estructurales**: el volumen de la obra es de la fuente/`content`; el duck del bus `ui` cubre TODOS los earcons (descendientes incluidos) sin reglas de cascada |
1.**`registerMedia` cambia su default**: `createProvider ?? (el => this.soma.uix?.sound ? this.soma.uix.sound.media(el, { metadata }) : nativeMediaProvider(el))` — el port `createProvider` SE CONSERVA (hls.js/dash siguen enchufables); sin `uix` degrada al adaptador local.
2.**El adaptador nativo de soma SE RETIRA** al completar la migración (sin shim, doctrina de la casa) y con él el drift-guard transicional (`media-provider-drift.test.ts`) — su trabajo habrá terminado. Un adaptador custom sobre elemento delega en `uix.sound.media(el)`; un embed sin elemento (YouTube) queda **sin ciudadanía** (limitación declarada en README).
3.**Prop nueva `metadata`** (`SoundMediaMetadata`) en el Provider — la identidad que Artwork/Title/Artist pintan es la MISMA que se proyecta al SO.
4.**G-5 aquí es cirugía concreta** (leído el provider): mover `trigger('commit-toggle-play')` de `togglePlay()` al evento `play`/`pause` de `applySnapshot` (el patrón que `commit-complete` ya usa con `ended`); ídem `toggleMute` → `volumechange`.
5. Los **pendientes del hilo del servicio** aterrizan aquí: medición Chrome del escenario mixto (earcon + media, 1 contexto) y prueba de teclas de medios — F7.
| **D-AP2.4** | Waveform | = v1 D-AP.4(a): componente canónico, iniciativa hermana POST-v1 (`peaks` de la app; `$libs/plots`) |
| **D-AP2.5** | Identidad → SO | **Nueva forma de D-AP.5**: prop `metadata` reenviada a `sound.media(el, { metadata })`; opt-in como siempre; el player NUNCA toca MediaSession |
| **D-AP2.6** | Cola / playlist | = v1 D-AP.6(b): fuera (blocks/app; `next-features.md`) |
| **D-AP2.7 v2** — REVISADA Y FIRMADA 2026-07-31 («lo firmo») | Semántica de los botones del player | **El transporte del player es SILENCIOSO POR DEFECTO, por definición** (D.7: su output ES audio; un tick de UI compite con la obra). Pack con el patrón D.5 (resta exacta del gain de familia — las primitivas del intent intactas, `threat`/`fulfill` afloran): `commit-toggle-play`/`-mute`/`commit-set-rate` en sus botones + **reglas de descendientes** (H-10) para el `contact-activate` de los Buttons compuestos (tuning nuevo `contact.silent`, −0.25) y el `commit-set` de los Sliders compuestos (scrubber y volumen). **Intactos**: `commit-fail` (risk debe oírse) y `commit-complete` (informa de la obra; revisitar con escucha real). **AMPLIADA por escucha real (mismo día, «los botones ya no… pero los sliders sí»)**: `handle-pick`/`handle-drag` de los Sliders compuestos → **silencio por CANAL** (`channels: ['haptic']`, reglas de descendientes de mayor especificidad) — ambos son ESTRUCTURALES sin intent (el libro concentra la evaluación en el drop, ya cubierto por la resta de `commit-set`), así que tirar su canal sonoro no borra carga evaluativa; el resolver del drag posee las primitivas del sonido, pero una señal con el canal inactivo no suena. Mi nota anterior («no callable desde el pack») era conservadora de más — corregida. Reactivación de la voz = capa de app (cascada 5b), sin prop nueva. Complementario con `duckUiWhileContent` (el pack calla AL PLAYER; el duck atenúa al RESTO de la app). Háptica se queda (no compite con la obra). Verificado: selectores 100% del builder tipado (solo el combinador es literal) · vocabulario limpio · sema 192/192 · check 0 propios |
| **D-AP2.8** | Capítulos + transcripción | = v1 (b): v2 con disposición; capítulos dependen de G-2 |
| **D-AP2.9** | Responsive | = v1 (a): `ResponsiveProp` en v1; container queries evaluadas en F6 |
| **D-AP2.10** | Alcance v1 | Paridad (§2 v1) + superaciones vía servicio (metadata→SO · duck) · **sub-decisión H-9**: `sustain-loading` (evento declarado JAMÁS emitido) — **cablearlo** (2 líneas: `waiting`→emit, clear en `canplay`; recomendación, el spinner ya existe y el morfo lo promete) o retirarlo del morfo |
| **D-AP2.11** | Providers custom | **Nueva**: el port `createProvider` se conserva; adaptadores sobre elemento delegan en el servicio; embeds sin elemento = sin ciudadanía (declarado). El adaptador nativo local se retira |
| **D-AP2.12** | Segmentos (`clip`) | = v1 D-AP.12(a): v2, diseño CONGELADO en v1 §7.5; (b) sigue en pie solo si hay caso de uso hoy |
| **D-AP2.13** | Scrubber duplica `commit-set` | = v1 D-AP.13(b): retirar `commit-set-time` del morfo y delegar en el Slider embebido (el provider ya practica el patrón en `setVolume`) |
| **F0 — Firma** | Presentar este gate UNA vez | Tabla firmada en el chat; plan actualizado |
| **F1 — Gaps de framework** ✅ **COMPLETA 2026-07-31 (G-4 · G-3 · G-1 · G-2)** — G-2: parte `SecondaryRange` en el morfo + `SliderSecondaryRangeProvider` (espejo de Range: inline `left/width%` desde el inicio, RTL/vertical; 0% sin valor) + prop `secondaryValue?` + `Slider.SecondaryRange` en soma/eidos + regla CSS bajo la de range + token `--slider-secondary-bg` (28% del primario, entre pista y range) regenerado. ⚠️ Lección: claves nuevas de `ActiveProps` OPCIONALES (`?`) — requeridas rompieron a los 7 consumidores de `SliderProvider.create` (color/time/time-range-picker), corregido. Verificado: slider 11/11 · `component:audit --only slider`**PASS** · eidos-lint morfo-backed · `check` 74 con **0 propios** (incl. consumidores) | Lecturas hechas: `slider-provider` completo · regla `[data-slider-range]` de la recipe · wrappers soma/eidos · **G-4 ✅** selector del pack a `play-button` (declarado: play/pause pasa a sonar con el tap sutil del pack, antes regla muerta → family default; `morfo:vocabulary` limpio, sema 192/192) · **G-3 ✅**`formatDuration` en `$libs/days/format.ts` + tests (19/19; el consumo del provider llega en F3; dígitos no localizados aquí — disposición: `$format` encima) · **G-1 ✅**`aria-valuetext` (morfo Thumb `propRef('valueText')` condicional + prop `valueText?: (value, index) => string` en soma/eidos + source por-thumb; slider 10/10) · **G-2 (bloqueante, siguiente)** — DISEÑO listo tras las lecturas: espejo EXACTO de `Range` (estilo inline del provider: `left/width%` desde min hasta `secondaryValue`, RTL/vertical como `rangeStyle`) · prop `secondaryValue?: number` (unidades del dominio; extensión a par `[start,end]` DIFERIDA a clip v2, no especular) · parte morfo `SecondaryRange` optional (data como `Range`) · provider `SliderSecondaryRangeProvider` + componente soma + export `Slider.SecondaryRange` + wrapper eidos + regla `[data-slider-secondary-range]` (bajo la de range, `background: var(--slider-secondary-bg)`) + token en la sección slider de `lib/recipes/base.ts` · tests (estilo %, RTL, ausencia sin prop) | `check`: **80 errores repo (73 baseline + 7 AJENOS nuevos de la rama viva), 0 propios** · vitest slider 10/10 · sema 192/192 · days 19/19 · `morfo:vocabulary` solo warn ajeno (`cropper`) · queda al cerrar G-2: `component:audit --only slider` PASS |
| **F2 — Morfo** ✅ **COMPLETA 2026-07-31** | 6 partes audio-only `optional` (artwork sin aria-hidden — alt real del consumidor · artist · identity · transport · rate-button con `data-rate` · live-indicator decorativo) · `commit-set-rate` (target rate-button; **el commit cabalga `ratechange`** — nace con el patrón G-5, cada fuente señala una vez al aterrizar el resultado) · **`commit-set-time` RETIRADO** (morfo + pin del test + trigger de `seek()`, rationale D-AP2.13 escrito en ambos) · **`sustain-loading` CABLEADO** (`waiting`→trigger; stateBound limpia con `data-buffering`) · teclado declarado E implementado (`j`/`l` alias seek · `<`/`>` ciclan `RATE_PRESETS` [0.5…2] desde el más cercano · `0-9` salto a % solo con duration finita) · texts `rate`/`live` | ✅ morfo 8/8 + player soma 18/18 · `morfo:vocabulary` limpio (warn `cropper` ajeno) · `check` 74 = baseline, **0 propios** · ⚠️ **`translations:check` BLOQUEADO POR AJENO**: el script muere evaluando `langs/components/palabras.ts` (catálogo con `url:` https — su stripper de comentarios cercena el `//`; palabras es territorio excluido y no se toca). Mis claves siguen el patrón idlangref de las 18 hermanas; correr el verify cuando el ajeno se arregle |
| **F3 — Soma: LA MIGRACIÓN** ✅ **COMPLETA 2026-07-31** | Default del port → `uix.sound.media(el, { metadata })` (sin `uix` → sin engine; el port es la costura) · prop `metadata` en opts/types/wrapper (Active OPCIONAL) · **`nativeMediaProvider` RETIRADO** (media-provider.ts = contrato puro del puerto; exports/README al día; drift-guard borrado) · **G-5**: commits de play/mute cabalgan `play`/`pause` (saltando el pause de `ended`) y `volumechange` con transición de muted · `formatTime` → `formatDuration` · **6 sub-providers + 6 wrappers + exports** (artwork·artist·identity·transport·rate-button con `cycleRate(1)`+`data-rate`+label `rate`·live-indicator con `data-live` soma-owned) · tablas del README (props con `metadata`, 6 partes) | ✅ player 10/10 + morfo 8/8 (pin nuevo: default con motor REAL refleja `ratechange`) · prettier limpio · `check` 74 = baseline viva, **0 propios** |
| **F4 — Sema** ✅ **COMPLETA 2026-07-31** | G-4 cayó en F1 · el docblock del pack escribe la doctrina D-AP2.7 (el silencio lo posee el SERVICIO: receta `defineEngineSound({duckUiWhileContent})`; la resta-de-gain queda diseñada sin cablear a propósito) · `expression: 'pack'` intacta | `morfo:vocabulary` limpio (S11d) · stamp en navegador → F7 (este entorno no alcanza el server vivo) |
| **F5 — Eidos** ✅ **COMPLETA 2026-07-31** | **Buffer real** (G-2 consumido: `secondaryValue` + `Slider.SecondaryRange`; `.mp-buffered`/`--media-buffered`/`--_mp-rail` retirados; look intacto 42%→`--slider-secondary-bg`) · **6 wrappers eidos** (Artwork = AspectRatio+Image del catálogo vía `child`; RateButton = Button ghost con `{data-rate}×`; resto passthrough) · **`AudioLayout`** (espejo de DefaultControls: Identity[Artwork·Title·Artist·Live] + Transport[seek·play·seek] + Time·TimeSlider·Time + Rate·Mute·Volume) · **raíz competidora `<AudioPlayer>`** en `eidos/components/audio-player/` (Toast/Toaster; `metadata` DERIVADA de title/artist/artwork salvo override — la identidad que ves ES la del lock screen) · **recipe de 4 variantes** scoped a `[data-variant]` (componer a mano queda sin opinar) + tokens `--audio-player-*` (gap·artwork-radius·artwork-size-row/bar) regenerados | ✅ `recipe-css-contract` + `component-api-contract`**37/37** · `eidos-lint media-player` 0 inválidos (audio-player.css = recipe de composición sobre el morfo del player; lint per-component no la cubre — gap del tooling declarado, F7) · `check` 78 = baseline+4 AJENOS nuevos (`arts/connection`, otra sesión), **0 propios** · Notas: badge `LIVE` hardcodeado en el wrapper (el text `live` del morfo queda para cablear con langs en F7) · los internos `--_mp-*` los comparte la recipe hermana del MISMO morfo (decisión declarada; promover a públicos si se quiere estricto) |
| Doble transporte durante F3 | Swap atómico en el mismo pase: default nuevo + retirada del adaptador + tests actualizados juntos |
| Consumidores de `nativeMediaProvider` | Solo la demo y el propio soma (verificar con grep al abrir F3); no hay consumidores externos |
| El morfo crece (ya es el mayor) | Partes nuevas `optional`; si pasa de ~700 líneas tras F2, reabrir D-AP2.1(b) con datos |
| `duckUiWhileContent` atenúa TODA la app | Es la lectura de la incongruencia (D.7) y es opt-in de composición; si la escucha pide precisión selectiva, la variante (b) de D-AP2.7 está diseñada (v1) y no bloquea |
| Los de v1 §11 no disueltos (bar sobre fondos, clip/timeupdate…) | Siguen escritos en v1; aplican a sus fases |