Files
ForgeLM/.agents/skills/debugging-protocol/languages/frontend.md
T

15 KiB
Raw Blame History

Frontend Debugging Module

Language-specific debugging guide for frontend projects (Vue 3, React, browser-based SPAs). Use alongside the main Debugging Protocol.


Toolchain Reference

Tool Purpose When to Use
Chrome DevTools — Console Runtime errors, warnings, console.debug traces Always first — fastest signal
Chrome DevTools — Network Request waterfall, response status, payload shape Data not rendering, 401/403/500, CORS
Chrome DevTools — Elements Computed styles, DOM structure, CSS specificity Styling / layout issues
Chrome DevTools — Performance Frame rate, scripting time, rendering phases Jank, slow interactions
Chrome DevTools — Application localStorage, sessionStorage, cookies, service workers Auth state, cached flags, stale SW
Vue DevTools (browser extension) Component tree, Pinia state, router history, event timeline Vue-specific state/lifecycle issues
router.afterEach / beforeEach logging Navigation guard execution trace Routing bugs, blank screens, redirect loops
onMounted / useEffect logging Component lifecycle confirmation Component "never mounts" symptoms
vite dev --force Force-clear Vite module cache Stale build artifacts after dependency changes
npx vue-tsc -b Full template type checking Type errors that only surface at build time

Phase 1: Initialize Session — Frontend Context

Add these fields to the debugging session document:

### Frontend Context
- **Framework version:** (e.g. Vue 3.5.x, React 19.x)
- **Bundler:** (e.g. Vite 7.x)
- **CSS framework:** (e.g. Tailwind v4, none)
- **State management:** (e.g. Pinia 3.x, Redux, Zustand)
- **Router:** (e.g. Vue Router 5.x, React Router 7.x)
- **Browser:** (exact version — behavior varies across engines)
- **PWA/Service Worker:** yes / no (stale caches are a common red herring)
- **Auth provider:** (e.g. Supabase Auth, Firebase Auth, custom JWT)

Phase 3: Hypothesis Categories

Common frontend-specific hypothesis categories:

Category Example Hypotheses
Routing & Navigation Guard blocks route; redirect loop between auth/onboarding; router.replace() resolves but component never mounts; stale meta on route definition
State Management Pinia store holds stale data after auth change; double-fetch from co-mounted components; reactive loss from const { x } = store without storeToRefs; store initialized before auth resolves
CSS × Animation transitionend event never fires due to @layer cascade; mode="out-in" blocks entering component indefinitely; animation stuck in leaving state; blank screen with zero console errors
Data & Rendering Backend payload shape mismatch (missing field → undefined → silent v-if failure); wrong reactive access pattern (v-if="fn()" instead of computed); component renders before async data resolves
Auth & Session Token expired but UI doesn't redirect; localStorage holds stale session after logout; race between auth state resolution and guard evaluation; middleware applies to wrong routes

Phase 4: Validation Task Patterns

Routing & Navigation — Blank Screen After Navigation

Symptom: User navigates, router reports success, destination component never renders. Page is blank with zero console errors.

Validation steps:

  1. Confirm no component mount — add lifecycle logging to the destination:

    // Vue 3
    onMounted(() => console.debug('[TargetView] onMounted fired'));
    // React
    useEffect(() => console.debug('[TargetView] mounted'), []);
    

    If the mount log never fires but the router reports navigation complete → this is NOT a routing logic bug.

  2. Trace the guard chain — add diagnostic logging:

    router.beforeEach((to, from) => {
        console.debug(`[Router] beforeEach: ${from.path} → ${to.path}`);
    });
    router.afterEach((to) => {
        console.debug(`[Router] afterEach: arrived at ${to.path}`);
    });
    

    If afterEach fires but onMounted does not → suspect the transition mechanism (see CSS × Animation below).

  3. Check for redirect loops — watch the URL bar or router logs for oscillating paths (e.g. /login → /home → /onboarding → /login).

  4. Verify auth state at guard time — log the auth store's state inside each guard to confirm it's resolved (not still loading).


State Management — Stale or Missing Data

Symptom: Component renders but shows stale data, or renders empty despite successful API call.

Validation steps:

  1. Inspect Pinia state via Vue DevTools — check the store's current values. Is the data present?

  2. Check for destructuring reactivity loss:

    // ❌ Loses reactivity — x is a plain value snapshot
    const { tasks } = useTaskStore();
    // ✅ Preserves reactivity
    const { tasks } = storeToRefs(useTaskStore());
    
  3. Check for race conditions with auth:

    // The store action fires before auth resolves → API returns 401 → store stays empty
    // Fix: await auth resolution before first store fetch
    
  4. Check for duplicate fetches from co-mounted components:

    • Open Network tab → filter by XHR → navigate to the page
    • If two identical requests appear → the store needs a loading-state guard

CSS × Animation — Transition Stuck (Blank Screen)

This section subsumes the former css-transition-pitfalls skill.

Symptom: Component "never mounts" after a route change. Blank screen with zero console errors. Works on hard refresh but recurs on SPA navigation. router.afterEach fires but onMounted does not.

The Anti-Pattern

User clicks link → Router navigates → Old component should leave → New component should enter
                                           ↓
                                    CSS framework's @layer rules
                                    override transition properties
                                           ↓
                                    transitionend event NEVER fires
                                           ↓
                                    SPA framework waits forever
                                           ↓
                                    New component NEVER mounts
                                           ↓
                                    BLANK SCREEN (no errors)

Why It's Hard to Catch

  1. No errors — everything completes from the router's perspective
  2. Works on refresh — hard refresh bypasses the SPA transition entirely
  3. Looks like a logic bug — the natural instinct is to debug auth state, guards, or template conditionals
  4. Intermittent — may only trigger on specific route pairs where the leaving component has conflicting animations

Affected SPA Frameworks

SPA Framework Transition Mechanism Vulnerable Mode
Vue 3 <Transition mode="out-in"> mode="out-in" blocks enter until leave completes
React (Framer Motion) AnimatePresence mode="wait" mode="wait" blocks enter until exit completes
React (React Transition Group) SwitchTransition mode="out-in" Same pattern
Svelte transition: directive with ` local`

CSS Frameworks That Can Cause This

CSS Framework Why
Tailwind v4 Uses @layer theme, base, components, utilities — unlayered styles (like Vue's transition classes) can be unexpectedly deprioritized
Open Props Uses @layer for custom property organization
Any framework using @layer The CSS Cascade spec gives layered styles lower priority than unlayered, BUT framework bundlers may re-layer your styles

Validation Steps

  1. Confirm the symptom class — add onMounted logging to the destination component. If mount log never fires but afterEach does → transition stuck.

  2. Rule out logic issues (time-box: 15 minutes):

    • Navigation guard allows the route (check afterEach logging)
    • Auth state is correct at navigation time
    • No redirect loops
  3. Inspect the transition mechanism — check your root layout for sequential transition modes:

    <!-- Vue: look for mode="out-in" -->
    <Transition name="fade" mode="out-in">
    <!-- React Framer Motion: look for mode="wait" -->
    <AnimatePresence mode="wait">
    <!-- React Transition Group: look for mode="out-in" -->
    <SwitchTransition mode="out-in">
    

    If found → this is almost certainly the cause.

  4. Verify CSS layer interference:

    # Check for @layer in built CSS
    grep -r "@layer" node_modules/tailwindcss/ | head -5
    grep -r "@import.*tailwindcss" src/
    # In browser DevTools: inspect the leaving element during transition
    # Look for: computed opacity, computed transition property
    
  5. Apply the fix — remove sequential transition mode:

    <!-- BEFORE (broken): sequential, depends on transitionend -->
    <Transition name="fade" mode="out-in">
      <component :is="Component" />
    </Transition>
    
    <!-- AFTER (fixed): simultaneous, always mounts new component -->
    <Transition name="fade">
      <component :is="Component" :key="$route.path" />
    </Transition>
    

    Update CSS to handle simultaneous leave/enter:

    .fade-enter-active {
      transition: opacity 0.15s ease-in !important;
    }
    .fade-leave-active {
      transition: opacity 0.15s ease-out !important;
      position: absolute !important;
      width: 100% !important;
      top: 0 !important;
      left: 0 !important;
    }
    .fade-enter-from,
    .fade-leave-to {
      opacity: 0 !important;
    }
    /* Parent needs position:relative to contain the absolute element */
    #main-content {
      position: relative;
    }
    

    Why !important? CSS @layer cascade can override your transition classes. !important guarantees your transition properties apply regardless of layer ordering.

Key Insight

When a component doesn't mount after navigation: suspect the transition mechanism FIRST, not the routing logic. The router will report success even when the component never renders. This is counter-intuitive because the blank screen looks like a routing/auth problem, but it's actually a CSS/animation problem.


Data & Rendering — Silent Template Failures

Symptom: Page renders but expected content is missing. No console errors. Data is present in the store.

Validation steps:

  1. Check for v-if on optional properties:

    <!-- Silent failure if exercise.options is undefined — v-if produces no error -->
    <div v-if="exercise.options.length > 0">
    <!-- Fix: optional chaining -->
    <div v-if="exercise.options?.length">
    
  2. Check for plain function calls in templates:

    <!-- ❌ Re-evaluates on every render cycle; may return inconsistent results -->
    <div v-if="isVisible()">
    <!-- ✅ Cached, reactive -->
    <div v-if="isVisibleComputed">
    
  3. Check for backend payload mismatches — log the raw API response and compare field names/types against what the component expects. Common: snake_case from backend vs camelCase in frontend.

  4. Check for async rendering issues:

    <!-- Component renders before data arrives — shows empty state permanently -->
    <TaskCard :task="task" />
    <!-- Fix: guard with v-if -->
    <TaskCard v-if="task" :task="task" />
    

Auth & Session — Redirect Loops and Ghost Sessions

Symptom: User gets stuck in a redirect loop between login, onboarding, and home pages. Or: user appears logged out despite having valid credentials.

Validation steps:

  1. Check localStorage for stale auth tokens:

    // In console
    Object.keys(localStorage).filter(k => k.includes('auth') || k.includes('supabase') || k.includes('token'));
    
  2. Verify auth state is resolved before guards run:

    router.beforeEach(async (to) => {
        console.debug('[Guard] authStore.isAuthenticated:', authStore.isAuthenticated);
        console.debug('[Guard] authStore.isLoading:', authStore.isLoading);
        // If isLoading is true here, the guard is racing the auth resolution
    });
    
  3. Check for circular guard logic:

    • Guard A: "if not authenticated → redirect to /login"
    • Guard B: "if authenticated and not onboarded → redirect to /onboarding"
    • Guard C: "if on /onboarding and already onboarded → redirect to /home"
    • If the onboarding flag is stale or the database read is async → loop
  4. Test by clearing all storage and hard-refreshing:

    localStorage.clear(); sessionStorage.clear(); location.reload();
    

    If the issue resolves → the problem is stale cached state, not logic.


Phase 6: Confidence Adjustments

Evidence Confidence Impact
onMounted log never fires but afterEach does High — definitively a transition/rendering issue, not routing
DevTools Network shows no request fired High — data never fetched (guard blocked, or fetch never called)
Vue DevTools shows correct Pinia state but UI is empty High — template rendering bug (wrong v-if, missing reactivity)
Issue resolves on hard refresh but recurs on SPA nav High — transition-stuck, service worker cache, or stale SPA state
@layer found in CSS framework and mode="out-in" is used Definitive for transition-stuck diagnosis
Issue only appears on specific route pairs Medium-High — likely CSS/animation interaction specific to the leaving component
Console shows no errors at all Medium — rules out JS exceptions; narrows to CSS/template/state issues

Quick Reference: Symptom → First Action

Symptom First Action
Blank screen after navigation (no errors) Add onMounted logging → if never fires, check <Transition mode>
Blank screen after navigation (console errors) Read the error — usually a runtime exception in the entering component
Redirect loop (URL oscillates) Log all guards with beforeEach → trace the loop pattern
Data present in store but not rendered Check v-if conditions for undefined/null access; check for destructuring without storeToRefs
Component renders then immediately disappears Check for a watch or watchEffect that re-triggers a navigation or state reset
API call fires twice Check for co-mounted components both calling the same store action; add loading-state guard
Works locally but not in production Check service worker cache; run vite build locally to reproduce; check env variables
Works on refresh but fails on SPA navigation Suspect transition mechanism or stale component state from previous route
Auth state incorrect in guard Log auth store state inside the guard; check if auth initialization is awaited before router starts
Styles not applying Check CSS specificity vs @layer cascade; inspect computed styles in DevTools Elements tab

  • Vue Idioms and Patterns @vue-idioms-and-patterns.md
  • TypeScript Idioms and Patterns @typescript-idioms-and-patterns.md
  • Error Handling Principles @error-handling-principles.md
  • Logging and Observability Principles @logging-and-observability-principles.md
  • Testing Strategy @testing-strategy.md