Files
led2/.planning/phases/04-timeline-editor/04-02-SUMMARY.md
Claude c37fa6d1f6 docs(04-02): complete block interactions plan — SUMMARY, STATE, ROADMAP updated
- CommandHistory + 4 command classes + backend CRUD endpoints
- Full mouse interactions: drag-place, move, resize, delete, undo/redo
- Requirements TL-02, TL-03, TL-06 marked complete
- Progress: 86% (12/14 plans)
2026-04-06 23:46:05 +00:00

7.6 KiB

phase, plan, subsystem, tags, dependency_graph, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags dependency_graph tech-stack key-files key-decisions patterns-established requirements-completed duration completed
04-timeline-editor 02 ui
canvas
timeline
drag-drop
undo-redo
command-pattern
fastapi
crud
requires provides affects
phase provides
04-timeline-editor/04-01 TimelineCanvas class with coordinate helpers, tracks data structure, render loop
phase provides
03-communication-protocol Show/Cue/Track models, show_store persistence
CommandHistory with 50-step undo/redo stack
PlaceBlockCommand, MoveBlockCommand, ResizeBlockCommand, DeleteBlockCommand
Block CRUD REST endpoints (POST/PATCH/DELETE /api/shows/.../blocks)
Full block interaction
drag-from-palette, move, resize, delete
Overlap prevention on same track
Snap-to-beat on mouseup (100ms threshold)
04-03 (block inspector wiring will use selectedBlock and _onSelectBlock)
04-04 (playback engine reads cues from tracks which are now populated)
added patterns
HTML5 drag-and-drop API
crypto.randomUUID()
command pattern
Command pattern with execute/undo/redo for all timeline mutations
Fire-and-forget fetch for backend sync (no await, no blocking UI)
Live drag preview with revert-then-command on mouseup
Document-level mousemove/mouseup listeners during drag (not canvas-level)
created modified
lightsync/frontend/timeline/history.js
lightsync/frontend/timeline/commands.js
lightsync/api/timeline.py
lightsync/frontend/timeline/timeline.js
lightsync/frontend/app.js
lightsync/main.py
_startMove revert-then-command pattern: live drag updates block.timestamp directly for visual feedback, mouseup reverts to oldTimestamp then executes MoveBlockCommand — ensures history always has the correct before/after state
Circular import with main.py is expected: timeline.py imports lightsync.main as state at module level, same as shows.py — works at runtime, breaks only on isolated python -c import (non-issue)
Resize handles drawn as 8px highlighted zones on selected block for discoverability
Ghost preview during dragover uses trackIdx + time from _dragState rather than raw pixel coordinates — decoupled from render
Timeline command: stores references to tracks array + block object, mutates block in-place, syncs to backend fire-and-forget
setCurrentShowId() module-level function for routing backend sync without coupling commands to app state
TL-02
TL-03
TL-06
9min 2026-04-06

Phase 04 Plan 02: Block Interactions + Command Pattern Summary

Command-pattern block editing with drag-from-palette, move/resize/delete interactions, undo/redo (50 steps), overlap prevention, snap-to-beat, and backend CRUD endpoints.

Performance

  • Duration: ~9 min
  • Started: 2026-04-06T23:35:38Z
  • Completed: 2026-04-06T23:44:50Z
  • Tasks: 2
  • Files modified: 5 (3 created, 3 modified)

Accomplishments

  • CommandHistory class: 50-step max, execute/undo/redo, redo cleared on new action (branching protection)
  • 4 command classes: PlaceBlockCommand, MoveBlockCommand, ResizeBlockCommand, DeleteBlockCommand — each with backend sync via fire-and-forget fetch
  • REST API: POST/PATCH/DELETE /api/shows/{show_id}/tracks/{device_id}/blocks with auto-create track if missing
  • Full mouse interaction: hitTest with 8px resize zones, live drag preview, overlap rejection, snap-to-beat on release
  • Keyboard: Delete key removes selected block, Ctrl+Z undo, Ctrl+Shift+Z / Ctrl+Y redo
  • Drag-and-drop from animation palette to canvas with ghost block preview

Task Commits

  1. Task 1: Command pattern + history + backend block CRUD - 658c92f (feat)
  2. Task 2: Wire mouse interactions into TimelineCanvas - 7e18ac6 (feat)

Files Created/Modified

  • lightsync/frontend/timeline/history.js — CommandHistory class (execute, undo, redo, canUndo, canRedo, maxSteps=50)
  • lightsync/frontend/timeline/commands.js — PlaceBlockCommand, MoveBlockCommand, ResizeBlockCommand, DeleteBlockCommand + setCurrentShowId() + syncBlock()
  • lightsync/api/timeline.py — Block CRUD FastAPI router (POST/PATCH/DELETE)
  • lightsync/frontend/timeline/timeline.js — hitTest, _hasOverlap, snapToBeat, _setupInteractions, _startMove, _startResize, handleDrop added; CommandHistory + 4 command imports wired
  • lightsync/frontend/app.js — dragstart/dragover/dragleave/drop listeners for palette-to-canvas drag
  • lightsync/main.py — timeline router registered with prefix="/api"

Decisions Made

Decision Rationale
Revert-then-command pattern in _startMove Live drag updates block.timestamp for visual feedback; mouseup reverts to oldTimestamp, then MoveBlockCommand.execute() sets final value — history always has correct before/after state
Document-level mousemove/mouseup during drag Canvas-only listeners lose events when mouse moves fast outside canvas bounds
Ghost preview via _dragState.trackIdx + time Decoupled from raw pixels — render() computes screen position, maintaining coordinate abstraction
setCurrentShowId() module-level function Commands need showId for backend sync but shouldn't be coupled to app state — module-level var injected by app.js

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Plan's _startMove had duplicated/contradictory draft code

  • Found during: Task 2 implementation
  • Issue: The plan's _startMove action block contained two conflicting implementations (onDrag/onUp followed by onDrag2/onUp2) with a comment "IMPORTANT: The executor MUST implement it cleanly as a single version"
  • Fix: Implemented clean single version following the stated logic: record old, live-update during drag, revert + command on mouseup
  • Files modified: lightsync/frontend/timeline/timeline.js
  • Committed in: 7e18ac6

Total deviations: 1 auto-fixed (Rule 1 - Bug) Impact on plan: Fix was necessary — the draft code in the plan would not compile/run correctly as-is. Clean implementation follows exact logic specified in the IMPORTANT note.

Issues Encountered

  • Circular import warning during python -c "from lightsync.api.timeline import router" — same pattern as existing shows.py. Not a bug: import lightsync.main as state at module level causes partially-initialized module error on isolated import, but works correctly at runtime when app bootstraps normally. Docker build and startup confirmed clean.

Known Stubs

None — block placement, move, resize, delete are all fully wired. Backend CRUD endpoints accept and persist mutations. Undo/redo history tracks all operations.

Self-Check

  • lightsync/frontend/timeline/history.js exists: YES (658c92f)
  • lightsync/frontend/timeline/commands.js exports 4 command classes: YES (658c92f)
  • lightsync/api/timeline.py has POST/PATCH/DELETE routes: YES (658c92f)
  • lightsync/main.py includes timeline router: YES (658c92f)
  • lightsync/frontend/timeline/timeline.js has hitTest, _startMove, _startResize, handleDrop: YES (7e18ac6)
  • lightsync/frontend/app.js has dragstart/dragover/drop: YES (7e18ac6)
  • Docker build: SUCCESS (fa66387)
  • App startup: CLEAN (no errors, uvicorn running)

Self-Check: PASSED

Next Phase Readiness

  • Block placement and editing is fully functional — timeline is now interactive
  • Plan 04-03 (block inspector panel) can wire into timeline._onSelectBlock callback and timeline.selectedBlock
  • Plan 04-04 (playback/live engine) reads cues from timeline.tracks[*].cues which are now populated by user interactions
  • No blockers

Phase: 04-timeline-editor Completed: 2026-04-06