Commit Graph

16 Commits

Author SHA1 Message Date
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
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
Steffen Schuhmann
ececcb24f6 fix: remove unused NcScanResult interface in BandPage (TS error)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 15:14:18 +02:00
Steffen Schuhmann
7cad3e544a feat: incremental SSE scan, recursive NC traversal, custom folder support
- 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>
2026-03-29 15:09:42 +02:00
Steffen Schuhmann
dc6dd9dcfd fix: scan visibility, NC folder validation, watcher logging
- 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>
2026-03-29 14:11:07 +02:00
Steffen Schuhmann
25502458d0 feat(web): BandPage — By Date and Search tabs
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>
2026-03-29 13:45:12 +02:00
Steffen Schuhmann
47bc802775 fix: robust NC activity filter, title extraction, scan result detail
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>
2026-03-29 12:26:58 +02:00
Steffen Schuhmann
fbac62a0ea feat: band NC folder config, fix watcher event filter, add light/dark theme
- 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>
2026-03-29 00:29:58 +01:00
Steffen Schuhmann
f7be1b994d Initial commit: RehearsalHub POC
Full-stack self-hosted band rehearsal platform:

Backend (FastAPI + SQLAlchemy 2.0 async):
- Auth with JWT (register, login, /me, settings)
- Band management with Nextcloud folder integration
- Song management with audio version tracking
- Nextcloud scan to auto-import audio files
- Band membership with link-based invite system
- Song comments
- Audio analysis worker (BPM, key, loudness, waveform)
- Nextcloud activity watcher for auto-import
- WebSocket support for real-time annotation updates
- Alembic migrations (0001–0003)
- Repository pattern, Ruff + mypy configured

Frontend (React 18 + Vite + TypeScript strict):
- Login/register page with post-login redirect
- Home page with band list and creation form
- Band page with member panel, invite link, song list, NC scan
- Song page with waveform player, annotations, comment thread
- Settings page for per-user Nextcloud credentials
- Invite acceptance page (/invite/:token)
- ESLint v9 flat config + TypeScript strict mode

Infrastructure:
- Docker Compose: PostgreSQL, Redis, API, worker, watcher, nginx
- nginx reverse proxy for static files + /api/ proxy
- make check runs all linters before docker compose build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 21:53:03 +01:00