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>
- 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>
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>
- 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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
- 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
- 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
- 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>
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>
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>