diff --git a/DEBUGGING_NAVIGATION.md b/DEBUGGING_NAVIGATION.md new file mode 100644 index 0000000..cc4aa7d --- /dev/null +++ b/DEBUGGING_NAVIGATION.md @@ -0,0 +1,61 @@ +# Navigation Issue Debugging Guide + +## The Problem +- User finishes recording a clip +- Toast appears: "Clip created! Redirecting to your clip..." +- Navigation to `/clip/{id}` doesn't happen +- Browser console shows: "Throttling navigation to prevent the browser from hanging" + +## What I've Added + +### Console Logging in Capture.tsx +Look for these logs when recording: +1. `handleUploadDone called` - When upload finishes +2. `handleRecordingComplete called` - When full processing completes +3. `saveRecordingToClip called` - When we try to save and navigate +4. `Clip saved to database, navigating to: /clip/xxx` - Just before navigate() +5. `Calling navigate() now` - Right before the navigate call +6. `navigate() called` - Right after the navigate call + +### Console Logging in useUser.ts +Look for these logs: +1. `[useUser] Effect running` - When hook mounts/re-runs +2. `[useUser] syncUser called` - When auth sync happens +3. `[useUser] Auth state changed: {event}` - When auth state changes +4. `[useUser] Navigating to login` - If redirecting to login + +## How to Debug + +### Step 1: Record a clip and watch console +1. Start recording +2. Stop recording (after 3s) +3. Watch the console logs +4. Note the sequence of events + +### Step 2: Check for navigation throttling +Look for: +- Is `navigate()` being called multiple times rapidly? +- Is `[useUser]` running repeatedly? +- Are there auth state changes happening? + +### Step 3: Identify the blocker +- If you see "Calling navigate() now" but no actual navigation, something is blocking it +- If you see multiple rapid navigate calls, we have a loop +- If you see auth state changes, they might be interfering + +## Expected Flow (Normal) +1. User stops recording +2. Video uploads to Livepeer +3. `handleUploadDone` called → `saveRecordingToClip` → `navigate('/clip/xxx')` +4. ClipView page loads +5. Video processes in background +6. Eventually `handleRecordingComplete` called → skipped (clipSavedRef is true) + +## Fixes Applied +1. **Race condition guard**: Set `clipSavedRef.current = true` BEFORE calling async saveRecordingToClip +2. **Double-callback prevention**: Check flag in both handleUploadDone and handleRecordingComplete +3. **Error handling**: Reset flag on error so user can retry +4. **Logging**: Added extensive console.logs to trace execution + +## Next Steps +Run the app, record a clip, and paste the console logs here to see what's happening. diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts index 49ae92d..e3607eb 100644 --- a/src/hooks/useUser.ts +++ b/src/hooks/useUser.ts @@ -41,15 +41,20 @@ export function useUser(options: UseUserOptions = {}): UseUserReturn { useEffect(() => { let mounted = true; + let hasNavigated = false; + + console.log('[useUser] Effect running, allowSignedOff:', allowSignedOff); const syncUser = async () => { + console.log('[useUser] syncUser called, mounted:', mounted, 'hasNavigated:', hasNavigated); try { // Get current session const { data: { session: currentSession }, error: sessionError } = await supabase.auth.getSession(); if (sessionError) { console.error('Error getting session:', sessionError); - if (!allowSignedOff) { + if (!allowSignedOff && !hasNavigated) { + hasNavigated = true; navigate('/login'); } return; @@ -62,7 +67,9 @@ export function useUser(options: UseUserOptions = {}): UseUserReturn { setUser(null); setLoading(false); - if (!allowSignedOff) { + if (!allowSignedOff && !hasNavigated) { + console.log('[useUser] Navigating to login (session error)'); + hasNavigated = true; navigate('/login'); } } @@ -161,7 +168,8 @@ export function useUser(options: UseUserOptions = {}): UseUserReturn { console.error('All upsert attempts failed:', lastError); setLoading(false); - if (!allowSignedOff) { + if (!allowSignedOff && !hasNavigated) { + hasNavigated = true; navigate('/login'); } } @@ -171,7 +179,8 @@ export function useUser(options: UseUserOptions = {}): UseUserReturn { if (mounted) { setLoading(false); - if (!allowSignedOff) { + if (!allowSignedOff && !hasNavigated) { + hasNavigated = true; navigate('/login'); } } @@ -181,7 +190,8 @@ export function useUser(options: UseUserOptions = {}): UseUserReturn { syncUser(); // Listen for auth state changes - const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { + const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => { + console.log('[useUser] Auth state changed:', event, 'mounted:', mounted); if (!mounted) return; // Re-run the sync when auth state changes @@ -192,7 +202,8 @@ export function useUser(options: UseUserOptions = {}): UseUserReturn { mounted = false; subscription.unsubscribe(); }; - }, [allowSignedOff, navigate]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [allowSignedOff]); // navigate is stable and should not be in deps return { user, loading, session }; } diff --git a/src/pages/Capture.tsx b/src/pages/Capture.tsx index c75287e..229a7ac 100644 --- a/src/pages/Capture.tsx +++ b/src/pages/Capture.tsx @@ -365,11 +365,7 @@ export default function Capture() { const saveRecordingToClip = useCallback( async (result: StudioRecordingResult, complete: boolean) => { try { - // Skip if we already saved the clip early (via progress callback) - if (clipSavedRef.current) { - console.log("Clip already saved in progress callback, skipping complete handler"); - return; - } + console.log("saveRecordingToClip called", { complete, hasRawUrl: !!result.rawUploadedFileUrl }); // Get session ID const { data: sessionData, error: sessionError } = await supabase @@ -413,17 +409,22 @@ export default function Capture() { tIndexList: canvasParams?.t_index_list || [], }); - // Set flag to avoid double saving on uploadDone/complete - clipSavedRef.current = true; + const clipUrl = `/clip/${clip.id}`; + console.log("Clip saved to database, navigating to:", clipUrl); toast({ title: "Clip created!", description: "Redirecting to your clip...", }); - navigate(`/clip/${clip.id}`); + // Navigate immediately - don't wait for anything else + console.log("Calling navigate() now"); + navigate(clipUrl); + console.log("navigate() called"); } catch (error: unknown) { console.error("Error saving clip to database:", error); + // Reset the flag on error so user can try again + clipSavedRef.current = false; toast({ title: "Error creating clip", description: error instanceof Error ? error.message : String(error), @@ -440,8 +441,17 @@ export default function Capture() { const handleRecordingComplete = useCallback( async (result: StudioRecordingResult) => { + console.log("handleRecordingComplete called", { result, clipSaved: clipSavedRef.current }); + // Don't call saveRecordingToClip if we already saved via handleUploadDone + if (clipSavedRef.current) { + console.log("Skipping handleRecordingComplete because clip already saved"); + return; + } + + // Set flag immediately to prevent race condition + clipSavedRef.current = true; console.log("Recording complete, saving to database...", result); - saveRecordingToClip(result, true); + await saveRecordingToClip(result, true); }, [saveRecordingToClip] ); @@ -449,14 +459,24 @@ export default function Capture() { // StudioRecorder callback: Handle upload completion and optimistically create clip const handleUploadDone = useCallback( async (result: StudioRecordingResult) => { + console.log("handleUploadDone called", { hasRawUrl: !!result.rawUploadedFileUrl, clipSaved: clipSavedRef.current }); + // Only save early if we have rawUploadedFileUrl if (!result.rawUploadedFileUrl) { console.log("No rawUploadedFileUrl, will wait for full completion"); return; } + // Don't save if already saved + if (clipSavedRef.current) { + console.log("Skipping handleUploadDone because clip already saved"); + return; + } + + // Set flag immediately to prevent race condition + clipSavedRef.current = true; console.log("Upload complete, saving clip optimistically with raw URL:", result.rawUploadedFileUrl); - saveRecordingToClip(result, false); + await saveRecordingToClip(result, false); }, [saveRecordingToClip] );