docs(05): capture phase context
This commit is contained in:
112
.planning/phases/05-live-show-execution/05-CONTEXT.md
Normal file
112
.planning/phases/05-live-show-execution/05-CONTEXT.md
Normal 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*
|
||||||
68
.planning/phases/05-live-show-execution/05-DISCUSSION-LOG.md
Normal file
68
.planning/phases/05-live-show-execution/05-DISCUSSION-LOG.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Phase 5: Live Show Execution - Discussion Log
|
||||||
|
|
||||||
|
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||||
|
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
||||||
|
|
||||||
|
**Date:** 2026-04-07
|
||||||
|
**Phase:** 05-live-show-execution
|
||||||
|
**Areas discussed:** Cue Scheduler Location, Live Preview Panel
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cue Scheduler Location
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Server decides | Extend ws.py tick handler — server scans cues and calls UDPSender.send() | ✓ |
|
||||||
|
| Browser decides | Browser tracks blocks, sends fire_cue messages to server as relay | |
|
||||||
|
|
||||||
|
**User's choice:** Server decides
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Timing Window
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| 60ms look-ahead window | Fire if block starts within 60ms of current tick position — never miss a cue | ✓ |
|
||||||
|
| Exact match + tolerance | ±20ms window — more precise but may miss cues between ticks | |
|
||||||
|
|
||||||
|
**User's choice:** 60ms look-ahead window
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Live Preview Panel
|
||||||
|
|
||||||
|
### What to show
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Animation color + type per device | Solid strip per device showing active animation and color | ✓ |
|
||||||
|
| Per-LED simulation | Actual animated frame-by-frame rendering (complex, needs backend or JS reimplement) | |
|
||||||
|
|
||||||
|
**User's choice:** Animation color + type per device
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Panel placement
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Below timeline, above transport bar | Horizontal strip always visible during playback | ✓ |
|
||||||
|
| Sidebar panel | Right sidebar alongside devices — less vertical space | |
|
||||||
|
|
||||||
|
**User's choice:** Below timeline, above transport bar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Claude's Discretion
|
||||||
|
|
||||||
|
- Keyboard shortcuts (UI-03) — standard DAW conventions (Space, arrows, Ctrl+Z/S)
|
||||||
|
- Show save/load UX — auto-save already exists via CRUD; Ctrl+S as explicit save shortcut
|
||||||
|
- Preview panel height — compact, 40-60px, one row per device
|
||||||
|
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Per-LED frame simulation — v2
|
||||||
|
- Standalone fullscreen preview mode — out of scope
|
||||||
|
- Keyboard shortcut customization UI — out of scope for v1
|
||||||
Reference in New Issue
Block a user