Files
led2/.planning/phases/04-timeline-editor/04-01-SUMMARY.md
Claude 96d7825c34 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
2026-04-06 23:35:29 +00:00

4.2 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
04-timeline-editor 01 frontend-canvas
canvas
timeline
daw-ui
audio
waveform
requires provides affects
Phase 03 communication protocol — UDP sender
animation encoders
TimelineCanvas class
timeline rendering foundation
lightsync/frontend
lightsync/models/show.py
added patterns
HTML5 Canvas 2D API
ES module import/export
requestAnimationFrame render loop
HiDPI canvas with devicePixelRatio
coordinate system with HEADER_WIDTH/TIME_AXIS_HEIGHT offsets
layer-ordered canvas render
created modified
lightsync/frontend/timeline/timeline.js
lightsync/models/show.py
lightsync/frontend/index.html
lightsync/frontend/style.css
lightsync/frontend/app.js
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
duration completed tasks files
4min 2026-04-06 2 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)