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 <[email protected]>
This commit is contained in:
2026-05-25 15:06:51 +01:00
co-authored by Claude Sonnet 4.6
parent 5284d30f84
commit 6bd85671d2
3 changed files with 27 additions and 10 deletions
@@ -40,7 +40,7 @@ import ImageIcon from '@mui/icons-material/Image';
import DescriptionIcon from '@mui/icons-material/Description';
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
import { useTLDraw } from '../../../../../contexts/TLDrawContext';
import { supabase } from '../../../../../supabaseClient';
import { useAuth } from '../../../../../contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
import {
calculateDirectoryStats,
@@ -92,7 +92,8 @@ interface FileListResponse {
}
export const CCFilesPanel: React.FC = () => {
const { tldrawPreferences, authToken } = useTLDraw() as { tldrawPreferences?: { colorScheme?: 'light' | 'dark' | 'system' }, authToken?: string };
const { tldrawPreferences } = useTLDraw() as { tldrawPreferences?: { colorScheme?: 'light' | 'dark' | 'system' } };
const { user: authUser, accessToken } = useAuth();
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const [cabinets, setCabinets] = useState<Cabinet[]>([]);
const [selectedCabinet, setSelectedCabinet] = useState<string>('');
@@ -144,14 +145,14 @@ export const CCFilesPanel: React.FC = () => {
const apiFetch = useCallback(async (url: string, init?: RequestInitLike) => {
const headers: HeadersInitLike = {
'Authorization': `Bearer ${(await supabase.auth.getSession()).data.session?.access_token || authToken || ''}`,
'Authorization': `Bearer ${accessToken || ''}`,
...(init?.headers || {})
};
const fullUrl = url.startsWith('http') ? url : `${API_BASE}${url}`;
const res = await fetch(fullUrl, { ...(init || {}), headers });
if (!res.ok) throw new Error(await res.text());
return res.json();
}, [authToken, API_BASE]);
}, [accessToken, API_BASE]);
const loadCabinets = useCallback(async () => {
setLoading(true);
@@ -207,8 +208,11 @@ export const CCFilesPanel: React.FC = () => {
}, [currentPage, itemsPerPage, sortBy, sortOrder, searchTerm, apiFetch, currentDirectoryId]);
useEffect(() => {
loadCabinets();
}, [loadCabinets]);
if (authUser?.id) {
initialSelectionDone.current = false;
loadCabinets();
}
}, [loadCabinets, authUser?.id]);
// Main loading effect - handles pagination, sorting, cabinet changes, directory navigation
useEffect(() => {