From c37fa6d1f60e69704bc1615ea7c48b20c7f38129 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Apr 2026 23:46:05 +0000 Subject: [PATCH] =?UTF-8?q?docs(04-02):=20complete=20block=20interactions?= =?UTF-8?q?=20plan=20=E2=80=94=20SUMMARY,=20STATE,=20ROADMAP=20updated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- .planning/REQUIREMENTS.md | 10 +- .planning/ROADMAP.md | 4 +- .planning/STATE.md | 15 +- .../04-timeline-editor/04-02-SUMMARY.md | 147 ++++++++++++++++++ 4 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 .planning/phases/04-timeline-editor/04-02-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index facf3e5..1910412 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -30,12 +30,12 @@ ### Timeline Editor -- [ ] **TL-01**: Per-device horizontal tracks on a time axis -- [ ] **TL-02**: Drag-and-place animation blocks onto a track -- [ ] **TL-03**: Move, resize, and delete animation blocks +- [x] **TL-01**: Per-device horizontal tracks on a time axis +- [x] **TL-02**: Drag-and-place animation blocks onto a track +- [x] **TL-03**: Move, resize, and delete animation blocks - [ ] **TL-04**: Color picker per animation block -- [ ] **TL-05**: Playback cursor (vertical line) tracking audio position in realtime -- [ ] **TL-06**: Undo/redo for all timeline mutations (min 20 steps, command pattern) +- [x] **TL-05**: Playback cursor (vertical line) tracking audio position in realtime +- [x] **TL-06**: Undo/redo for all timeline mutations (min 20 steps, command pattern) - [ ] **TL-07**: Beat detection marks overlaid on timeline (librosa beat_track) - [ ] **TL-08**: Snap-to-beat when placing/resizing blocks diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index b67d6c8..d478a0b 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -91,7 +91,7 @@ Plans: Plans: - [x] 04-01: Timeline canvas — per-device tracks, time axis, block rendering, playback cursor -- [ ] 04-02: Block interactions — drag-and-place, move, resize, delete; command pattern undo/redo (min 20 steps) +- [x] 04-02: Block interactions — drag-and-place, move, resize, delete; command pattern undo/redo (min 20 steps) - [ ] 04-03: Beat detection + snap — librosa beat_track + plp + onset_detect in ProcessPoolExecutor, beat mark overlay, snap-to-beat, calibration offset UI - [ ] 04-04: Block editor — animation library panel, color picker, parameter controls per block, changes reflected in show file **UI hint**: yes @@ -155,7 +155,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 | 1. Foundation | 4/4 | Complete | 2026-04-05 | | 2. Audio Engine | 3/3 | Complete | 2026-04-06 | | 3. Communication Protocol | 3/3 | Complete | 2026-04-06 | -| 4. Timeline Editor | 1/4 | In Progress| | +| 4. Timeline Editor | 2/4 | In Progress| | | 5. Live Show Execution | 0/3 | Not started | - | | 6. AI Sync | 0/3 | Not started | - | | 7. Microcontroller Firmware | 0/3 | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 546b27a..a83571f 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: executing -stopped_at: Completed 04-timeline-editor/04-01-PLAN.md -last_updated: "2026-04-06T23:35:19.480Z" +stopped_at: Completed 04-timeline-editor/04-02-PLAN.md +last_updated: "2026-04-06T23:45:55.310Z" last_activity: 2026-04-06 progress: total_phases: 7 completed_phases: 3 total_plans: 14 - completed_plans: 11 + completed_plans: 12 percent: 0 --- @@ -26,7 +26,7 @@ See: .planning/PROJECT.md (updated 2026-04-05) ## Current Position Phase: 04 (timeline-editor) — EXECUTING -Plan: 2 of 4 +Plan: 3 of 4 Status: Ready to execute Last activity: 2026-04-06 @@ -63,6 +63,7 @@ Progress: [░░░░░░░░░░] 0% | Phase 03-communication-protocol P02 | 3min | 1 tasks | 10 files | | Phase 03-communication-protocol P03 | 2min | 2 tasks | 3 files | | Phase 04-timeline-editor P01 | 4min | 2 tasks | 5 files | +| Phase 04-timeline-editor P02 | 9 | 2 tasks | 6 files | ## Accumulated Context @@ -101,6 +102,8 @@ Recent decisions affecting current work: - [Phase 03-communication-protocol]: UDPSender stopped before registry.save() in shutdown — orderly teardown, no sends after socket close - [Phase 04-timeline-editor]: Beats API uses 'beats' field not 'beat_times' — loadBeats() handles both with fallback - [Phase 04-timeline-editor]: TimelineCanvas auto-scroll at 80%/20% threshold — standard DAW follow behavior +- [Phase 04-timeline-editor]: Revert-then-command pattern in _startMove: live drag updates block.timestamp for visual feedback; mouseup reverts to oldTimestamp then MoveBlockCommand executes — history always has correct before/after state +- [Phase 04-timeline-editor]: setCurrentShowId() module-level function routes backend sync without coupling commands to app state ### Pending Todos @@ -114,8 +117,8 @@ None yet. ## Session Continuity -Last session: 2026-04-06T23:35:19.475Z -Stopped at: Completed 04-timeline-editor/04-01-PLAN.md +Last session: 2026-04-06T23:45:55.305Z +Stopped at: Completed 04-timeline-editor/04-02-PLAN.md Resume file: None Next action: `/gsd:plan-phase 1` diff --git a/.planning/phases/04-timeline-editor/04-02-SUMMARY.md b/.planning/phases/04-timeline-editor/04-02-SUMMARY.md new file mode 100644 index 0000000..7e28b68 --- /dev/null +++ b/.planning/phases/04-timeline-editor/04-02-SUMMARY.md @@ -0,0 +1,147 @@ +--- +phase: 04-timeline-editor +plan: 02 +subsystem: ui +tags: [canvas, timeline, drag-drop, undo-redo, command-pattern, fastapi, crud] +dependency_graph: + requires: + - phase: 04-timeline-editor/04-01 + provides: TimelineCanvas class with coordinate helpers, tracks data structure, render loop + - phase: 03-communication-protocol + provides: Show/Cue/Track models, show_store persistence + provides: + - 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) + affects: + - 04-03 (block inspector wiring will use selectedBlock and _onSelectBlock) + - 04-04 (playback engine reads cues from tracks which are now populated) + +tech-stack: + added: [HTML5 drag-and-drop API, crypto.randomUUID(), command pattern] + patterns: + - 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) + +key-files: + created: + - lightsync/frontend/timeline/history.js + - lightsync/frontend/timeline/commands.js + - lightsync/api/timeline.py + modified: + - lightsync/frontend/timeline/timeline.js + - lightsync/frontend/app.js + - lightsync/main.py + +key-decisions: + - "_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" + +patterns-established: + - "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" + +requirements-completed: [TL-02, TL-03, TL-06] + +duration: 9min +completed: 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*