- CommandHistory class with execute/undo/redo, 50-step max, redo-clear on new action
- PlaceBlockCommand, MoveBlockCommand, ResizeBlockCommand, DeleteBlockCommand
- setCurrentShowId() module-level function for backend sync routing
- syncBlock() fire-and-forget fetch helper for backend sync
- POST/PATCH/DELETE /api/shows/{show_id}/tracks/{device_id}/blocks endpoints
- timeline router registered in main.py with prefix=/api
- Create lightsync/frontend/timeline/timeline.js with TimelineCanvas class
- Per-device horizontal tracks with track headers and time axis
- HiDPI canvas rendering with devicePixelRatio handling
- Waveform peaks background layer, beat marks overlay, cue block rendering
- Playback cursor synced to audio.currentTime via requestAnimationFrame
- Auto-scroll when cursor approaches right edge
- Mouse wheel scrolling on timeline canvas
- loadTracks(), loadWaveform(), loadBeats() data loading methods
- Empty state messages when no devices or audio loaded
- Add timeline CSS to style.css (toolbar, canvas, inspector strip, snap button)
- Wire timeline into app.js: import, initTimeline(), zoom/beat-offset/snap controls
- Remove old animation-grid click listener (element no longer exists in main-area)
- Populate animation palette dynamically in sidebar with 7 animation types