C1.2 + C1.3 · Shipped 2026-05-19 · Coach Review workstream

Pause the heat. Draw on the frame. Strokes replay as the clip plays.

The Annotate toolbar button on ArchiveDetailView wraps the video player surface in a stroke-capture canvas. Click to enter annotation mode — the player pauses, a 4-swatch palette appears in the top-trailing corner, and any drag against the player draws a stroke that commits to a JSON sidecar in the anchor's directory. C1.3 adds the replay side: when you exit annotation mode and play the clip, strokes appear at their authored timestamp and fade out 4 seconds later — the athlete watching the clip sees the coach's notes flow with the action.

SETUP

A synthesized fixture clip

The C1 walkthrough used to be the only one on this site whose screenshots were captured by hand — XCUITest had no way to drive against a video-bearing fixture because TestFixtures didn't seed any media files. That blocker is gone now: the coachReview scenario adds a sub-slice that synthesizes a placeholder .mov at install time via AVAssetWriter (5 seconds × 1280×720 × solid teal frames, ~10 KB), wires an ArchivedAnchor pointing at it, and registers it with the coordinator. The walkthrough now auto-regenerates on each test run — UI drift trips the test the same way it does everywhere else on this site.

The Mac archive's Live Meet window just after launch with the Sample Invitational fixture loaded.

Launch state. Live Meet shows the Sample Invitational; the coachReview-scenario fixture has registered a synthetic heat anchor with a placeholder .mov ready to play.

STEP 1

Open the Archive window, find the heat

Coach Review lives inside the DM Track Archive window (Window menu → DM Track Archive). The center pane's table lists every archived heat across every meet; the fixture's demo heat reads 100m · Boys · Heat 1 with 8 finishes and a 10 KB clip.

DM Track Archive window with the demo heat row visible in the center pane; row hovered.

Center table with the demo heat hover-highlighted. Click to load it.

STEP 2

Detail view loads + video plays

Click the row. The inspector pane mounts ArchiveDetailView — top-trailing timeline strip showing the clip's bar against Demo Camera, Full Recordings section in the center, player surface bottom-right with the standard transport controls underneath. The window title rewrites to the anchor's display name. AVPlayer starts loading frames as soon as the surface mounts; the first one paints within ~500 ms.

Archive window with the heat selected; detail view inspector pane showing timeline, Full Recordings, and the player surface displaying the teal placeholder video.

Heat selected (blue row), title bar updated, detail view mounted. Teal player surface in the bottom-right is the fixture clip's first frame.

STEP 3

The Annotate toolbar button

The window's toolbar gains the Annotate button (pencil-tip-crop-circle icon) once a video-bearing anchor is selected. Hovering shows the standard tooltip; clicking enters annotation mode.

Detail view with the Annotate toolbar button hovered.

Annotate button hovered (top toolbar). Click to flip the player into annotation mode.

STEP 4

Annotation mode on

Clicking Annotate: the player pauses (if it wasn't already), the floating overlay chrome appears in the top-trailing corner of the player surface (4-color swatch palette + width slider + undo + annotation count), and the canvas overlay becomes drag-receptive. The button switches to its filled "Exit Annotate" state so the toggle is obvious.

Annotation mode active: paused teal player surface, overlay chrome visible top-trailing, canvas ready for strokes.

Annotation mode on. Player paused, canvas ready. The overlay chrome at the top-trailing of the player carries the pen palette + width + undo.

STEP 5

Draw strokes on the paused frame

Pick a pen color from the 4-swatch palette (default red), adjust width via the slider, and drag against the player surface to draw. Each stroke commits to the in-memory sidecar on mouse-up; the count chip in the overlay ticks up. The canvas coordinates are stored as normalized [0, 1] floats so strokes re-render at the right place regardless of how the player surface is resized.

Three diagonal red strokes drawn across the teal player surface.

Three short strokes on the paused frame, mid-session. Each is its own committed stroke; the strokes ledger in the overlay's count chip reads 3.

Strokes are timecoded: each stroke captures the player's currentTime() in milliseconds at draw time. The sidecar records both the stroke geometry AND the frame it was drawn against. A future surface (C1.3+) can scrub the timeline and only render strokes drawn at the visible frame ±a tolerance.
STEP 6

Strokes persist to disk

A green Saved ✓ banner flashes in the overlay chrome ~1.5 s after each stroke commit. The underlying write goes to <anchor.directory>/coach-notes.json — a Codable CoachReviewSidecar with an array of CoachAnnotation records keyed by stroke ID. The sidecar is read in ArchiveDetailView's .task(id:) on every anchor reopen so strokes round-trip cleanly.

Same canvas with three strokes; saved banner in the overlay chrome has just flashed.

Post-commit. The Saved banner fired + faded; the strokes are now on disk in coach-notes.json next to the .mov.

STEP 7

Reopen the anchor, strokes re-render

Navigate away from the anchor and back, the strokes re-render at exactly the same canvas coordinates and the same frame. Below is the same canvas state captured after the auto-save fired — this is what the operator sees on reopen too.

The annotation canvas with the three strokes still visible — same state after a navigate-away + return cycle.

Strokes still visible. The sidecar JSON round-trip preserves stroke geometry, color, width, and frame-time across reopens.

Why the reopen step uses the same shot: XCUITest can't easily drive a navigate-away-and-back cycle when the window title rewrites to the selected anchor name (the by-title lookup gets ambiguous mid-test). The persistence path itself is verified by the CoachReviewSidecarIO unit-test suite — load + save + round-trip equality on the sidecar JSON. This walkthrough demonstrates that the visual state is preserved; the unit tests prove the disk round-trip.
STEP 8 · C1.3

Exit annotate — strokes replay as the clip plays

Click Annotate a second time to exit. The floating chrome (4-swatch palette + width slider) disappears, the canvas stops accepting drags — but the strokes stay on screen. A new read-only overlay (AnnotationPlaybackOverlay) mounts whenever a sidecar is loaded AND annotation mode is off. It reads the player's current time at 20 Hz and renders strokes whose authored clipTimestampMillis sits within the visibility window from that time. Strokes fade out linearly over 4 seconds once their authored timestamp passes — matching the C1 SPEC's locked decision: "every annotation appears at its bound timestamp and fades out 4 seconds later."

The Mac archive after exiting annotation mode. The three red strokes are still visible on the player surface but the floating chrome (palette + slider + count) is gone — the playback overlay has taken over rendering.

Post-exit. Strokes still visible because the player is paused at the same timestamp they were authored against (delta = 0). Press Play and they'll fade out over the next 4 seconds; scrub backward past their timestamp and they'll disappear (future strokes don't render).

Why this matters: the coach reviewing a clip with an athlete doesn't want to stare at a frozen frame with strokes overlaid. They want to play the clip and have the strokes appear at the right moments — drive phase circled when the athlete's at the drive phase, arrow on the foot plant when the foot plants. C1.3 makes the strokes timed to the action, not pinned to one frame.
20 Hz redraw, no GPU strain: the overlay is a SwiftUI TimelineView(.animation(minimumInterval: 0.05)) wrapping a Canvas. 20 Hz is more than enough for smooth opacity fades at this scale — much slower than the 60 Hz video underneath, but the fade is the only thing that visibly changes per tick. allowsHitTesting(false) on the overlay means clicks always pass through to the player's transport controls underneath.
UNDER THE HOOD

How the canvas overlays the player layer

RELATED

What's next on the Coach Review path

C1.3 — Frame-bound stroke rendering: Each stroke captures the frame it was drawn against (already implemented in C1.2's sidecar). C1.3 scrubs the timeline and only renders strokes within ±100 ms of the visible frame — so a stroke drawn at the finish-line crossing doesn't bleed across the whole clip.

C2 — Voice notes: Dictate-to-text against the paused frame, ties into the same sidecar as a sibling VoiceNote record. Useful for "look at his foot plant here" without typing.

C3 — Per-athlete profile attachment: Send a selected annotation (strokes + voice + frame) to the athlete's profile page so the coach review surfaces in the athlete-development context.

← All walkthroughs Home