71 Commits

Author SHA1 Message Date
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
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
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
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
Mistral Vibe
ff4985a719 feat: fix song view layout - align waveform top, scrollable comments, compose section always visible 2026-04-07 14:36:14 +00:00
Mistral Vibe
5690c9d375 fix: remove redundant time label from media controls
- Remove extra time display above buttons (already shown in waveform)
- Simplify transport section structure
- Keep buttons centered for clean UI
2026-04-07 14:22:14 +00:00
Mistral Vibe
b75c716dba feat: optimize media controls in song view
- Center media control buttons horizontally
- Remove tempo button (playspeed always 1x)
- Display time above button group for better UX
- Clean up unused SpeedSelector component
2026-04-07 14:13:14 +00:00
Mistral Vibe
3a36469789 Remove volume slider from song view
- Removed volume slider UI component
- Removed unused IconVolume function
- Volume is now always at 100% (default browser behavior)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-07 13:48:11 +00:00
Mistral Vibe
647bde2cf4 Optimize song view: remove queue section and center comments on mobile
- Removed 'Up next in Session' queue section to declutter mobile view
- Added responsive layout that stacks waveform and comments vertically on mobile
- Centered comment panel on mobile with max-width constraint
- Removed unused queueSongs variable

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-07 13:42:04 +00:00
Mistral Vibe
9a09798100 Remove Player icon from mobile bottom navigation
The Player icon was removed from the BottomNavBar component since
player functionality stops when switching screens, making the navigation
item non-functional and confusing for users.

Changes:
- Removed IconPlay component
- Removed Player NavItem from BottomNavBar
- Removed isPlayer state calculation
- Updated component to only show Library, Members, and Settings icons

This improves UX by removing a non-functional navigation option.
2026-04-07 13:35:09 +00:00
Mistral Vibe
6f0e2636d0 feat(mobile): Implement responsive mobile menu with band context preservation
This commit implements a comprehensive mobile menu solution that:

1. **Mobile Menu Components**:
   - Created TopBar.tsx with circular band switcher (mobile only)
   - Enhanced BottomNavBar.tsx with band-context-aware navigation
   - Updated ResponsiveLayout.tsx to integrate TopBar for mobile views

2. **Band Context Preservation**:
   - Fixed black screen issue by preserving band context via React Router state
   - Implemented dual context detection (URL params + location state)
   - Added graceful fallback handling for missing context

3. **Visual Improvements**:
   - Changed band display from square+text to perfect circle with initials only
   - Updated dropdown items to use consistent circular format
   - Improved mobile space utilization

4. **Debugging & Testing**:
   - Added comprehensive debug logging for issue tracking
   - Created test plans and documentation
   - Ensured all static checks pass (TypeScript + ESLint)

5. **Shared Utilities**:
   - Created utils.ts with shared getInitials() function
   - Reduced code duplication across components

Key Features:
- Mobile (<768px): TopBar + BottomNavBar + Main Content
- Desktop (≥768px): Sidebar (unchanged)
- Band context preserved across all mobile navigation
- Graceful error handling and fallbacks
- Comprehensive debug logging (can be removed in production)

Files Changed:
- web/src/utils.ts (new)
- web/src/components/TopBar.tsx (new)
- web/src/components/BottomNavBar.tsx (modified)
- web/src/components/ResponsiveLayout.tsx (modified)
- web/src/components/Sidebar.tsx (modified)

Documentation Added:
- implementation_summary.md
- refinement_summary.md
- black_screen_fix_summary.md
- test_plan_mobile_menu_fix.md
- test_plan_refinement.md
- testing_guide.md
- black_screen_debug.md

Resolves:
- Mobile menu band context loss
- Black screen on Library navigation
- Inconsistent band display format
- Missing mobile band switching capability

Breaking Changes: None
Backward Compatibility: Fully maintained

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-07 13:26:33 +00:00
Mistral Vibe
21c1673fcc WIP: Mobile optimizations - responsive layout with bottom nav 2026-04-07 12:27:32 +00:00
Mistral Vibe
fdf9f52f6f Rework song player view to match design system
- New split layout: waveform/transport/queue left, comment panel right
- Avatar pins above waveform positioned by timestamp with hover tooltips
- Transport bar: speed selector, ±30s skip, 46px amber play/pause, volume
- Comment compose: live timestamp pill, suggestion/issue/keeper tag buttons
- Comment list: per-author colour avatars, amber timestamp seek chips,
  playhead-proximity highlight, delete only shown on own comments
- Queue panel showing other songs in the same session
- Waveform colours updated to amber/dim palette (104px height)
- Add GET /songs/{song_id} endpoint for song metadata
- Add tag field to SongComment (model, schema, router, migration 0005)
- Fix migration 0005 down_revision to use short ID "0004"
- Fix ESLint no-unused-expressions in keyboard shortcut handler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 21:14:56 +02:00
Mistral Vibe
a8cbd333d2 Fix dev container startup for both API and web
API:
- Add python symlink (python3-slim has no bare 'python') so uv doesn't
  invalidate the baked venv on every container start
- Copy src/ before uv sync so hatchling installs rehearsalhub as a
  proper editable install (.pth pointing to /app/src) — previously
  sync ran with no source present, producing a broken empty wheel
- Remove ENV PYTHONPATH workaround (no longer needed with correct install)
- Add --reload-dir /app/src to scope uvicorn's file watcher to the
  mounted source directory

Web:
- Add COPY . . after npm install so index.html and vite.config.ts are
  baked into the image — without them Vite ignored port config and fell
  back to 5173 with no entry point

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:14:21 +02:00
Mistral Vibe
44503cca30 Add hot-reload dev environment via docker-compose.dev.yml
- web/Dockerfile: add `development` stage that installs deps and runs
  `vite dev --host 0.0.0.0`; source is mounted at runtime so edits
  reflect immediately without rebuilding the image
- web/vite.config.ts: read proxy target from API_URL env var
  (falls back to localhost:8000 for outside-compose usage)
- docker-compose.dev.yml: lightweight compose for development
  - api uses existing `development` target (uvicorn --reload)
  - web uses new `development` target with ./web mounted as volume
    and an anonymous volume to preserve container node_modules
  - worker and nc-watcher omitted (not needed for UI work)
  - separate pg_data_dev volume keeps dev DB isolated from prod

Usage:
  podman-compose -f docker-compose.dev.yml up --build

Frontend hot-reloads at http://localhost:3000
API auto-reloads at http://localhost:8000

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:49:33 +02:00
Mistral Vibe
659598913b Remove play button from Library session rows
Play buttons don't make sense at the session level since sessions
group multiple recordings. Removed from both session rows and
unattributed song rows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:44:02 +02:00
Mistral Vibe
013a2fc2d6 Fix Invalid Date for datetime strings from API
The API returns dates as "2024-12-11T00:00:00" (datetime, no timezone),
not bare "2024-12-11". Appending T12:00:00 directly produced an invalid
string. Use .slice(0, 10) to extract the date part first before parsing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:40:38 +02:00
Mistral Vibe
b09094658c Redesign Library view to match mockup spec
- Replace band-name header + tab structure (By Date / Search) with a
  unified Library view: title, inline search input, filter pills
  (All / instrument / Commented), and date-group headers
- Session rows now use the recording-row card style (play circle,
  mono filename, recording count)
- Move mini waveform bars from session list to individual recording
  rows in SessionPage, where they correspond to a single track
- Fix Invalid Date by appending T12:00:00 when parsing date-only
  ISO strings in both BandPage and SessionPage
- Update tests: drop tab assertions (TC-07), add Library heading
  (TC-08) and filter pill (TC-09) checks, update upload button label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 18:37:49 +02:00
Mistral Vibe
16bfdd2e90 Move band management into dedicated settings pages
- Add BandSettingsPage (/bands/:id/settings/:panel) with Members,
  Storage, and Band Settings panels matching the mockup design
- Strip members list, invite controls, and NC folder config from
  BandPage — library view now focuses purely on recordings workflow
- Add band-scoped nav section to AppShell sidebar (Members, Storage,
  Band Settings) with correct per-panel active states
- Fix amAdmin bug: was checking if any member is admin; now correctly
  checks if the current user holds the admin role
- Add 31 vitest tests covering BandPage cleanliness, routing, access
  control (admin vs member), and per-panel mutation behaviour
- Add test:web, test:api:unit, test:feature (post-feature pipeline),
  and ci tasks to Taskfile; frontend tests run via podman node:20-alpine
- Add README with architecture overview, setup guide, and test docs
- Add @testing-library/dom and @testing-library/jest-dom to package.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:55:10 +02:00
Mistral Vibe
5d7c613317 Component for invitations 2026-04-01 12:12:26 +02:00
Mistral Vibe
d61772207e Fix remaining TS6133 errors
- invites.ts: Remove unused bandId from listNonMemberUsers
- InviteManagement.tsx: Remove unused code (useEffect, queryClient, isRefreshing)

All TypeScript errors resolved!

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-01 11:55:38 +02:00
Mistral Vibe
1280020f83 Temporarily remove UserSearch - needs backend support
UserSearch component disabled until backend endpoint for listing non-member users is implemented.

Currently MVP includes:
- Backend APIs: list invites, revoke invites, get invite info 
- Frontend: InviteManagement component (list + revoke) 
- Note: UserSearch (admin search) disabled - needs backend support

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-01 11:53:30 +02:00
Mistral Vibe
2aa8ec8c59 Fix TypeScript build errors
- Remove unused imports in invites.ts
- Fix InviteManagement component (remove unused props, unneeded code)
- Fix BandPage.tsx (remove currentMemberId, remove UserSearch for now)
- Remove unused imports in types/invite.ts

Build errors resolved:
- TS6133: unused variables
- TS2304: missing variables
- TS2307: module not found

Note: UserSearch temporarily disabled - needs backend support for listing non-members

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-01 11:53:00 +02:00
Mistral Vibe
81c90222d5 Phase 2 frontend: Add React components for band invite management
Components created:
- InviteManagement.tsx: List pending invites, revoke functionality, copy links
- UserSearch.tsx: Search users to invite, role selection
- web/src/api/invites.ts: API wrappers for new endpoints
- web/src/types/invites.ts: TypeScript interfaces

UI enhancements:
- BandPage.tsx: Integrated new components, admin-only sections
- Members section now includes invite management for admins
- Search component for finding users to invite

Features:
- Admin can list, view, and revoke pending invites
- Copy invite links to clipboard
- Search existing users to invite (excluding current members)
- Real-time invite status (pending/expired/used)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-01 11:48:14 +02:00
Mistral Vibe
6dc191585c style: align all pages with CLAUDE.md design system
- Inputs: uniform padding (8px 12px), borderRadius 7, bg-inset background
- List rows/cards: bg-subtle background, border-subtle border (bg-inset was input-only)
- Invite/admin badge borders: use accent-border var instead of raw accent
- Section headers: 11px, weight 500, uppercase, 0.7px letter-spacing
- Notification/status banners: borderRadius 8
- Remove debug console.log statements from SettingsPage avatar upload flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 09:52:06 +02:00
Mistral Vibe
d9035acdff feat: app shell with sidebar + bug fixes
UI:
- Add persistent sidebar (210px) with band switcher dropdown, Library/Player/Settings nav, user avatar row, and sign-out button
- Align design system CSS vars to CLAUDE.md spec (#0f0f12 bg, #e8a22a amber accent, rgba borders/text)
- Remove light mode toggle (no light mode in v1)
- Homepage auto-redirects to first band; shows create-band form only when no bands exist
- Strip full-page wrappers from all pages (shell owns layout)
- Remove debug console.log statements from SongPage

Bug fixes:
- nginx: trailing slash on `location ^~ /api/v1/bands/` caused 301 redirect on POST, dropping the request body — removed trailing slash
- API: _member_from_request (used by nc-scan stream) only accepted Bearer token, not httpOnly cookie — add rh_token cookie fallback
- API: internal_secret config field now has a dev default so the service starts without INTERNAL_SECRET env var set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 09:43:47 +02:00
Mistral Vibe
c1941ed9ac security: httpOnly cookies, rate limiting, nginx headers, SSE sanitization
Auth / token storage:
- JWT is now set as an httpOnly Secure SameSite=Lax cookie on login
- Add POST /auth/logout endpoint that clears the cookie
- get_current_member falls back to rh_token cookie when no Authorization header
- WebSocket auth now accepts cookie (rh_token) or optional ?token= query param
- Frontend removes all localStorage JWT access; uses credentials:"include" on
  every fetch so the httpOnly cookie is sent automatically
- Replace clearToken() with logout() that calls the server logout endpoint
- Non-sensitive rh_session flag in localStorage used only for client-side routing

Rate limiting:
- Add slowapi>=0.1.9 dependency
- /auth/login limited to 10 req/min per IP
- /auth/register limited to 5 req/min per IP

Nginx security headers:
- Add X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
  X-XSS-Protection, Permissions-Policy to all responses

SSE error leakage:
- songs.py nc-scan/stream no longer leaks str(exc) to clients

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 21:11:53 +02:00
Mistral Vibe
cd1d098ca4 fix: avatar stale state, nginx intercept, and dev tooling
Frontend (SettingsPage):
- Sync avatarUrl state via useEffect when me.avatar_url changes after
  background refetch, so profile section never shows stale avatar
- Invalidate ["comments"] after upload/generate/remove so SongPage
  comment avatars update immediately instead of waiting for staleTime
- Fix Remove button: was sending avatar_url: undefined which JSON.stringify
  drops entirely, so the server never cleared it; now sends ""

nginx:
- Change /api/ and /ws/ locations to use ^~ prefix so the static-asset
  regex rule (~* \.(png|svg|ico)$) cannot intercept API paths; PNG/SVG
  avatar uploads were returning 404 from nginx in production
- Merge nc-scan 300s timeout into ^~ /api/v1/bands/ block
- Add client_max_body_size 10m (default 1MB was silently rejecting
  uploads before they reached FastAPI)

Dev tooling:
- Add docker-compose.dev.yml for hot-reload development workflow
- Add Taskfile.yml with dev, test, lint, migrate, and shell tasks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:41:32 +02:00
Mistral Vibe
cd6fabb31c fix: correct avatar upload and DiceBear URL version
- Add api.upload() to client.ts that passes FormData without setting
  Content-Type, letting the browser set multipart/form-data with the
  correct boundary (was causing 422 on the upload endpoint)
- Use api.upload() instead of api.post() for avatar file upload
- Update DiceBear URLs from v6 to 9.x in both frontend and backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:17:21 +02:00
Mistral Vibe
da051be673 fix: proper TypeScript type assertion for error object
- Use type assertion to define error object structure
- Use optional chaining for safe property access
- Maintain all error handling functionality

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-03-30 19:58:29 +02:00
Mistral Vibe
e4beebc57e fix: add type guard for detail property in error data
- Check for 'detail' property before accessing it
- Maintain all error handling functionality
- Ensure TypeScript type safety

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-03-30 19:57:24 +02:00
Mistral Vibe
b66660ee30 fix: TypeScript type safety for error object properties
- 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>
2026-03-30 19:56:41 +02:00
Mistral Vibe
a62297f2c4 fix: add comprehensive debugging and validation for avatar uploads
- 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>
2026-03-30 19:55:20 +02:00
Mistral Vibe
b20b98a17a fix: improve error handling for avatar uploads
- 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>
2026-03-30 19:49:39 +02:00
Mistral Vibe
48969e110a fix: remove incorrect headers parameter from API call
- 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>
2026-03-30 19:44:00 +02:00