Dos hilos que comparten ficheros, así que van juntos.
## Captions — el player pinta las cues
La parte estaba declarada en el morfo desde el principio y nunca implementada.
El render nativo no servía: con la pista en mode='showing' el navegador dibuja
su caja de cues en bottom:0 del <video> — la misma franja de 49px que la barra
de controles — y contra el z-index:4 de la barra al 82% de opacidad llegaba el
18% del texto. ::cue no puede moverlo (la spec no admite propiedades de
posición ahí).
Ahora la pista se mantiene en 'hidden': la spec deja la pista activa —cues
parseadas, activeCues mantenida, cuechange disparándose— y el navegador no
pinta nada, así que el texto se monta en la parte Captions, por encima de la
barra. Es el movimiento de Plyr, y por el mismo motivo.
Tres trampas de la especificación, encaradas por escrito:
- Pasar una pista a 'disabled' desactiva sus cues SIN disparar cuechange. Nadie
avisa de que hay que borrar, así que se limpia a mano — si no, el último
subtítulo se queda congelado sobre el vídeo.
- Los eventos de cue no corren mientras está puesto el show-poster flag, así
que el primer pintado se siembra leyendo activeCues.
- cues/activeCues son null en 'disabled'.
De paso cierra un defecto latente: captionsOn era un espejo de una sola
dirección que nunca releía track.mode, así que si el SO activaba subtítulos
salían cues mientras el botón decía que estaban apagados. Ahora se DERIVA de
los modos reales, y una pista que el navegador encienda se adopta (degradada a
'hidden', para que no pinte encima de la nuestra).
Decisiones declaradas: el marcado inline se conserva (se monta el
DocumentFragment de getCueAsHTML como nodos, nunca por innerHTML — el sumidero
de XSS que abre Plyr al serializarlo de vuelta a cadena); la metadata de
posición (line/position/align/vertical/snapToLines/regiones) se DESCARTA. Esto
no es una implementación de WebVTT y el README lo dice.
## Dirección — el player estaba partido por la mitad
Con dir="rtl" por prop y sin dir ambiental, el cromo se disponía en LTR y sus
dos Sliders en RTL: el wrapper pasaba la prop cruda y sólo la reenviaba a los
hijos, sin resolver la cadena ni estampar. En la demo estaba enmascarado porque
el stage estampa dir además de pasar la prop.
Canonizado con el patrón de CONTINUE-direction.md §9.16: activeDir en el
wrapper, resolvedDir en el provider, dir crudo + data-dir resuelto en los
props. El censo del §9.15 no lo tenía — era un cuarto caso que su búsqueda no
cubre (acepta dir y no lo escribe en ningún sitio).
Y los glifos direccionales no se volteaban: en RTL el transporte se leía
▶▶ ▶ ◀◀, con las dos flechas exteriores apuntando HACIA DENTRO — el síntoma que
carousel.css ya documentaba. Se voltean con scale:-1 1 (no rotate:180deg: sólo
coinciden en glifos verticalmente simétricos, y rotar el PiP le llevaría el
recuadro a la esquina de arriba), anclado a data-dir, el atributo propio del
componente. Alcance elegido: seek, play, volumen y PiP; captions y fullscreen
NO. El teclado pasa a resolver por getDirectionalKeys, así que las flechas
concuerdan con el glifo y con el Slider embebido.
## Verificación
check 0 errores propios · vitest del player 14/14, con los tres guards nuevos
vistos en ROJO por mutación · eidos-lint invalid 0 (morfo-backed 25 → 29) ·
morfo:check sin fallos del player · component:audit PASS · smoke PASS ·
docs:check 0 · prettier limpio en lo propio. A ojo en Chrome real: claro y
oscuro, LTR y RTL, y las dos regresiones (el espejo del SO y el subtítulo
congelado).
Nota: el README de soma se lleva una línea ajena — la fila `dir` de la tabla de
props, del barrido de otra sesión, que describe justamente esta cadena.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>