- Library view redesigned to match mockup: unified view with search
input, filter pills, date-group headers, and recording-row style
- Mini waveform bars moved to SessionPage individual recording rows
- Play buttons removed from Library session rows
- Fixed Invalid Date for API datetime strings (slice to date part)
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>
Invited members have no Nextcloud credentials of their own — stream and
waveform endpoints now use the file uploader's NC credentials instead of
the current member's. Falls back to the current member if uploaded_by is
null.
The invite listing/info endpoints were comparing timezone-aware
expires_at values against naive datetime.now(), causing a TypeError (500).
Fixed by using datetime.now(timezone.utc) throughout bands.py and
invites.py.
Also removes leftover debug logging from versions.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added import for BandRepository in stream_version function
- Import at function level to avoid circular import issues
- Now debug logging will work correctly
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Fixed undefined 'song' and 'role' variables in stream_version
- Now properly gets song and role from _get_version_and_assert_band_membership
- Debug logging will now work correctly
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- bands.py: Change permission from admin-only to member-only
- Line 33: Changed 'role != "admin"' to 'role is None'
- Now regular band members can list invites
- versions.py: Add debug logging for audio stream access
- Added logging to track user access and membership status
- Helps diagnose why users get 403 on /versions/{id}/stream
These changes should resolve:
- 403 on /bands/{id}/invites (invited users)
- 403 on /versions/{id}/stream (audio playback)
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Identified root cause: list_invites endpoint requires admin role
- Should allow regular members to see invites
- Found bug in bands.py line 33
- Includes recommended fixes and action plan
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Complete summary of band invitation system implementation
- Captures all phases: analysis, backend, frontend, testing
- Documents technical decisions, file changes, and current state
- Includes unresolved issues and next steps
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
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>
- Summary of all changes made
- Syntax verification results
- Test coverage details
- API endpoint documentation
- Security considerations
- Metrics and checklist
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
Implements core invite management features for band admins:
- GET /bands/{band_id}/invites - List all invites for a band (admin only)
- DELETE /invites/{invite_id} - Revoke pending invite (admin only)
- GET /invites/{token}/info - Get invite details (public)
Backend changes:
- Add invites router with 3 endpoints
- Update BandRepository with get_invites_for_band and get_invite_by_id methods
- Add new schemas for invite listing and info
- Register invites router in main.py
Tests:
- Integration tests for all 3 endpoints
- Permission tests (admin vs non-admin)
- Edge cases (not found, expired, etc.)
This addresses the core requirements:
- Admins can see pending invites
- Admins can revoke pending invites
- Users can view invite details before accepting
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
- Deep dive into existing band invitation implementation
- Identified gaps in current system (invite listing, revocation, user search)
- Created detailed architecture analysis and design options
- Documented comprehensive implementation plan with phases
- Includes backend endpoints, frontend components, and testing strategy
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>
Added INTERNAL_SECRET environment variable to the api service in docker-compose.yml.
This fixes the pydantic validation error that was causing the API to fail to start.
The INTERNAL_SECRET is required for internal service-to-service communication
(nc-watcher → API) and was missing from the container environment.
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