7.8 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 | 03 | frontend-canvas |
|
|
|
|
|
|
|
2min | 2026-04-06 |
Phase 04 Plan 03: BeatGrid Module + Beat Rendering Summary
BeatGrid ES module encapsulating librosa beat data with calibration offset management, loading/error states, and snap-to-beat logic — extracted from TimelineCanvas for clean separation of concerns.
Performance
- Duration: ~2 min
- Started: 2026-04-06T23:47:10Z
- Completed: 2026-04-06T23:49:30Z
- Tasks: 1
- Files modified: 3 (1 created, 2 modified)
Accomplishments
BeatGridclass inbeats.js: asyncload()fetching from/api/audio/beats,getCalibratedBeats(),getCalibratedOnsets(),snap()with 100ms threshold,loading/errorstate,hasBeatsgetter- Beat mark rendering refactored to use
beatGrid.getCalibratedBeats()— calibration offset applied at render time, raw data never mutated - Loading state indicator "DETECTING BEATS..." displayed in canvas during async analysis
- Error state displayed when beat detection fails
- BPM label updates in header toolbar after beat analysis resolves via
.then() - Getters/setters on TimelineCanvas proxy
snapEnabledandcalibrationOffsetto beatGrid — existing app.js toolbar wiring works without changes snapToBeat()delegates tobeatGrid.snap()— block placement, move, and resize all continue to snap correctly
Task Commits
- Task 1: BeatGrid module + enhanced timeline integration -
287fe16(feat)
Files Created/Modified
lightsync/frontend/timeline/beats.js— BeatGrid class (load, getCalibratedBeats, getCalibratedOnsets, snap, hasBeats, loading/error state)lightsync/frontend/timeline/timeline.js— Imports BeatGrid, creates this.beatGrid in constructor, render() uses beatGrid for beat marks + loading indicator, snapToBeat() delegates to beatGrid.snap(), getters/setters for calibrationOffset/snapEnabledlightsync/frontend/app.js— loadBeats() chained with .then() to update BPM label from beatGrid.tempoBpm
Decisions Made
| Decision | Rationale |
|---|---|
| API field normalization in load() | Existing API returns beats/tempo not beat_times/tempo_bpm as plan expected — BeatGrid.load() handles both variants with fallback |
| onset_times silently absent | Current API has no onset_times field — getCalibratedOnsets() returns [] and rendering skips onset ticks — future API extension will work automatically |
| textAlign reset after centered text | Drawing loading/error text centered then resetting to 'left' prevents layout corruption for subsequent draw calls in same render frame |
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] API field name mismatch for tempo and beat times
- Found during: Task 1 implementation (reading audio.py + beats.py)
- Issue: Plan's BeatGrid code expected
data.beat_timesanddata.tempo_bpmbut the actual/api/audio/beatsendpoint returns{"beats": [...], "tempo": float}(confirmed inlightsync/audio/beats.py) - Fix: BeatGrid.load() uses
data.beats || data.beat_timesfor beat times anddata.tempo_bpm || data.tempofor BPM — handles both field name variants - Files modified:
lightsync/frontend/timeline/beats.js - Committed in:
287fe16
2. [Rule 2 - Missing Critical] onset_times not in API response
- Found during: Task 1 implementation
- Issue: Plan called for rendering onset marks from
onset_timesbut the API has no such field — would silently break if array access attempted on undefined - Fix:
this.onsetTimes = data.onset_times || []— fallback to empty array; onset tick rendering guarded byif (onsets.length > 0)— no errors, graceful no-op - Files modified:
lightsync/frontend/timeline/beats.js,lightsync/frontend/timeline/timeline.js - Committed in:
287fe16
Total deviations: 2 auto-fixed (1 Rule 1 - API field names, 1 Rule 2 - missing null guard) Impact on plan: Both fixes necessary for correctness. No scope creep. Core functionality (beat marks, snap, loading state, BPM display) works as designed.
Issues Encountered
None beyond the field name deviations documented above.
Known Stubs
None — BeatGrid loads live data from /api/audio/beats. Onset ticks are gracefully absent (no onset data in current API) but will render automatically when API is extended.
Self-Check: PASSED
lightsync/frontend/timeline/beats.jsexists: YES (287fe16)export class BeatGridin beats.js: YESgetCalibratedBeats,getCalibratedOnsets,snap(),SNAP_THRESHOLD: YESimport { BeatGrid }in timeline.js: YESthis.beatGrid = new BeatGrid()in constructor: YESDETECTING BEATS...in render(): YESgetCalibratedBeatsandgetCalibratedOnsetsused in render(): YESget snapEnabled,set snapEnabled,get calibrationOffset,set calibrationOffset: YESloadBeats()delegates tobeatGrid.load(): YES- Docker build: SUCCESS (no errors)
Next Phase Readiness
- BeatGrid is available as
timeline.beatGrid— playback engine (Plan 04-04) can readtimeline.beatGrid.getCalibratedBeats()for live sync - Beat snap works for all block operations (place, move, resize) via beatGrid.snap()
- Calibration offset and snap toggle continue working through existing app.js toolbar wiring
- No blockers
Phase: 04-timeline-editor Completed: 2026-04-06