diff --git a/src/features/library/LibraryPage.tsx b/src/features/library/LibraryPage.tsx index bd01e15..edd54e8 100644 --- a/src/features/library/LibraryPage.tsx +++ b/src/features/library/LibraryPage.tsx @@ -381,7 +381,7 @@ function HomeView({ currentUserId: string onShared: () => void }) { - const { groups, processes, settings, createGroup, isLoading } = useLibraryStore() + const { groups, processes, settings, createGroup, isLoading, error, load } = useLibraryStore() const { user } = useAuth() const [search, setSearch] = useState('') const [activeTags, setActiveTags] = useState([]) @@ -422,6 +422,24 @@ function HomeView({ ) } + // Error state: el load() falló o se colgó (timeout) — distinto de "biblioteca vacía" + if (error && groups.length === 0 && processes.length === 0) { + return ( +
+ +

No se pudo cargar la biblioteca

+

{error}

+ +
+ ) + } + // Empty state: sin grupos ni procesos (ya terminó de cargar) if (groups.length === 0 && processes.length === 0) { return ( diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts index 57b2357..8825624 100644 --- a/src/lib/supabase.ts +++ b/src/lib/supabase.ts @@ -8,3 +8,14 @@ if (!supabaseUrl || !supabaseAnonKey) { } export const supabase = createClient(supabaseUrl, supabaseAnonKey) + +// supabase.auth.getSession() puede colgarse indefinidamente si el cliente queda +// esperando un lock interno (navigator.locks) que nunca se libera. Esta barrera +// nunca bloquea más de 4s: si no resuelve a tiempo, sigue de largo sin sesión confirmada +// en vez de dejar la UI con un spinner/skeleton infinito. +export async function getSessionSafe(): Promise { + await Promise.race([ + supabase.auth.getSession(), + new Promise((resolve) => setTimeout(resolve, 4000)), + ]) +} diff --git a/src/store/library-store.ts b/src/store/library-store.ts index e694dee..4709827 100644 --- a/src/store/library-store.ts +++ b/src/store/library-store.ts @@ -1,6 +1,6 @@ import { create } from 'zustand' import { v4 as uuidv4 } from 'uuid' -import { supabase } from '@/lib/supabase' +import { getSessionSafe } from '@/lib/supabase' import { supabaseProcessRepo } from '@/persistence/supabase/process-repo' import { supabaseGroupRepo } from '@/persistence/supabase/group-repo' import { supabaseSettingsRepo } from '@/persistence/supabase/settings-repo' @@ -12,6 +12,7 @@ interface LibraryState { processes: Process[] settings: AppSettings isLoading: boolean + error: string | null load: () => Promise @@ -36,27 +37,32 @@ export const useLibraryStore = create((set, get) => ({ processes: [], settings: DEFAULT_SETTINGS, isLoading: false, + error: null, load: async () => { - set({ isLoading: true }) + set({ isLoading: true, error: null }) try { // Barrera única: garantiza que el cliente Supabase tenga la sesión restaurada - // antes de salir a buscar datos en paralelo (evita 0 filas por RLS en el primer load) - const { data: sessionData } = await supabase.auth.getSession() - console.log('[library-store] load session:', { - hasSession: !!sessionData.session, - userId: sessionData.session?.user?.id ?? 'NULL', - }) + // antes de salir a buscar datos en paralelo (evita 0 filas por RLS en el primer load). + // Con timeout defensivo: si el cliente se cuelga, no bloquea la UI más de 4s. + await getSessionSafe() - const [groups, processes, settings] = await Promise.all([ - supabaseGroupRepo.getAll(), - supabaseProcessRepo.getAll(), - supabaseSettingsRepo.get(), + // Timeout global: si el cliente Supabase queda colgado (ej. lock interno nunca liberado), + // isLoading nunca debe quedar en true para siempre — la UI debe poder mostrar error y reintentar. + const [groups, processes, settings] = await Promise.race([ + Promise.all([ + supabaseGroupRepo.getAll(), + supabaseProcessRepo.getAll(), + supabaseSettingsRepo.get(), + ]), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Timeout: Supabase no respondió en 15s')), 15_000) + ), ]) - set({ groups, processes, settings, isLoading: false }) + set({ groups, processes, settings, isLoading: false, error: null }) } catch (err) { console.error('Error al cargar la biblioteca:', err) - set({ isLoading: false }) + set({ isLoading: false, error: err instanceof Error ? err.message : 'Error al cargar la biblioteca' }) } },