From 80733f673e53090a3b2022bf1642d2ef4af738fe Mon Sep 17 00:00:00 2001 From: jedarden Date: Tue, 21 Apr 2026 18:07:44 -0400 Subject: [PATCH] =?UTF-8?q?feat(replay):=20wire=20AnnotationOverlay=20to?= =?UTF-8?q?=20EventTimeline=20with=20annotation=20badges=20per=20=C2=A716.?= =?UTF-8?q?8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sync annotations to both the canvas renderer (spatial markers) and the event timeline (colored badge markers) so user feedback appears in both the replay canvas and the timeline ribbon. Co-Authored-By: Claude Opus 4.7 --- web/src/pages/replay.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/web/src/pages/replay.ts b/web/src/pages/replay.ts index c0a38d4..6159bea 100644 --- a/web/src/pages/replay.ts +++ b/web/src/pages/replay.ts @@ -652,15 +652,42 @@ function initReplayViewer(ReplayViewerClass: any, initialUrl?: string): void { // ── Annotation overlay integration (§16.8) ────────────────────────────────────── let annotationOverlay: AnnotationOverlay | null = null; + let eventTimeline: EventTimeline | null = null; let allAnnotations: Annotation[] = []; let clickedGridPosition: Position | undefined; let canvasAnnotationHint: HTMLDivElement | null = null; + function syncAnnotationsToViewer(): void { + // Push annotations to the canvas renderer for marker drawing + viewer.setAnnotations(allAnnotations); + // Push annotations to the event timeline for badge rendering + eventTimeline?.setAnnotations(allAnnotations); + } + function initAnnotations(replay: Replay): void { const overlayContainer = document.getElementById('annotation-overlay-container'); const formContainer = document.getElementById('annotation-form-container'); if (!overlayContainer || !formContainer) return; + // Initialize EventTimeline (desktop) + const timelineContainer = document.getElementById('event-timeline-container'); + if (timelineContainer) { + eventTimeline = new EventTimeline(timelineContainer, { + onTurnClick: (turn: number) => { + viewer.setTurn(turn); + updateUI(); + updateEventLog(); + }, + }); + // Extract events from replay turns and feed to timeline + const timelineTurns = replay.turns.map((t: any, i: number) => ({ + turn: i, + events: t.events ?? [], + })); + eventTimeline.setEvents(timelineTurns); + timelineContainer.style.display = ''; + } + annotationOverlay = new AnnotationOverlay(overlayContainer, { onTurnClick: (turn: number) => { viewer.setTurn(turn); @@ -675,9 +702,11 @@ function initReplayViewer(ReplayViewerClass: any, initialUrl?: string): void { fetchFeedback(replay.match_id).then(remote => { allAnnotations = [...remote, ...local]; annotationOverlay?.loadAnnotations(replay.match_id, allAnnotations, replay.turns.length); + syncAnnotationsToViewer(); }).catch(() => { allAnnotations = local; annotationOverlay?.loadAnnotations(replay.match_id, allAnnotations, replay.turns.length); + syncAnnotationsToViewer(); }); // Create the annotation form @@ -688,6 +717,7 @@ function initReplayViewer(ReplayViewerClass: any, initialUrl?: string): void { const ann = e.detail as Annotation; allAnnotations.push(ann); annotationOverlay?.addAnnotation(ann); + syncAnnotationsToViewer(); clickedGridPosition = undefined; if (canvasAnnotationHint) canvasAnnotationHint.classList.remove('visible'); }) as EventListener); @@ -709,6 +739,9 @@ function initReplayViewer(ReplayViewerClass: any, initialUrl?: string): void { if (annotationOverlay) { annotationOverlay.setCurrentTurn(viewer.getTurn()); } + if (eventTimeline) { + eventTimeline.setCurrentTurn(viewer.getTurn()); + } } // Handle canvas clicks for spatial annotation position