- .cc-shape-header and .cc-shape-toolbar button were missing pointer-events:all,
causing toolbar buttons to be unclickable inside tldraw canvas
- Replace color:'white' on lock indicator with var(--cc-text-inverse)
Commit 669a661 had a stale staged deletion of cc-design-system.css and
reverted three other CSS-variable files. This commit restores them to
the state from cd37771 (adopt --cc-* CSS variable system).
Replaces JSON.stringify snapshot comparison with a store.listen() dirty flag.
Eliminates 5-15ms main-thread serialize on every poll tick when canvas is idle.
F3: authenticated unknown routes now redirect to /dashboard instead of
rendering private NotFound.
F2: Header shows a warning Chip with the current onboarding next_step
when the user has not completed onboarding (next_step != 'ready').
Chip navigates to /dashboard on click. Hidden on xs screens.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Use GET /me/bootstrap as the primary authenticated state source per ADR-0003. AuthContext now fetches and exposes typed bootstrapData, Header and class/detail admin checks use bootstrap permissions/roles, dashboard surfaces onboarding/calendar/timetable/graph status, and graph navigation derives school setup state from bootstrap instead of /school/status.
Refs: t_44353587
- MyClassesPage: fix all classItem.class?.* references to flat field access (class_code, name, description etc)
- MyClassesPage: fix class detail link /timetable/classes/:id -> /classes/:id (actual route)
- CCGraphNavPanel: SubjectClass click navigates to /classes/:id since no Neo4j SubjectClass nodes exist yet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Header: trim menu to My Work section (My Lessons, My Classes, Lesson Plans), School Admin (gated), Platform Admin (gated)
- MyClassesPage: fix loading/error state destructure (classesLoading not myClassesLoading)
- NotFound: fix ErrorOutline -> ErrorOutlineIcon to prevent 404 page crash
- timetableService: getMyClasses now calls both /me/teacher and /me/student, merges with role annotation
- timetableStore: myClasses type updated to ClassWithRole[]
- timetable.types: add ClassWithRole interface and code/institute_id optional fields to Class
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix: skip canvas navigation for Section-type nodes (was crashing on cc-section-node)
- Add By Class / By Term toggle to My Timetable section (mirrors calendar Generic/Academic)
- By Class: pre-loaded SubjectClass children shown immediately on expand
- By Term: lazy-loads AcademicTerms -> Weeks -> TaughtLessons when switched to term view
- displayChildren respects timetableView context for the timetable section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each lesson card gets a LibraryAdd button that opens a searchable
plan picker. Falls back gracefully to empty state when no plans exist,
with a link to /lesson-plans to create one.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add useNavigate + Launch icon to TreeItem; timetable section shows a
launch button routing to /my-lessons, classes section routes to /my-classes.
Both buttons only appear when the section status is populated.
Exclude classes section from the unlinked/pending icon fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bugs fixed:
- 'cc-teacher-timetable-node' missing from NODE_TYPE_THEMES caused
Cannot read properties of undefined (reading 'headerColor') crash
when clicking My Timetable
- 'cc-journal-node' and 'cc-planner-node' had no shape utils registered,
causing 'No shape util found for type' error on Journal/Planner click
- Added null safety (?? fallback) to getNodeTheme to prevent future crashes
from any other unmapped type
- Removed AcademicWeek from canExpand exclusion so weeks can be expanded
to show individual academic days
Added:
- CCJournalNodeShapeUtil and CCPlannerNodeShapeUtil (stub shapes)
- CCJournalNodeProps and CCPlannerNodeProps types
- Journal/Planner added to CCNodeTypes, ccGraphShapeProps, NODE_TYPE_THEMES
- 'cc-department-structure-node' mapping added to NODE_TYPE_THEMES
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds LessonPlansPage (card grid with search/filter, create dialog) and
LessonPlanDetailPage (structured editor with objectives, activities, Bloom
taxonomy tags, per-field AI suggest via ✨ button, and auto-save).
Routes: /lesson-plans and /lesson-plans/:planId wired into AppRoutes.
Nav: Lesson Plans item added to Header menu under Timetable & Classes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SchoolOnboardingWizard.tsx: new 3-step wizard (GAIS search → confirm →
calendar setup) triggered from the nav panel school section when no school
is linked. Calls GET /school/search for live school lookup and
POST /school/register to create the institute record.
- CCGraphNavPanel.tsx: add showOnboard button for no_school state,
onOnboardSchool context action, wire up SchoolOnboardingWizard state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
navigationStore: rewritten off Neo4j db names — Supabase whiteboard_rooms table,
setAuthInfo(token, userId) pattern, auto-creates default room per context on first use
snapshotService: rewritten to Supabase Storage REST (/storage/v1/object/authenticated/cc.users/…),
setAccessToken() instance method, static methods take accessToken not dbName
AuthContext/NeoUserContext: auth injected into nav store, no Neo4j db names required
singlePlayerPage: loadNodeData no longer calls Neo4j; snapshot wired via accessToken
navigation types: NeoGraphNode updated for Supabase-backed tree structure
transcriptionStore/Service: getSession() removed, accessToken via AuthContext
LLMConfigModal: auth context wiring fixes
GraphNavigator/GraphSidebar: updated nav components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
- 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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>