Files
led2/.planning/phases/05-live-show-execution/05-CONTEXT.md
2026-04-07 08:48:34 +00:00

7.1 KiB

Phase 5: Live Show Execution - Context

Gathered: 2026-04-07 Status: Ready for planning

## Phase Boundary

A complete show runs end-to-end: audio plays, animation blocks fire UDP commands to LED devices at correct timestamps, a live preview panel shows per-device active animations in the browser, and shows persist across sessions. Keyboard shortcuts make all primary operations mouse-free. No AI sync (Phase 6), no firmware (Phase 7).

## Implementation Decisions

Cue Scheduler Architecture

  • D-01: Server-side scheduling — the server fires UDP commands, not the browser. On each WebSocket tick, the server scans the current show's cue list and dispatches UDPSender.send() for any block due to fire.
  • D-02: 60ms look-ahead window — if a block's timestamp is within 60ms ahead of the current tick position, fire it immediately. This prevents missed cues at 10Hz tick rate (max ~100ms tick gap). Blocks fire slightly early, never late.
  • D-03: Seek-safe reset — on seek events, the server resets the "already fired" cue pointer so no cues are double-fired or skipped after a seek.
  • D-04: The server needs the current show's cue list available per-connection. Wire this up by loading the active show from show_store in the WebSocket handler when a load message arrives (browser already sends this when audio loads).

Live Preview Panel

  • D-05: Animation color + type per device — show the active animation type and its color as a simple strip per device. No per-LED frame simulation; just which animation is active and in what color. Turns dark when no block is active on a device.
  • D-06: Panel placement: below the timeline canvas, above the transport bar — always visible during playback. Layout: TIMELINE → LIVE PREVIEW → TRANSPORT BAR. Matches the Phase 4 inspector strip placement approach.
  • D-07: Preview updates are driven by the same WebSocket tick loop — when the server fires a cue, it also broadcasts a preview_update message so the browser canvas reflects the new state.

Claude's Discretion

  • Keyboard shortcuts (UI-03): Standard DAW conventions: Space = play/pause, Left/Right arrow = seek ±5s, Ctrl+Z = undo, Ctrl+Shift+Z or Ctrl+Y = redo, Ctrl+S = explicit save. Planner decides exact key bindings but these are safe defaults.
  • Show save/load UX: All block mutations already auto-save via CRUD API (Phase 4). Add Ctrl+S as an explicit "save now" shortcut that calls the existing save endpoint. Show loading: use the existing show selector dropdown + a "load" button. No need to redesign.
  • Preview panel height: Compact — 40-60px total. One row per device, enough for a labeled color strip. Collapsible if many devices.

<canonical_refs>

Canonical References

Downstream agents MUST read these before planning or implementing.

Requirements

  • .planning/REQUIREMENTS.md — SHW-03 (live show execution), UI-03 (keyboard shortcuts), UI-04 (live preview panel)
  • .planning/ROADMAP.md §Phase 5 — Success criteria (5 items), plan breakdown (05-01 through 05-03)

Existing WebSocket protocol

  • lightsync/api/ws.py — Tick/beat WebSocket handler; cue scheduler extends this handler. Already handles tick, play, pause, seek, load message types.

UDP dispatch

  • lightsync/protocol/animation_cmd.pyencode_animation_cmd(animation, params) + ANIMATION_IDS registry
  • lightsync/protocol/udp_sender.pyUDPSender.send(payload, ip, port) — already wired in main.py

Data models

  • lightsync/models/show.pyShowModel, TrackModel, CueModel (fields: id, timestamp, duration, animation, params)
  • lightsync/models/device.pyDeviceConfig (fields: id, name, strip_type, led_count, target_ip, target_port)

Show persistence

  • lightsync/api/shows.py — Existing save/load REST API (GET/POST /api/shows, GET /api/shows/{id})
  • lightsync/api/timeline.py — Block CRUD endpoints (all mutations already auto-save via show_store.save())

Existing frontend

  • lightsync/frontend/app.js — WebSocket client, 10Hz tick loop, show/device state, existing event wiring
  • lightsync/frontend/timeline/timeline.jsTimelineCanvas, this.tracks, this.showId — preview panel reads same track data
  • lightsync/frontend/style.css — CSS variables; preview strip must match terminal aesthetic

Prior context

  • .planning/phases/01-foundation/01-CONTEXT.md — Visual aesthetic decisions (flat, sharp, cyan/teal, no rounded corners, no shadows)
  • .planning/phases/04-timeline-editor/04-CONTEXT.md — D-01/D-03: inspector strip placement pattern; preview panel follows same layout slot logic

</canonical_refs>

<code_context>

Existing Code Insights

Reusable Assets

  • ws.py ConnectionManager.broadcast() — already wired for multi-client broadcast; preview_update messages can use this
  • UDPSender (wired in main.py) — available as app.state.udp_sender or module-level ref; Phase 5 calls send() from the tick handler
  • show_store.load(show_id) — async, used in shows.py and timeline.py; ws.py can call this on load message
  • lightsync/frontend/timeline/timeline.js this.tracks — the live track data (including blocks) is already in the browser; preview can read timeline.tracks directly to know which blocks are active at audio.currentTime

Established Patterns

  • WebSocket message protocol: {"type": "...", ...} — extend with {"type": "preview_update", "device_id": "...", "animation": "chase", "color": [0, 255, 180]}
  • Server-side per-connection state pattern already used in ws.py (current_file, beats, last_beat_reported)
  • CSS: --accent (cyan), --bg (near-black), --border (dimmed cyan); panel borders 1px solid var(--border)
  • Panel strip pattern from Phase 4: <div id="block-inspector"> horizontal row — preview strip follows same structure

Integration Points

  • ws.py websocket_endpoint function — add cue scheduler loop inside the tick branch; needs show loaded per-connection
  • main.pyapp.state.udp_sender already initialized; access from ws.py via websocket.app.state.udp_sender
  • index.html layout — preview panel slot between .timeline-container and .transport-bar
  • app.js WebSocket onmessage handler — add preview_update case to update the preview canvas

</code_context>

## Specific Ideas
  • Terminal aesthetic carries forward: preview strips should look like terminal status bars — flat rectangles, color fill, device name label in monospace uppercase, no rounded corners.
  • The "lights dark" / "no active block" state should show the device label with a dim inactive indicator — consistent with the rest of the UI's use of --text-dim.
## Deferred Ideas
  • Per-LED frame simulation in the preview (see what the chase animation actually looks like moving) — v2 or Phase 6+
  • Preview panel as a standalone full-screen mode — out of scope
  • Keyboard shortcut customization UI — out of scope for v1

Phase: 05-live-show-execution Context gathered: 2026-04-07