docs(05): capture phase context

This commit is contained in:
Claude
2026-04-07 08:48:34 +00:00
parent 34077018a9
commit 4ce9759055
2 changed files with 180 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
# Phase 5: Live Show Execution - Context
**Gathered:** 2026-04-07
**Status:** Ready for planning
<domain>
## 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).
</domain>
<decisions>
## 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.
</decisions>
<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.py``encode_animation_cmd(animation, params)` + `ANIMATION_IDS` registry
- `lightsync/protocol/udp_sender.py``UDPSender.send(payload, ip, port)` — already wired in `main.py`
### Data models
- `lightsync/models/show.py``ShowModel`, `TrackModel`, `CueModel` (fields: `id`, `timestamp`, `duration`, `animation`, `params`)
- `lightsync/models/device.py``DeviceConfig` (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.js``TimelineCanvas`, `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.py``app.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>
<specifics>
## 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`.
</specifics>
<deferred>
## 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
</deferred>
---
*Phase: 05-live-show-execution*
*Context gathered: 2026-04-07*