Commit Graph

27 Commits

Author SHA1 Message Date
Claude
70b86f78ce feat(06-03): add AI Sync panel UI and frontend wiring
- Add AI SYNC button to timeline toolbar, panel hidden by default (D-08)
- Panel: feature toggles (BEATS, ONSETS, CHROMA, RMS), GENERATE button, USE LLM toggle
- GENERATE disabled until audio loaded; re-enables on loadedmetadata event
- LLM toggle enabled/disabled based on /api/sync/llm-available at page load
- D-07: confirmation dialog before replacing existing timeline blocks
- D-11: section_range passed when a segment is selected in the overlay
- Segment selection watcher (200ms interval) updates info text dynamically
- Add AI sync panel styles — flat terminal aesthetic, no border-radius
2026-04-07 11:47:14 +00:00
Claude
48e15ee013 feat(06-02): add segment overlay canvas rendering and click interaction
- Create lightsync/frontend/timeline/segments.js with SegmentOverlay class
- Integrate SegmentOverlay into timeline.js render pipeline (between row banding and waveform)
- Add loadSegments method to TimelineCanvas
- Wire loadSegments call in app.js loadedmetadata handler
2026-04-07 11:41:41 +00:00
Claude
dca1cb837b feat(06-01): add YouTube URL input with terminal progress bar
- Add yt-url input, FETCH button, and yt-progress span to transport bar
- Add fetchWithProgress() SSE ReadableStream reader function
- Add renderProgressBar() terminal-style block fill renderer (DOWNLOADING ████)
- Wire FETCH button click handler with D-02 disable-during-download behavior
- Auto-load audio into player after download (D-01 same pipeline as upload)
- Add .yt-url-input and .yt-progress CSS styles (flat, monospace, no border-radius)
- Enable yt-url input on page load via JS
2026-04-07 11:37:10 +00:00
Claude
ac0f3d52db feat(05-04): add WebSocket play/pause messages to wireAudio() event listeners
- audio 'play' listener now sends client.send({ type: 'play', position }) to server
- audio 'pause' listener now sends client.send({ type: 'pause', position }) to server
- Server is_playing flag now set correctly on mouse-initiated playback
- UDP cue scheduler fires for both mouse and keyboard playback
2026-04-07 10:37:18 +00:00
Claude
3562652f2b feat(05-03): add wireKeyboard() with all 7 keyboard shortcuts
- Space toggles play/pause, sends play/pause msg to server
- ArrowLeft/ArrowRight seek +/-5s, send seek msg, clear preview strips
- Ctrl+Z calls timeline.history.undo()
- Ctrl+Shift+Z and Ctrl+Y both call timeline.history.redo()
- Ctrl+S prevents browser dialog, fetches show and POSTs to save
- All shortcuts suppressed when focus is on input/select/textarea
- Space skips when active element is a button (prevents double-toggle)
- wireKeyboard() called at init after wireAudio()
2026-04-07 10:18:19 +00:00
Claude
e064954b28 feat(05-02): wire preview_update handler, show selector, and preview strip builder
- Import setCurrentShowId from commands.js
- Add preview_update WebSocket message handler that calls updatePreviewStrip
- Add updatePreviewStrip() function to update device strip color and animation
- Add buildPreviewStrips() function to populate #live-preview from device list
- Call buildPreviewStrips() inside device fetch callback in initTimeline
- Add async refreshShows() to populate show-select dropdown from /api/shows
- Add btn-load-show click handler: loads show, populates tracks, sends WebSocket load msg
- Clear preview strips on seek (remove active class, reset color and anim label)
2026-04-07 10:15:34 +00:00
Claude
e0e412fcd1 feat(05-02): add live preview panel HTML and CSS styles
- Add #live-preview div between timeline-canvas and block-inspector
- Add show selector (show-select + btn-load-show) to transport bar
- Add keyboard-hint span at far right of transport bar
- Append .live-preview, .device-strip, .strip-label, .strip-color, .strip-anim, .keyboard-hint CSS styles
2026-04-07 10:14:16 +00:00
Claude
8e084ca8dc feat(04-04): wire BlockInspector into timeline and app.js
- app.js imports BlockInspector and DeleteBlockCommand
- Inspector created with #block-inspector element and timeline.history
- timeline._onSelectBlock callback shows/hides inspector on block select
- inspector._onDelete fires DeleteBlockCommand and hides inspector
- inspector.tracks kept in sync after loadTracks() resolves
- timeline.js block rendering uses blockColor + '1a' for color accent fill
- undo/redo handlers re-trigger _onSelectBlock to refresh inspector values
2026-04-06 23:54:22 +00:00
Claude
e82fc4a775 feat(04-04): add UpdateParamsCommand + BlockInspector module
- UpdateParamsCommand with execute/undo for color/speed/direction/length
- Uses module-level currentShowId (not this._showId) matching existing pattern
- BlockInspector renders compact strip with animation label, color swatch,
  speed input, direction select, length input, and remove button
- Color change committed on 'change' event (not 'input') to avoid command flood
- _onDelete callback wired to remove button for app.js to handle
2026-04-06 23:51:48 +00:00
Claude
287fe16a56 feat(04-03): BeatGrid module + enhanced beat rendering + loading states
- Create beats.js with BeatGrid class: load(), getCalibratedBeats(), getCalibratedOnsets(), snap(), hasBeats getter
- BeatGrid manages calibrationOffset and snapEnabled state
- snap() uses 100ms threshold against calibrated beat positions
- Handles API field variants: beats/beat_times, tempo/tempo_bpm
- Refactor timeline.js: replace beatTimes/calibrationOffset/snapEnabled with this.beatGrid
- Add getters/setters for calibrationOffset and snapEnabled (backward compat with app.js)
- render() now shows DETECTING BEATS... loading indicator and error state
- render() draws beat marks via getCalibratedBeats(), onset ticks via getCalibratedOnsets()
- snapToBeat() delegates to beatGrid.snap()
- app.js chains BPM label update after loadBeats() resolves
2026-04-06 23:48:27 +00:00
Claude
7e18ac6c84 feat(04-02): wire block interactions into TimelineCanvas
- Import CommandHistory and all 4 command classes from new modules
- hitTest() with 8px EDGE for resize-left/resize-right/move zones
- _hasOverlap() check for time range collision on same track
- snapToBeat() with 0.1s threshold and calibrationOffset
- _setupInteractions() with mousedown/mousemove/keydown listeners
- _startMove(): live drag preview, snap on mouseup, overlap rejection
- _startResize(): left/right edge handles, 0.5s min duration, snap
- handleDrop(): place block from palette with crypto.randomUUID()
- Keyboard: Delete, Ctrl+Z (undo), Ctrl+Shift+Z / Ctrl+Y (redo)
- app.js: dragstart on palette, dragover/dragleave/drop on canvas
- Ghost block rendered during dragover for visual feedback
- Resize handle indicators drawn on selected block
2026-04-06 23:44:45 +00:00
Claude
658c92f94d feat(04-02): command pattern + CommandHistory + block CRUD backend
- 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
2026-04-06 23:37:29 +00:00
Claude
efa612380c feat(04-01): TimelineCanvas class + CSS + app.js wiring
- 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
2026-04-06 23:34:32 +00:00
Claude
454fddc35e feat(04-01): data model changes + HTML layout restructure
- Add CueModel.duration field (float = 4.0) for block length in seconds
- Add AnalysisBlock.calibration_offset field (float = 0.03) for librosa bias
- Replace main-area animation grid with timeline canvas layout (toolbar, canvas, inspector)
- Move ANIMATIONS panel into sidebar below DEVICES panel
- Add timeline-toolbar with zoom slider, beat offset input, snap toggle
2026-04-06 23:30:55 +00:00
Claude
37f483e880 feat(02): controller UI — vertical anim list, play/pause, browser audio, terminal aesthetic
- Replace waveform+DAW layout with animation controller
- Animations as vertical list with > cursor, no icons
- Transport: play/pause button, seek slider, time display
- Browser <audio> handles playback, 10Hz ticks to server
- Sidebar: devices only (200px)
- python:3.11-slim base (replaces Alpine, fixes librosa/numba build)
- libsndfile symlink kept for soundfile
- Remove python-mpv, add librosa for beat detection
2026-04-06 17:32:11 +00:00
Claude
0963ad4a70 fix(02-03): fix upload file selection — pass abs path to refreshFileList
- Options use /app/shows/filename as value
- Upload response returns /app/shows/filename as path
- Pass path directly to refreshFileList so pre-selection matches
2026-04-06 15:39:21 +00:00
Claude
f1941a90db feat(02-03): redesign frontend — browser audio element, beat markers, animation selector
- index.html: add <audio id=player> (hidden), beat-indicator dot in header
- index.html: animation selector panel with Beat Pulse/Strobe/Color Wave/Rainbow options
- index.html: remove seek slider + time display (browser audio element has native controls)
- index.html: upload button + file selector remain in transport bar
- app.js: wire <audio> element — play/pause/seeked/timeupdate events send WS ticks
- app.js: 10Hz setInterval tick loop (supplements native timeupdate ~4Hz)
- app.js: WS beat message -> flashBeatIndicator() animation
- app.js: loadWaveformAndBeats() draws waveform peaks + beat marker lines on canvas
- app.js: loadSelectedFile() sets audio.src=/shows/filename, fetches beats+waveform
- app.js: upload response caches beat data returned from server immediately
- style.css: .beat-indicator with .beat-flash (yellow glow animation)
- style.css: .animation-select-input for the animations panel dropdown
2026-04-06 15:38:30 +00:00
Claude
9239988d38 fix(02): Alpine libmpv/libsndfile symlinks, upload endpoint, browser file picker
- Dockerfile: add libmpv.so + libsndfile.so symlinks (Alpine/musl has no ldconfig)
- engine.py: monkey-patch ctypes.util.find_library as fallback for Alpine
- audio.py: add /upload (multipart) and /files endpoints
- pyproject.toml: add python-multipart
- frontend: replace path input with upload button + file selector dropdown
2026-04-06 15:34:11 +00:00
Claude
148f16ba5e feat(02-03): build transport UI and waveform canvas
- Replace timeline placeholder with waveform canvas + playback cursor
- Replace transport placeholder with full transport bar controls
- Add play/pause/stop buttons, seek slider, time display, audio path input
- Add drawWaveform(), loadWaveform(), updatePosition(), formatTime() to app.js
- Dispatch msg.type=tick to updatePosition in handleMessage
- Wire transport buttons via WebSocket commands
- Add transport/waveform styles to style.css using existing CSS variables
- Deploy via ~/bin/deploy.sh
2026-04-06 13:14:05 +00:00
Claude
7590fc309d fix(ws): use wss:// on HTTPS to prevent mixed content SecurityError blocking module execution 2026-04-06 12:23:05 +00:00
Claude
e3dd78bee2 fix(ui): replace JS toggle with native <details> for device manage section 2026-04-05 21:38:48 +00:00
Claude
06845d862d fix(ui): compact devices panel (flex auto), fix manage button type=button 2026-04-05 21:23:44 +00:00
Claude
82995de95a feat(ui): hide device management behind ⚙ MANAGE toggle — form and remove buttons only shown when active 2026-04-05 20:59:35 +00:00
Claude
2ff35f9628 feat(01-04): fix CSS variable naming — add --accent as canonical source of truth
- Add --accent: #00ffff as canonical variable in :root
- Make --border-bright and --text-accent aliases of var(--accent)
- Add .device-form styles: flex column, gap, border-top separator
- Add .device-remove styles: borderless, hover red
- Update .device-row to flex row with justify-content: space-between for remove button
- Add .device-row-info for name/metadata stack within each row
2026-04-05 20:13:19 +00:00
Claude
ccc40c8721 feat(01-04): add device CRUD UI — form, POST, DELETE, remove buttons
- Add <form id='add-device-form'> to DEVICES panel with 5 inputs (name, strip_type, led_count, ip, port) and submit button
- Add apiPost() and apiDelete() helper functions to app.js
- Update loadDevices() to render device rows with remove button (.device-remove)
- Add removeDevice(id) function with DELETE /api/devices/{id} + window export for onclick
- Add form submit handler: POST /api/devices, form reset, refresh device list
- Add panel IDs: devices-panel, animations-panel, timeline-panel, transport-panel to index.html
2026-04-05 20:12:44 +00:00
Claude
a21962cbcf feat(01-03): integration wiring, Docker deployment, lightsync.groll.cloud
- Fix API routing: redirect_slashes=False + strip trailing slashes from route paths
- Fix StaticFiles catch-all intercepting /api/* before FastAPI redirect
- Add [tool.hatch.build.targets.wheel] to pyproject.toml for Docker builds
- Install docker-compose plugin (v2.34.0) as CLI plugin
- Create Dockerfile (python:3.11-alpine, CMD python -m lightsync)
- Create docker-compose.prod.yml (Traefik + Authelia, edge network)
- Deploy lightsync container to lightsync.groll.cloud
- Add LightSync entry to start/projects.json
- Update frontend app.js to use /api/devices (no trailing slash)
2026-04-05 18:59:34 +00:00
Claude
5e10272c69 feat(01-01): create device registry, show store, FastAPI app, API routes, and frontend shell
- DeviceRegistry with load/save/add/remove/instantiate and _DEVICE_CLASSES dict
- ShowStore with save/load/list_ids using aiofiles for async I/O
- FastAPI app with lifespan hook (startup: load registry/store, shutdown: save registry)
- API routes: GET/POST /api/devices/, DELETE /api/devices/{id}, GET/POST /api/shows/, GET /api/shows/{id}
- WebSocket stub at /ws with ConnectionManager and ack echo
- Frontend shell: DAW layout with header, sidebar (DEVICES + ANIMATIONS), TIMELINE, TRANSPORT
- Terminal aesthetic: dark theme, cyan accents, monospace fonts, no drop shadows
- .gitignore for runtime data (devices.json, shows/, __pycache__, .venv)
2026-04-05 18:47:25 +00:00