38 Commits

Author SHA1 Message Date
3a65cf436b fix(panels): eliminate getSession() from transcription store and cabinets panel
Same GoTrueClient lock contention fix as CCFilesPanel:
- transcriptionStore: add _accessToken/_userId state + setAuthInfo() action;
  replace all 6 getSession() calls (startSession, flushCanvasEvents, loadSessions,
  loadKeywordWatches, addKeywordWatch, deleteKeywordWatch, checkSegmentForKeywords)
  with stored values — zero getSession() calls remain in the store
- CCTranscriptionPanel: destructure accessToken from useAuth; sync both values into
  store via setAuthInfo() on every auth change; gate loadSessions on authUser.id
- CCCabinetsPanel: same pattern as CCFilesPanel — useAuth for token, useCallback on
  apiFetch/loadCabinets, gate effect on authUser.id

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 15:13:37 +01:00
6bd85671d2 fix(panels): bypass GoTrueClient lock — expose accessToken via AuthContext, gate panels on auth
Root cause: apiFetch called supabase.auth.getSession() on every API request, acquiring
the GoTrueClient internal lock. On refresh, concurrent mount of panels caused lock
contention — the loadCabinets/loadSessions fetch awaits hung, loading spinner never
cleared.

- AuthContext: add accessToken state, set/clear it alongside user on all auth events
- CCFilesPanel: apiFetch reads accessToken from AuthContext (no lock), loadCabinets
  effect gated on authUser.id (fires only after SIGNED_IN, session guaranteed valid)
- CCTranscriptionPanel: loadSessions effect gated on authUser.id for same reason

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 15:06:51 +01:00
5284d30f84 fix(panels): resolve sidebar refresh bugs — getUser→getSession, files double-call, hardcoded IP
- transcriptionStore: replace all supabase.auth.getUser() with getSession() so session
  restoration on page refresh does not race against GoTrue network validation
- CCFilesPanel: remove selectedCabinet from loadCabinets useCallback deps; use
  initialSelectionDone ref to prevent double-call on first mount
- CCTranscriptionPanel: replace hardcoded LAN IP with VITE_API_URL env var

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 13:41:25 +00:00
fb1795fd2b fix(phase-a): actually remove Neo provider JSX from App.tsx
Python heredoc string replacement missed the JSX wrapper lines due to
indentation mismatch. Rewrote App.tsx directly. Previous commit removed
the imports but left NeoUserProvider/NeoInstituteProvider in the JSX,
causing ReferenceError at runtime (Vite builds without tsc so it
compiled clean despite the undefined references).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 13:22:24 +00:00
7b546c933e feat(phase-a): remove Neo4j from startup chain, clean auth flow
- Remove NeoUserProvider + NeoInstituteProvider from App.tsx startup chain
- Strip user_db_name/school_db_name from CCUser; add school_id (Phase B wires it to Supabase)
- Remove DatabaseNameService from AuthContext and UserContext
- Remove provisionUser() call from login path; API endpoint preserved for Phase B decision
- Simplify UserContext.resolveProfile: fast-path JWT metadata then background Supabase fetch
- Replace user.user_db_name reads in singlePlayerPage + snapshotService with null-safe guards
- Add useDeviceContext hook (desktop/tablet/phone/iwb, persists to localStorage)

App now loads to dashboard without any Neo4j dependency at startup.
Canvas opens to blank TLDraw state; Phase B rebuilds navigation on Supabase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 13:06:39 +00:00
4139eb8fd3 fix: audio pipeline — 16 kHz AudioContext, 4096-sample buffering, SERVER_READY handshake
Root causes of disconnection and slow transcription:
- AudioWorklet was firing every 128 native samples (~48 kHz), sending
  ~375 tiny WebSocket messages/sec. Server flooded with tiny frames
  during silence → keepalive ping timed out → connection dropped.
- JS resampling 48 kHz → 16 kHz added CPU overhead on every chunk.
- Audio started on ws.onopen before server sent SERVER_READY, so early
  frames were dropped.

Fixes:
- audioWorklet.js: accumulate 4096 samples before posting (256 ms/chunk
  at 16 kHz, ~4 messages/sec), transfer ArrayBuffer zero-copy.
- transcriptionService: AudioContext({ sampleRate: 16000 }) — browser
  handles native resampling, no JS resampling needed. Remove
  resampleTo16kHZ entirely.
- Wait for SERVER_READY message before calling setupAudioProcessing().
- Send 'END_OF_AUDIO' string on stop so server can finalise last segment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 07:26:20 +00:00
308889937c fix: transcription segment dedup, store finalisation, and panel colours
- transcriptionService: track finalizedSegmentCount so only newly-final
  segments are emitted per WS message (was re-processing full array each
  time, causing the live segment to freeze in the completed list)
- transcriptionStore: saveSegment isFinal branch now appends the passed
  text directly instead of currentSegment (currentSegment was stale
  relative to the incoming final)
- CCTranscriptionPanel: record button colour changed from var(--color-text)
  to explicit #2563eb so it is visible in dark mode; completed segment
  backgrounds changed from hardcoded #fff to var(--color-muted) so text
  is readable in both themes; keyword Add button gets same blue fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 07:08:53 +00:00
0345258247 fix: transcription getUserMedia called before enumerateDevices
The old startTranscription() called enumerateDevices() first to find a
device ID, then getUserMedia. Without microphone permission, enumerateDevices()
returns devices with deviceId="" (empty string, falsy). This caused the
!this.selectedDeviceId check to bail out early, never calling getUserMedia,
never prompting the user for mic permission, and never creating the WebSocket.

Result: user clicks Start Recording → isRecording=true → button changes to
Stop Recording → but no mic prompt, no WebSocket, no transcription.

Fix: call getUserMedia directly (with optional echoCancellation/noiseSuppression
if no device pre-selected). getUserMedia triggers the browser permission prompt
automatically. Device selection is still honoured via exact deviceId constraint
when one has been explicitly chosen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 19:32:43 +00:00
83f17c9ab6 fix: UserContext fast-path initialization — no spinner on page refresh
Before: UserContext waited for a Supabase profiles table query (~200ms)
before setting isInitialized=true, causing FullContextRoutes to show
a spinner on every page refresh even with a valid stored session.

After: UserContext immediately builds a profile from auth metadata +
localStorage (synchronous, zero network calls) and sets isInitialized=true
before the Supabase query. The Supabase profiles table is still queried
in the background and the profile is updated when it returns with
authoritative user_db_name/school_db_name data.

On refresh with a stored session: auth metadata is available
synchronously via INITIAL_SESSION, so the page renders with no visible
spinner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 19:16:02 +00:00
5e21a2c7fc fix: add missing transcription entry to PANEL_DIMENSIONS
BasePanel reads PANEL_DIMENSIONS[currentPanelType] to get the panel
dimensions (topOffset, bottomOffset, width). The transcription panel
type was added to the panel list and render switch during CIS Phase 3C
but was never added to PANEL_DIMENSIONS. This caused a crash whenever
the BasePanel rendered with the transcription tab active:

  PANEL_DIMENSIONS["transcription"] === undefined
  undefined.topOffset → TypeError: Cannot read properties of undefined

This crash appeared as a topOffset error anywhere a Tldraw canvas was
rendered (singlePlayerPage, multiplayerUser, etc.).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 19:05:51 +00:00
e27d1f5c8d fix: prevent TLDraw topOffset crash — decouple store init from editor ready
Three changes to singlePlayerPage:

1. Store creation no longer waits for isEditorReady. The editor ref
   is already passed as optional to NavigationSnapshotService and
   loadNodeSnapshotFromDatabase, so we can create and populate the
   store without needing the editor to be mounted first.

2. <Tldraw> only renders when store is defined: {store && <Tldraw>}.
   Previously it rendered immediately with store={undefined}, which
   caused TLDraw to initialize with no backing store and crash
   reading topOffset on an unresolved internal object.

3. Loading guard also checks !user so the component never renders
   with a null profile before the redirect effect fires.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 18:17:52 +00:00
b1681d86fb fix: rewrite AuthContext to use canonical Supabase onAuthStateChange pattern
The previous implementation had two concurrent session recovery paths:
1. loadInitialSession() calling supabase.auth.getSession()
2. onAuthStateChange handling INITIAL_SESSION/SIGNED_IN

These raced unpredictably causing setLoading(false) to never fire in
certain interleavings, leaving the app permanently stuck on the spinner.

Fix: Remove loadInitialSession() entirely. Start loading=true. Rely
solely on onAuthStateChange — INITIAL_SESSION fires immediately with
the current session state, eliminating the race. One path, one
setLoading(false) call, no interleaving possible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 18:05:51 +00:00
3c0fd4f647 fix: FullContextRoutes no longer blocks on Neo4j contexts
Removed NeoUserContext and NeoInstituteContext from the gateway
spinner. They were blocking ALL authenticated routes until Neo4j
graph data was loaded, which took 1-8 seconds and caused persistent
spinners. Now only UserContext (Supabase profile, ~200ms) must be
ready before routes render. Each page handles its own graph data
loading state independently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:52:31 +00:00
679653c118 fix: workspace spinner — use profile (not null user) and add 8s context timeouts
singlePlayerPage was using useUser().user which is always null (no setter
in UserContext). This caused the redirect effect to always fire, making
the workspace completely inaccessible. Fixed by destructuring profile as
user, which IS properly set from the Supabase profiles query.

Also added 8s timeout to NeoUserContext.switchContext call to match the
NeoInstituteContext timeout fix, preventing infinite spinner if the
navigation API call hangs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:43:38 +00:00
ab1f8111f6 fix: add 8s timeout to NeoInstituteContext school node fetch
Races the SchoolNeoDBService.getSchoolNode() call against an 8-second
timeout. If Neo4j is slow or unavailable the workspace now loads within
seconds rather than waiting for the full axios 120s timeout. The context
degrades gracefully — workspace opens without institute data, error is
logged as a warning not an exception.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:28:57 +00:00
d3c2a9bdff security: pass TLSYNC_SECRET token in sync WebSocket URI
Reads VITE_TLSYNC_SECRET from env and appends ?token=... to the
/connect/:roomId WebSocket URI so tlsync server can authenticate connections.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:09:39 +00:00
06f761e750 feat(cis): add Keywords tab — watches, real-time detection, match log (Phase 3C)
- KeywordWatch and KeywordMatch interfaces in transcriptionStore
- loadKeywordWatches, addKeywordWatch, deleteKeywordWatch actions via API with JWT auth
- checkSegmentForKeywords: client-side detection on each final segment, logs events to backend
- clearKeywordMatches: resets session-scoped match list
- Keywords tab in CCTranscriptionPanel: add/delete watches, match log with timestamp
- Match count badge on Keywords tab when hits exist during recording
- Also fixes missing Close import that was present in summary modal
2026-05-21 12:21:17 +00:00
4d10d75003 fix(cis): fix JSX syntax errors and import paths for Phase 3 build
- Fix broken template literal in transcriptionStore.ts export function
- Fix broken regex in Content-Disposition parser
- Fix broken character class in filename sanitization
- Fix LLMConfigModal import path (5 levels up, not 6)
- Export button JSX properly nested in CCTranscriptionPanel
- Build now passes cleanly
2026-05-21 06:35:09 +00:00
cb8c2dab74 feat(cis): add LLM config modal, summary generation, keywords tab, and export button (Phase 3)
- LLMConfigModal: provider dropdown, model field, API key input (localStorage only)
- Summary generation: modal with 5 summary types, calls API, displays result
- Keywords tab: add/delete watches, real-time detection, event logging
- Export button: SRT/TXT/JSON download via API endpoint
- All Phase 3 frontend tasks complete
2026-05-20 22:57:06 +00:00
6bbed42f55 feat(cis): add Supabase integration, canvas event logger, and sessions tab (Phase 2)
- Connect transcriptionStore to Supabase (start/stop session, save segments)
- Add CanvasEventLogger for silent TLDraw activity tracking
- Add Sessions tab to CCTranscriptionPanel with past sessions list
- Auto-detect timetable context on panel mount
- Flush canvas events to API every 5 seconds during recording
2026-05-20 22:06:31 +00:00
2ee4e4afe7 feat(cis): add CCTranscriptionPanel Live tab to sidebar (Phase 1)
- Add CCTranscriptionPanel component with Live tab
- Add Zustand transcriptionStore for session state management
- Wire panel into BasePanel sidebar system
- Fix merged switch cases in getIconForPanel, getDescriptionForPanel, renderCurrentPanel
- Add VITE_WHISPERLIVE_URL to .env
2026-05-20 21:34:21 +00:00
Agent Zero
0f4956d4a4 fix: correct enrollment service method aliases
- Fix getEnrollmentRequests to use listEnrollmentRequests
- Fix respondToEnrollmentRequest to use respondToEnrollment
2026-02-26 17:47:37 +00:00
Agent Zero
d17acffd41 fix: add method aliases to timetableService for store compatibility
- Added getClasses, getClass, createClass, updateClass, deleteClass aliases
- Added getMyClasses, getMyTeachingClasses aliases
- Added timetable and lesson method aliases
- Added enrollment request method aliases
- Fixes 'getClasses is not a function' error in timetableStore
2026-02-26 07:49:53 +00:00
Agent Zero
fc6b6c3d10 fix: rename timetableServiceExports to timetableService to fix store imports
- timetableOnlyService: timetable-specific methods (line 134)
- timetableService: combined export with class/lesson/enrollment methods (line 328)
- This fixes 'getClasses is not a function' and 'getMyClasses is not a function' errors
2026-02-26 07:44:34 +00:00
Agent Zero
51c0bfaeaf fix: correct MUI icon usage in Header and NotFoundPublic
- Header.tsx: Use <MenuIcon /> instead of <Menu /> component
- NotFoundPublic.tsx: Use <ErrorOutlineIcon /> instead of <ErrorOutline />

Resolves: Menu open prop error and ErrorOutline undefined error
2026-02-26 07:38:05 +00:00
Agent Zero
856816c36a fix: TimetablePage fetchTimetableDetail -> fetchTimetable and add null check for currentLessons 2026-02-26 07:36:40 +00:00
Agent Zero
067df34c50 fix: correct Material UI icon naming in Header.tsx and update timetable components
- Fix icon naming: remove 'Icon' suffix from MUI icon components in Header.tsx
  (AccessTime, Close, Person, School, Schedule, Class, Book, Settings, Student, Login, Logout)
- Update timetable components to use UserContext instead of ProfileContext
- Fix timetableService naming collision and circular reference
- Update various components for consistency
2026-02-26 07:28:47 +00:00
Agent Zero
00a6f941c7 fix(contexts): replace ProfileContext with UserContext in timetable pages 2026-02-26 07:09:43 +00:00
Agent Zero
c7207eb805 fix(icons): replace lucide-react with @mui/icons-material
Replace lucide-react icon imports with @mui/icons-material equivalents
across all timetable pages and common Modal component.

Icons replaced:
- lucide-react Check, X, UserPlus, Users, Filter, Search -> MUI equivalents
- lucide-react ChevronDown, ChevronLeft, ChevronRight -> MUI equivalents
- lucide-react BookOpen, Clock, GraduationCap, School -> MUI equivalents
- lucide-react Calendar, MapPin, Edit, Trash2, Plus -> MUI equivalents
- lucide-react ArrowLeft, FileText, CheckCircle, XCircle -> MUI equivalents

Fixes build error: Failed to resolve import 'lucide-react'
2026-02-26 07:06:11 +00:00
Agent Zero
a64836c94a feat(timetable): add navigation links to Header component 2026-02-26 03:28:08 +00:00
Agent Zero
11c139b410 feat(timetable): add page components, services, stores and types
- Add timetable page components:
  - ClassesListPage.tsx (browse and search classes)
  - MyClassesPage.tsx (student enrolled classes)
  - EnrollmentRequestsPage.tsx (teacher approval interface)
  - TimetablePage.tsx (weekly schedule view)
  - LessonViewPage.tsx (TLDraw-integrated lesson view)
- Add timetableService.ts for API communication
- Add timetableStore.ts for state management
- Add timetable.types.ts for TypeScript definitions
- Add common components (LoadingSpinner, ErrorMessage, EmptyState)
- Add .env.development with local development configuration
2026-02-26 03:27:46 +00:00
Agent Zero
d5c53f2c17 feat(timetable): add frontend routes and comprehensive documentation
- Add TIMETABLE_FEATURE.md with complete API integration guide
- Verify AppRoutes.tsx includes all timetable routes:
  - /timetable (TimetablePage)
  - /classes (ClassesListPage)
  - /my-classes (MyClassesPage)
  - /enrollment-requests (EnrollmentRequestsPage)
  - /lessons/:lessonId (LessonViewPage)
- Routes configured with PrivateRoute for authentication
- Documentation includes:
  - Component reference (teacher and student views)
  - Route configuration
  - State management with Zustand stores
  - API integration examples
  - Environment variables
  - Demo credentials
  - TLDraw integration for Lesson View
2026-02-26 03:27:28 +00:00
Agent Zero
341c551ea5 refactor: simplify UserContext and supabaseClient
- Refactored UserContext.tsx (67 lines simplified)
- Simplified supabaseClient.ts (82 lines reduced)
- Updated .env.development for local dev
2026-02-23 17:48:14 +00:00
5c100a015d feat: update source files, Dockerfile, vite config and service worker 2026-02-21 16:25:42 +00:00
5e5aad54cb chore: remove dist artifacts, untrack .env, add vite-env types, ignore package-lock 2026-02-21 16:24:29 +00:00
3472f203b9 update api base 2025-11-20 11:01:55 +00:00
3b4876793e latest 2025-11-14 14:47:26 +00:00
8a7ab3ac24 Initial commit 2025-07-11 13:21:49 +00:00