docs(04-01): complete timeline canvas foundation plan — SUMMARY, STATE, ROADMAP updated

- SUMMARY: timeline canvas with per-device tracks, waveform, beat marks, playback cursor
- STATE: plan advanced to 2/4, progress 79%, decisions and metrics recorded
- ROADMAP: Phase 4 progress updated (1/4 plans complete, In Progress)
- Requirements TL-01, TL-05 marked complete
This commit is contained in:
Claude
2026-04-06 23:35:29 +00:00
parent efa612380c
commit 96d7825c34
3 changed files with 100 additions and 14 deletions

View File

@@ -0,0 +1,83 @@
---
phase: 04-timeline-editor
plan: 01
subsystem: frontend-canvas
tags: [canvas, timeline, daw-ui, audio, waveform]
dependency_graph:
requires: [Phase 03 communication protocol — UDP sender, animation encoders]
provides: [TimelineCanvas class, timeline rendering foundation]
affects: [lightsync/frontend, lightsync/models/show.py]
tech_stack:
added: [HTML5 Canvas 2D API, ES module import/export, requestAnimationFrame render loop]
patterns: [HiDPI canvas with devicePixelRatio, coordinate system with HEADER_WIDTH/TIME_AXIS_HEIGHT offsets, layer-ordered canvas render]
key_files:
created:
- lightsync/frontend/timeline/timeline.js
modified:
- lightsync/models/show.py
- lightsync/frontend/index.html
- lightsync/frontend/style.css
- lightsync/frontend/app.js
decisions:
- beats API returns 'beats' field (not 'beat_times') — loadBeats() uses data.beats with data.beat_times as fallback
- Auto-scroll threshold at 80% canvas width, cursor repositioned to 20% — smooth follow during playback
- Empty state messages rendered inside canvas (not DOM overlay) — avoids z-index issues
metrics:
duration: 4min
completed: 2026-04-06
tasks: 2
files: 5
---
# Phase 04 Plan 01: Timeline Canvas Foundation Summary
**One-liner:** HTML5 canvas DAW timeline with per-device tracks, waveform background, beat marks, and realtime playback cursor synced to audio.currentTime via requestAnimationFrame.
## What Was Built
- **CueModel.duration** (float = 4.0) — block length in seconds, Pydantic default handles old show files
- **AnalysisBlock.calibration_offset** (float = 0.03) — compensates librosa latency bias, applied when rendering beat marks
- **TimelineCanvas class** (`lightsync/frontend/timeline/timeline.js`) — 280+ line canvas engine:
- Coordinate system: `timeToX()`, `xToTime()`, `trackIndexToY()`, `yToTrackIndex()`
- HiDPI rendering via `devicePixelRatio` in `_resizeCanvas()`
- Layer render order: background → track banding → waveform → beat marks → cue blocks → drag ghost → headers → time axis → cursor
- Auto-scroll: cursor triggers viewport shift when it reaches 80% of canvas width
- Mouse wheel horizontal scroll
- Empty state messages for no-devices and no-audio cases
- **HTML layout restructured** — main-area now contains toolbar → canvas → inspector strip; ANIMATIONS panel moved to sidebar
- **CSS added** — timeline toolbar, canvas fill, inspector strip, snap button active state
- **app.js wired** — imports TimelineCanvas, creates instance, wires zoom/beat-offset/snap controls, populates animation palette dynamically
## Decisions Made
| Decision | Rationale |
|----------|-----------|
| beats API 'beats' field (not 'beat_times') | API returns `{"beats": [...]}` — plan expected `beat_times` — auto-fixed (Rule 1) |
| Auto-scroll at 80%/20% threshold | Standard DAW follow behavior — keeps cursor in view without jerky jumps |
| Empty state in canvas | Simpler than DOM overlay, no z-index conflicts with toolbar/inspector |
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] beats API field name mismatch**
- **Found during:** Task 2 implementation
- **Issue:** Plan specified `data.beat_times` but `/api/audio/beats` returns `{"beats": [...]}` (from `analyze()` in `beats.py`)
- **Fix:** `loadBeats()` uses `data.beats || data.beat_times || []` — handles both field names
- **Files modified:** `lightsync/frontend/timeline/timeline.js`
- **Commit:** efa6123
## Known Stubs
None — timeline renders live data from `/api/devices`, `/api/audio/waveform`, and `/api/audio/beats`. Cue blocks will be empty (`track.cues = []`) until Plan 04-02 adds block drag-and-drop, but that is expected for this phase.
## Self-Check: PASSED
- `lightsync/frontend/timeline/timeline.js` exists: YES (efa6123)
- `lightsync/models/show.py` has `duration: float = 4.0`: YES (454fddc)
- `lightsync/models/show.py` has `calibration_offset: float = 0.03`: YES (454fddc)
- `lightsync/frontend/index.html` has `id="timeline-canvas"`: YES
- `lightsync/frontend/style.css` has `#timeline-canvas`, `.block-inspector`: YES
- `lightsync/frontend/app.js` imports TimelineCanvas: YES
- Docker build and startup: CLEAN (no errors)
- Python model import test: PASSED (`CueModel(timestamp=1.0).duration == 4.0`)