- Add proper type guards for error object properties
- Check for 'status' and 'data' properties before accessing
- Maintain all debugging functionality
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add detailed error extraction from API responses
- Validate file content is not empty before saving
- Verify file was actually saved to disk
- Check saved file size matches expectations
- Add extensive logging for debugging upload issues
- Improve error messages with specific details
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Change invalid file type error from 400 to 422 for better frontend handling
- Add specific error message for 422 responses in frontend
- Improve error message clarity
- Better error classification and user guidance
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Remove extra parameter that was causing TypeScript error
- Keep all other file size handling improvements
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Reduce server-side limit to 5MB for upload endpoint
- Increase client-side resizing threshold to 4MB
- Add specific error handling for 413 responses
- Add more detailed logging for file sizes
- Improve user error messages
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add resizeImage function to SettingsPage
- Resize images larger than 2MB to max 800x800 pixels
- Convert to JPEG with 80% quality to reduce file size
- Add server-side validation for 10MB file size limit
- Maintain aspect ratio during resizing
- Log resizing details for debugging
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Remove unnecessary headers parameter from API call
- Fix unused error parameter in onError handler
- Use undefined instead of null for avatar_url removal
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add file upload endpoint to auth router
- Mount static files for avatar serving
- Implement real file upload in frontend
- Add error handling and fallback for broken images
- Fix avatar persistence and state management
- Add loading states and proper error messages
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add avatar_url field to MemberSettingsUpdate schema
- Create AvatarService for generating default avatars using DiceBear
- Update auth service to generate avatars on user registration
- Add avatar upload UI to settings page
- Update settings endpoint to handle avatar URL updates
- Display current avatar in settings with upload/generate options
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Add author_avatar_url to API schema and frontend interface
- Capture current playhead timestamp when creating comments
- Display user avatars in waveform markers instead of placeholders
- Improve marker visibility with better styling (size, borders, shadows)
- Fix TypeScript type errors for nullable timestamps
- Add debug logging for troubleshooting
This implements the full comment waveform integration as requested:
- Comments are created with exact playhead timestamps
- Waveform markers show at correct positions with user avatars
- Clicking markers scrolls to corresponding comments
- Backward compatible with existing comments without timestamps
Implements proper play behaviour in track view:
- Version switching stops playback and resets UI state
- Playback must be started manually after version switch
- Added space key shortcut for play/pause toggle
- UI elements correctly reflect playback state
Fixes playback control issues after version switching
Enhances user experience with keyboard shortcuts
- Version switching now stops playback and resets UI state
- Playback must be started manually after version switch
- Added space key shortcut for play/pause toggle
- UI elements correctly reflect playback state
Fixes: Play/pause controls work correctly after version switching
Improves: User experience with keyboard shortcuts
- Added wasPlayingRef to preserve playback state across version changes
- Auto-play new waveform if previous version was playing
- Ensure play/pause buttons work correctly after version switch
- Added WebSocket proxy configuration for real-time features
Fixes: Version switching now preserves playback state
Todo: Test edge cases and finalize implementation
- Remove global Nextcloud settings from config
- Make NextcloudClient require explicit credentials
- Update for_member() to return None when no credentials
- Modify services to accept optional storage client
- Update routers to pass member storage to services
- Add 403 responses when no storage provider configured
- Update internal endpoints to use member storage credentials
This change enforces that each member must configure their own
Nextcloud storage provider. If no provider is configured,
file operations will return 403 FORBIDDEN instead of falling
back to global placeholders.
Covers: service topology, directory layout, data model, full API surface,
scan/import pipeline, audio analysis flow, auth model, and key conventions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Files directly in YYMMDD/ (e.g. 231015/take1.wav, 231015/take2.wav) all
shared the same nc_folder_path, so take2 was silently merged as a new
version of the take1 song instead of being a separate recording.
Fix: when a file's parent == the session folder, append the file stem
to make a unique virtual nc_folder_path per file. Files in subfolders
(e.g. 231015/groove/take1.wav) are unaffected — they still group by folder.
Applied in both nc_scan.py (manual scan) and internal.py (watcher uploads).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pydantic v2 model_validate() does not accept an update kwarg; the correct
pattern is model_validate(obj).model_copy(update={...}).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- nc_scan.py: recursive collect_audio_files (fixes depth-1 bug); scan_band_folder
yields ndjson events (progress/song/session/skipped/done) for streaming
- songs.py: replace old flat scan with scan_band_folder; add GET nc-scan/stream
endpoint using _member_from_request so ?token= auth works for fetch-based SSE
- BandPage.tsx: scan button now consumes ndjson stream via fetch+ReadableStream;
sessions/unattributed invalidated as each song/session event arrives
- session.py: add extract_session_folder() for YYMMDD path extraction
- rehearsal_session.py: get_or_create uses begin_nested() savepoint to handle races
- band.py: add get_by_nc_folder_prefix() for custom nc_folder_path band lookup
- internal.py: nc-upload falls back to prefix match when slug lookup fails
- event_loop.py: remove hardcoded bands/ guard; let internal API handle filtering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- nc-scan: detailed INFO logging of every path found, subfolder
contents and skip reasons; 502 now includes the exact folder and
error so user sees a real message instead of a blank result
- band creation: if nc_base_path is explicitly given, verify the
folder exists in Nextcloud before saving — returns 422 with a
clear message to the user; auto-generated paths still do MKCOL
- songs search: add ?unattributed=true to return songs with no
session_id (files not in a YYMMDD folder)
- BandPage: show "Unattributed Recordings" section below sessions
so scanned files without a dated folder always appear
- watcher event_loop: promote all per-activity log lines from DEBUG
to INFO so they're visible in default Docker Compose log output;
log normalized path and skip reason for every activity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces flat song list with two tabs:
- By Date: session list (newest first) with weekday, date, label,
recording count — each row links to SessionPage
- Search: title/key/BPM range/tag filters, results as flat song list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows date, optional label/notes (admin-editable), and a flat list
of all recordings from that session. Each recording links to SongPage
and shows tags, key, BPM chips inline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
parse_rehearsal_date() extracts YYMMDD / YYYYMMDD from the file path
and get_or_create() a RehearsalSession. Both the watcher nc-upload
endpoint and the nc-scan endpoint now set song.session_id when a
dated folder is detected. Existing songs without a session_id are
back-filled on the next import of the same folder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GET /bands/{id}/sessions — list with recording counts, newest first
GET /bands/{id}/sessions/{sid} — session detail with flat song list
PATCH /bands/{id}/sessions/{sid} — admin: update label/notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces the rehearsal session concept: each YYMMDD folder in
Nextcloud maps to one RehearsalSession row (band_id + date unique).
Songs gain a nullable session_id FK and a tags TEXT[] column for
manual labeling (searchable by name, tag, BPM, key).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WaveSurfer makes plain browser fetches without Authorization headers,
causing 401s on the stream endpoint. The stream endpoint now accepts
a ?token= query param in addition to the Authorization header, and
proxies audio bytes directly through FastAPI instead of redirecting to
raw WebDAV (which would require a second Nextcloud auth challenge).
Falls back to nc_file_path if HLS transcoding hasn't run yet.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Watcher:
- Accept both NC 22+ (type="file_created") and older NC (subject="created_self")
so the upload filter works across all Nextcloud versions
- Add .opus to audio_extensions
- Fix tests: set nc.username on mocks, use realistic activity dicts with type field
- Add tests for old NC style, non-band path filter, normalize_nc_path, cursor advance
API:
- Fix internal.py title extraction: always use filename stem (was using
parts[-2] for >3-part paths, which gave folder name instead of song title)
- nc-scan now returns NcScanResult with folder, files_found, imported, skipped counts
instead of bare song list — gives the UI actionable feedback
Web:
- Show rich scan result message: folder scanned, count imported, count already registered
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add PATCH /bands/{id} endpoint for admins to update nc_folder_path
- Add band NC scan folder UI panel with inline edit
- Fix watcher: use activity type field (not human-readable subject) for upload detection
- Reorder watcher filters: audio extension check first, then band path, then type
- Add dark/light theme toggle using GitHub Primer-inspired CSS custom properties
- All inline styles migrated to CSS variables for theme-awareness
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two filter bugs caused every activity to be silently dropped:
1. Path format: Nextcloud activity API returns paths as
/username/files/bands/... but is_band_audio_path() was checking
parts[0] == "bands" after strip("/"), getting "username" instead.
Fix: normalize_nc_path() strips the /username/files/ and DAV prefixes
before any path checks are applied.
2. Subject names: filter was checking for "file_created"/"file_changed"
but Nextcloud uses "created_by"/"changed_by"/"created_self"/etc.
Fix: expanded _UPLOAD_SUBJECTS to cover all known NC activity subjects.
3. Expose NextcloudWatcherClient.username so normalize_nc_path() can
construct the correct prefix for the configured user.
4. Set watcher log level to DEBUG with per-activity skip reasons logged,
so the next filter edge case is immediately diagnosable. httpx/httpcore
kept at INFO/WARNING to avoid flooding the output.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
401 Unauthorized loop:
- client.ts had no 401 handler, leaving stale expired tokens in
localStorage. The PrivateRoute guard only checked token existence,
so the app would render but every API call would fail silently.
- Fix: on any 401 response, clear the token and redirect to /login.
504 Gateway Timeout on nc-scan:
- nginx default proxy_read_timeout is 60s. The scan endpoint makes
one Nextcloud request per audio file (list + metadata), which easily
exceeds that on larger libraries.
- Fix: add a dedicated location block for nc-scan with 300s timeouts.
General /api/ block gets explicit 60s timeouts for clarity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Race condition (worker "Job not found in DB"):
- RedisJobQueue.enqueue() was pushing job IDs to Redis immediately after
flush() but before the API transaction committed, so the worker would
read an ID that didn't exist yet in the DB from its own session.
- Fix: defer the Redis rpush until after session.commit() via a pending-
push list drained by get_session() after each successful commit.
- Worker: drain stale Redis queue entries on startup to clear any IDs
left over from previously uncommitted transactions.
- Worker: add 3-attempt retry with 200ms sleep when a job is not found,
as a safety net for any remaining propagation edge cases.
NC scan folder structure (YYMMDD rehearsal subfolders):
- Previously used dir_name as song title for all files in a subdirectory,
meaning every file got the folder name (e.g. "231015") as its title.
- Fix: derive song title from Path(sub_rel).stem so each audio file gets
its own name; use the file's parent path as nc_folder for version grouping.
- Rehearsal folder name stored in song.notes as "Rehearsal: YYMMDD".
- Added structured logging throughout the scan: entries found, per-folder
file counts, skip/create/import decisions, and final summary count.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>