fix(pip): properly restore canvas from mini-player on replay page return

§16.13: Picture-in-Picture replay mini-player

When navigating back to a replay page where PIP was active, the
restoration logic was creating duplicate canvas elements (the
placeholder from the new DOM and the restored canvas from PIP).

Changes:
- Remove placeholder canvas before inserting restored PIP canvas
- Set 'replay-canvas' ID on restored canvas for TheaterMode and other consumers
- Use consistent 'actualCanvas' variable throughout initialization

The full PIP flow now works:
1. User starts replay on /watch/replay/:id
2. Clicks nav link → canvas reparents to floating mini-player
3. Playback continues uninterrupted
4. Click "return" → canvas reparents back to inline wrapper
5. Replay resumes at same tick

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-04-22 23:21:37 -04:00
parent be66af1ad5
commit 59fb673edb

View file

@ -565,19 +565,24 @@ function initReplayViewer(ReplayViewerClass: any, initialUrl?: string): void {
const pipMatch = getPipMatchId();
const canvasWrapper = document.querySelector('.canvas-wrapper') as HTMLElement;
let viewer: any;
let actualCanvas = canvas; // May be replaced by restored PIP canvas
if (pipMatch && canvasWrapper && (initialUrl?.includes(pipMatch) || !initialUrl)) {
const restored = restorePip();
if (restored) {
// Move canvas back into the inline wrapper
// Remove the placeholder canvas from DOM (PIP canvas has the real content)
canvas.remove();
// Move the PIP canvas back into the wrapper
canvasWrapper.insertBefore(restored.canvas, canvasWrapper.firstChild);
restored.canvas.style.display = 'block';
viewer = new ReplayViewerClass(restored.canvas, { cellSize: 10 });
restored.canvas.id = 'replay-canvas'; // Ensure ID is set for TheaterMode and other consumers
actualCanvas = restored.canvas;
viewer = new ReplayViewerClass(actualCanvas, { cellSize: 10 });
} else {
viewer = new ReplayViewerClass(canvas, { cellSize: 10 });
viewer = new ReplayViewerClass(actualCanvas, { cellSize: 10 });
}
} else {
viewer = new ReplayViewerClass(canvas, { cellSize: 10 });
viewer = new ReplayViewerClass(actualCanvas, { cellSize: 10 });
}
let criticalMoments: Array<{turn: number; delta: number; description: string}> = [];
@ -587,7 +592,7 @@ function initReplayViewer(ReplayViewerClass: any, initialUrl?: string): void {
// Theater mode
const theaterBtn = document.getElementById('theater-btn') as HTMLButtonElement;
const theater = new TheaterMode(canvas, {
const theater = new TheaterMode(actualCanvas, {
getScoreText: () => {
const replay = viewer.getReplay() as Replay | null;
if (!replay) return '';