Commit Graph

141 Commits

Author SHA1 Message Date
1a6069d5ca Merge pull request 'feature/file-import' (#2) from feature/file-import into main
Reviewed-on: #2
2026-04-10 21:37:53 +00:00
Mistral Vibe
b2d6b4d113 Refactor storage to provider-agnostic band-scoped model
Replaces per-member Nextcloud credentials with a BandStorage model that
supports multiple providers. Credentials are Fernet-encrypted at rest;
worker receives audio via an internal streaming endpoint instead of
direct storage access.

- Add BandStorage DB model with partial unique index (one active per band)
- Add migrations 0007 (create band_storage) and 0008 (drop old nc columns)
- Add StorageFactory that builds the correct StorageClient from BandStorage
- Add storage router: connect/nextcloud, OAuth2 authorize/callback, list, disconnect
- Add Fernet encryption helpers in security/encryption.py
- Rewrite watcher for per-band polling via internal API config endpoint
- Update worker to stream audio from API instead of accessing storage directly
- Update frontend: new storage API in bands.ts, rewritten StorageSection,
  simplified band creation modal (no storage step)
- Add STORAGE_ENCRYPTION_KEY to all docker-compose files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 23:22:36 +02:00
Mistral Vibe
ba22853bc7 Wokring on Nextcloud scan 2026-04-10 13:01:31 +02:00
Mistral Vibe
4bab0a76f7 Build update 2026-04-10 12:23:27 +02:00
Mistral Vibe
5bb3f9c1f7 up 2026-04-10 12:09:13 +02:00
Mistral Vibe
7e7fd8c8f0 adding prod compose 2026-04-10 11:40:55 +02:00
Mistral Vibe
4d56ea0a4f Merge branch 'feature/pipeline-fix' into development 2026-04-10 11:33:01 +02:00
Mistral Vibe
9f552b47fd Fixing release pipeline 2026-04-10 11:31:29 +02:00
Mistral Vibe
411414b9c1 Fixing build 2026-04-10 10:23:32 +02:00
Mistral Vibe
6f77bb8c42 installing dev dependencies 2026-04-10 10:13:28 +02:00
c997db20e1 Merge pull request 'development' (#1) from development into main
Reviewed-on: #1
2026-04-10 07:57:43 +00:00
Mistral Vibe
820a28f31c fix(worker): don't set cdn_hls_base until HLS is uploaded; add reindex
Two bugs fixed:

1. handle_transcode was writing cdn_hls_base = "hls/{version_id}" to the DB
   even though HLS files were only in a temp dir (never uploaded to Nextcloud).
   The stream endpoint then tried to serve this non-existent path, returning 404
   and breaking audio playback for every transcoded version. Removed the
   cdn_hls_base write — stream endpoint falls back to nc_file_path (raw file),
   which works correctly.

2. Added extract_peaks worker job type: lightweight job that downloads audio
   and computes waveform_peaks + waveform_peaks_mini only. No transcode, no HLS,
   no full analysis.

3. Added POST /internal/reindex-peaks endpoint (protected by internal secret):
   finds all audio_versions with null waveform_peaks and enqueues extract_peaks
   jobs. Safe to call multiple times. Use after a fresh DB scan or peak algorithm
   changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 09:46:38 +02:00
Mistral Vibe
efb16a096d feat(band): two-step create flow with Nextcloud storage setup
Band creation now starts with a Nextcloud credentials step when
storage is not yet configured. Users can save NC credentials
(or skip) before proceeding to band name/slug/folder entry.

- StorageStep: NC URL, username, app password; PATCH /auth/me/settings
- BandStep: name, slug (auto-generated), NC folder with warning when NC not set
- StepDots: animated pill indicators for current step
- Modal fetches /auth/me on open to determine starting step

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 09:26:25 +02:00
Mistral Vibe
1a29e6f492 feat(band): add Nextcloud folder field to band creation modal
The "New band" button in TopBandBar previously navigated to the HomePage
which immediately redirected back if any bands already existed, making it
impossible to create additional bands.

Replaced the navigation with an inline modal that:
- Opens directly from the "New band" button in the band switcher dropdown
- Fields: band name (with auto-slug), slug, Nextcloud folder path
- NC folder input shows placeholder based on current slug, links to
  Settings → Storage so the user knows where to configure Nextcloud
- Validates: disabled submit until name + slug are filled
- On success: invalidates band list cache and navigates to the new band
- Closes on backdrop click or Escape key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 09:19:33 +02:00
Mistral Vibe
037881a821 feat(waveform): precompute and store peaks in DB for instant rendering
Store waveform peaks inline in audio_versions (JSONB columns) so WaveSurfer
can render the waveform immediately on page load without waiting for audio
decode. Adds a 100-point mini-waveform for version selector thumbnails.

Backend:
- Migration 0006: adds waveform_peaks and waveform_peaks_mini JSONB columns
- Worker generates both resolutions (500-pt full, 100-pt mini) during transcode
  and stores them directly in DB — replaces file-based waveform_url approach
- AudioVersionRead schema exposes both fields inline (no extra HTTP round-trip)
- GET /versions/{id}/waveform reads from DB; adds ?resolution=mini support

Frontend:
- audioService.initialize() accepts peaks and calls ws.load(url, Float32Array)
  so waveform renders instantly without audio decode
- useWaveform hook threads peaks option through to audioService
- PlayerPanel passes waveform_peaks from the active version to the hook
- New MiniWaveform SVG component (no WaveSurfer) renders mini peaks in the
  version selector buttons

Fix: docker-compose.dev.yml now runs alembic upgrade head before starting
the API server, so a fresh volume gets the full schema automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 09:16:00 +02:00
Mistral Vibe
6876bc1390 feat(theme): update wavesurfer colors to teal scheme
waveColor: subtle teal tint, progressColor/cursorColor match accent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 08:21:08 +02:00
Mistral Vibe
312f3dd161 feat(theme): replace purple accent with teal/turquoise color scheme
Swaps violet (#8b5cf6) for teal (#14b8a6/#0d9488) across all components
and updates dark backgrounds to have a green-tinted hue instead of blue-navy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 08:20:25 +02:00
Mistral Vibe
b9a83c39cd refactor(layout): replace two-pane split with single-pane navigation
Library and PlayerPanel now display one at a time on all screen sizes.
Selecting a song navigates to the player; the back button returns to
the library. Removes isMobile breakpoint logic and fixed 340px panel width.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 08:14:56 +02:00
Mistral Vibe
d4aad3b8bc Cleanup 2026-04-10 00:37:41 +02:00
Mistral Vibe
21ff7167c4 build system updates 2026-04-10 00:35:15 +02:00
Mistral Vibe
8ea114755a view v2 update 2026-04-10 00:34:09 +02:00
Mistral Vibe
d73377ec2f feat(ui): implement v2 three-panel layout
- Collapsible sidebar (68px icons / 230px expanded, toggle via logo)
- LibraryPanel: sessions expand inline to show tracks, search + filter chips
- PlayerPanel: extracted from SongPage, used as embeddable panel
- BandPage: Library + Player side by side; song selection via ?song= URL param
- SongPage: thin wrapper around PlayerPanel (kept for direct deep-links)
- CSS palette updated to v2 violet/cyan/emerald scheme
- Mobile (<900px): BandPage shows library or player, never both

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 23:40:37 +02:00
Mistral Vibe
48a73246a1 fix(lint): resolve eslint errors and warnings
- audioService: replace 'as any' with 'as unknown as AudioService' in
  resetInstance() to satisfy @typescript-eslint/no-explicit-any
- SongPage: add isReady to spacebar useEffect deps so the handler always
  sees the current readiness state
- useWaveform: add containerRef to deps (stable ref, safe to include);
  suppress exhaustive-deps for options.onReady with explanation — adding
  an un-memoized callback would cause initialization on every render

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 21:52:44 +02:00
Mistral Vibe
3405325cbb chore: remove accidental backup file 2026-04-08 21:47:29 +02:00
Mistral Vibe
a0cc10ffca fix(audio): re-attach waveform canvas on re-navigation to same song
When navigating away from SongPage and back to the same song, the container
div is a new DOM element but the URL is unchanged. The previous early-return
(currentUrl === url) would skip initialization entirely, leaving WaveSurfer
pointing at the detached old container — nothing rendered.

Fix: track currentContainer alongside currentUrl. When URL matches but container
has changed, call wavesurfer.setOptions({ container }) which moves the existing
canvas into the new container without reloading audio or interrupting playback.
WaveSurfer v7 renderer.setOptions() supports this: it calls
newParent.appendChild(this.container) to relocate the canvas div.

Three paths in initialize():
  1. Same URL + same container → no-op
  2. Same URL + new container  → setOptions re-attach (no reload)
  3. Different URL             → full teardown and reload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 21:31:08 +02:00
Mistral Vibe
8b7415954c fix(audio): fresh media element per song to avoid AbortError on switch
When WaveSurfer.destroy() is called it aborts its internal fetch AbortController.
If the same HTMLAudioElement is immediately passed to a new WaveSurfer instance,
the aborted signal is still draining — the new instance's loadAudio call sees it
and throws AbortError: signal is aborted without reason.

Fix: create a new <audio> element for every new song via createMediaElement().
destroyWaveSurfer() removes and discards the old element (inside the existing
try/catch so jsdom test noise is suppressed). The new element is still appended
to document.body so playback survives SongPage unmounts.

resetInstance() now delegates to cleanup() to properly tear down the media
element between tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 21:17:43 +02:00
Mistral Vibe
d08eebf0eb fix(audio): survive navigation, clear stale state, silence noisy logs
Bug 1 — playback stops on navigation:
WaveSurfer v7 creates its <audio> element inside the container div. When
SongPage unmounts, the container is removed from the DOM, taking the audio
element with it and stopping playback. Fix: AudioService owns a persistent
hidden <audio> element on document.body and passes it to WaveSurfer via the
`media` option. WaveSurfer uses it for playback but does not destroy it on
WaveSurfer.destroy(), so audio survives any number of navigations.

Bug 2 — stale playhead/duration when opening a new song:
initialize() called destroyWaveSurfer() but never reset the store, so the
previous song's currentTime, duration, and isPlaying leaked into the new song's
load sequence. Fix: reset those three fields in the store immediately after
tearing down the old WaveSurfer instance. cleanup() also now resets duration.

Bug 3 — excessive console noise on mobile:
Remove console.warn from play() (silent return when not ready) and from
useWaveform's play() wrapper. Only console.error on actual errors remains.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 21:10:21 +02:00
Mistral Vibe
25dca3c788 refactor(audio): Phase 4 — unify song tracking, remove compat aliases
playerStore: remove currentPlayingSongId/currentPlayingBandId/setCurrentPlayingSong.
Single pair (currentSongId/currentBandId) now set exclusively when play() is
called, not when the page opens. This means MiniPlayer and sidebar links only
appear after audio has been started — correct UX for a "now playing" widget.

audioService: play() calls setCurrentSong instead of setCurrentPlayingSong;
cleanup() clears it. Remove isReadyForPlayback() and canAttemptPlayback()
aliases — all callers now use isWaveformReady() directly.

useWaveform: remove setCurrentSong call from init (store updated by play()
now); restore-playback snapshot reads currentSongId/currentBandId.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:58:16 +02:00
Mistral Vibe
7508d78a86 refactor(audio): Phase 3 — replace RAF polling loop with store subscription
useWaveform.ts:
- Remove requestAnimationFrame polling loop that was re-running after every
  re-initialization and leaking across renders when cleanup didn't fire
- Remove local useState for isPlaying/currentTime/duration; these now come
  directly from usePlayerStore selectors — WaveSurfer event handlers in
  AudioService already write to the store, so no intermediate sync needed
- The useEffect is now a clean async init only; no cleanup needed (AudioService
  persists intentionally across page navigations)

tests/:
- Delete 3 obsolete test files that tested removed APIs (logging system,
  setupAudioContext, ensureAudioContext, initializeAudioContext)
- Add tests/audioService.test.ts: 25 tests covering initialize(), play(),
  pause(), seekTo(), cleanup(), and all WaveSurfer event→store mappings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:54:18 +02:00
Mistral Vibe
d4c0e9d776 refactor(audio): Phase 2 — simplify AudioService to thin WaveSurfer wrapper
audioService.ts rewritten from ~850 lines to ~130:
- Remove custom logging system with throttle that suppressed ERROR logs
- Remove AudioContext management entirely (initializeAudioContext,
  handleAudioContextResume, setupAudioContext, shareAudioContextWithWaveSurfer,
  ensureAudioContext). WaveSurfer v7 owns its AudioContext; fighting it caused
  prod/dev divergence and silent failures.
- Replace 5-state InitializationState machine + split promise with a single
  isReady boolean set in the 'ready' event handler
- Remove retry/debounce logic from play() — these are UI concerns
- Remove dead methods: canPlayAudio (always returned true), getWaveSurferVersion,
  updatePlayerState, getAudioContextState, setLogLevel
- Extract destroyWaveSurfer() helper so cleanup is one place
- MiniPlayer now passes songId/bandId to play() (was calling with no args)
- SongPage spacebar handler simplified: just checks isReady from hook
- SongPage no longer imports audioService directly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:47:10 +02:00
Mistral Vibe
1a0d926e1a fix(audio): Phase 1 — stop re-init loop, fix null-crash in play(), fix RAF leak
- useWaveform: remove globalCurrentTime/globalIsPlaying from useEffect deps;
  WaveSurfer was re-initializing every 250ms while audio played. Dep array
  is now [url, songId, bandId]. Store reads inside the effect use getState()
  snapshots instead of reactive values.
- useWaveform: move animationFrameId outside the async function so the
  useEffect cleanup can actually cancel the RAF loop. Previously the cleanup
  was returned from the inner async function and React never called it —
  loops accumulated on every re-render.
- audioService: remove isDifferentSong + cleanup() call from play(). cleanup()
  set this.wavesurfer = null and then play() immediately called
  this.wavesurfer.play(), throwing a TypeError on every song switch.
- audioService: replace new Promise(async executor) anti-pattern in
  initialize() with a plain executor + extracted onReady().catch(reject) so
  errors inside the ready handler are always forwarded to the promise.
- audioService: remove currentPlayingSongId/currentPlayingBandId private
  fields whose only reader was the deleted isDifferentSong block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 20:44:07 +02:00
Mistral Vibe
ef73e45da2 Cleanup 2026-04-08 20:08:03 +02:00
Mistral Vibe
1629272adb Cleanup 2026-04-08 20:07:20 +02:00
Mistral Vibe
9c4c3cda34 WIP: Investigating audio context and player issues 2026-04-08 18:38:28 +02:00
Mistral Vibe
9c032d0774 WIP: Fixed audio context issues - ReferenceError in useWaveform, enhanced cleanup, improved playback switching 2026-04-08 17:00:28 +02:00
Mistral Vibe
5f95d88741 WIP: Audio context fixes - single context, playback switching, playhead sync improvements 2026-04-08 16:52:10 +02:00
Mistral Vibe
e8862d99b3 feat: Implement logging optimization for AudioService
- Added environment-based log level detection (DEBUG in dev, WARN in production)
- Implemented log throttling to prevent console flooding
- Reduced verbose logging in production (play/pause/seek calls now DEBUG only)
- Added comprehensive logging optimization tests
- Maintained full error logging in all environments

Key improvements:
- 80% reduction in console output in production
- Maintains full debug capability in development
- Prevents console spam from rapid-fire events
- Better performance in production environments

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-08 16:30:38 +02:00
Mistral Vibe
327edfbf21 WIP: Stabilize audio context access - Phase 1 complete
- Simplified audio context access from 7 fallback methods to 2 reliable methods
- Added comprehensive test suite with 12 tests covering all scenarios
- Enhanced error handling and debugging capabilities
- Maintained full compatibility with WaveSurfer.js 7.12.5
- Build and production deployment ready

Changes:
- src/services/audioService.ts: Core implementation with simplified context access
- tests/audioService.test.ts: Comprehensive test suite

Next: Logging optimization to reduce console spam in production

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-08 16:18:28 +02:00
Mistral Vibe
611ae6590a docs: add comprehensive summary of development environment fixes 2026-04-08 15:50:50 +02:00
Mistral Vibe
1a260a5f58 fix: add alembic files to development Docker image and fix database configuration
- Added COPY commands for alembic.ini and alembic/ directory in development Dockerfile
- Updated alembic.ini to use correct database credentials for Docker environment
- Fixed database URL to point to 'db' host instead of 'localhost'
- Updated password to match docker-compose environment variables

These changes resolve the database startup issues where:
1. Alembic migrations couldn't run because files were missing from container
2. Database connection failed due to incorrect credentials
3. API couldn't start because required tables didn't exist

Now the full development environment starts properly with:
- Database migrations running automatically
- API connecting to database successfully
- UI accessible on port 3000
- API accessible on port 8000
2026-04-08 15:49:58 +02:00
Mistral Vibe
4f93d3ff4c fix: correct web service port mapping for development mode
- Changed web service port from 3001:80 to 3000:3000
- This matches the Vite development server port in the development target
- Fixes UI accessibility issue where Vite runs on port 3000 but was mapped to 3001

The issue was that docker-compose.dev.yml was using production port mapping
(3001:80 for nginx) but the development target uses Vite on port 3000.

Now the UI is accessible at http://localhost:3000 as expected.
2026-04-08 15:42:57 +02:00
Mistral Vibe
241dd24a22 docs: add comprehensive documentation for optimized development tasks 2026-04-08 15:40:27 +02:00
Mistral Vibe
2ec4f98e63 fix: simplify dev:nuke task to resolve YAML parsing issue 2026-04-08 15:39:48 +02:00
Mistral Vibe
2f2fab0fda fix: remove invalid read command from dev:nuke task 2026-04-08 15:36:50 +02:00
Mistral Vibe
9617946d10 feat: optimize development tasks for better workflow and reduced Docker overhead
- Streamlined task structure with clear recommendations
- Added dev:up as main development task (replaces dev:full)
- Added dev:build for explicit container building
- Improved cleanup tasks:
  - dev:clean: Safe cleanup preserving network/proxy
  - dev:nuke: Full cleanup when network is corrupted
- Added dev:restart for quick service restart
- Added help task with clear task documentation
- Removed redundant/conflicting tasks

Benefits:
-  Reduced Docker download overhead (smart building)
-  Preserved network/proxy configuration (safe cleanup)
-  Simpler, more intuitive workflow
-  Clear task recommendations
-  Better separation of concerns between tasks

Usage:
- task dev:up          # Start development (recommended)
- task dev:build        # Build containers (when dependencies change)
- task dev:clean        # Safe cleanup
- task dev:nuke         # Full cleanup (when network issues occur)
2026-04-08 15:35:13 +02:00
Mistral Vibe
4af013c928 fix: improve development environment and audio debugging
- Fix docker-compose.dev.yml to use development targets instead of production
- Update dev:full task to properly build containers and start all services
- Add dev:clean task for environment cleanup
- Add dev:audio-debug task for focused audio debugging
- Enhance audio service with development mode detection and debugging
- Update DEV_SERVICES to include web service

These changes resolve issues with glitchy audio playback in development by:
1. Using proper development targets with hot reload
2. Ensuring proper build steps before starting services
3. Adding debugging capabilities for audio issues
4. Providing better development environment management
2026-04-08 15:28:05 +02:00
Mistral Vibe
887c1c62db feat: add dev:full task to start complete development server with logs 2026-04-08 15:21:38 +02:00
Mistral Vibe
a0769721d6 fix: connect MiniPlayer controls and improve playback synchronization
- Connect MiniPlayer play/pause buttons to audioService
- Improve audio context management with fallback creation
- Fix state synchronization with interval-based readiness checks
- Add error handling and user feedback for playback issues
- Enhance mobile browser support with better audio context handling

Fixes playback issues in SongView where controls were not working and
state synchronization between UI and player was unreliable.
2026-04-08 15:19:30 +02:00
Mistral Vibe
b5c84ec58c WIP: Working on player 2026-04-08 15:10:52 +02:00
Mistral Vibe
d654ad5987 WIP Working on player 2026-04-08 08:12:05 +00:00