--- name: Comprehensive Debugging description: Master debugging techniques for Vue.js, Pinia, and cached state issues. Identify reactivity problems, memory leaks, performance bottlenecks, and complex state synchronization issues with systematic troubleshooting approaches. --- # Comprehensive Debugging ## Instructions ### Debugging Philosophy Always follow systematic debugging methodology: 1. **Identify the Problem**: Gather specific symptoms and error messages 2. **Isolate the Issue**: Use binary search to narrow down the root cause 3. **Verify the Fix**: Ensure the solution works and doesn't introduce regressions 4. **Document the Solution**: Create reusable debugging patterns ### Vue.js Reactivity Debugging #### Common Reactivity Issues - **Direct Property Assignment**: Adding properties to reactive objects without `Vue.set()` or `reactive()` - **Array Mutation**: Direct index assignment or length modification without proper methods - **Computed Caching**: Computed properties not updating due to stale dependencies - **Watch Invalidation**: Watchers not firing due to incorrect configuration #### Debugging Tools ```typescript import { watch, watchEffect, computed, ref, reactive } from 'vue' // Debug reactivity dependencies const debugReactivity = () => { const state = reactive({ count: 0, nested: { value: 0 } }) // Track what triggers reactivity watchEffect(() => { console.log('Effect triggered with:', state.count, state.nested.value) }) // Debug computed dependencies const doubled = computed(() => { console.log('Computed recalculating with:', state.count) return state.count * 2 }) return { state, doubled } } // Component-level debugging hooks export default { setup() { // Debug what triggers re-renders onRenderTracked((e) => { console.log('Render tracked:', e.key, e.type, e.target) }) onRenderTriggered((e) => { console.log('Render triggered:', e.key, e.type, e.target) }) } } ``` ### Pinia State Debugging #### Store State Issues - **Stale State**: State not updating due to missing reactivity - **Persistence Conflicts**: LocalStorage hydration overriding current state - **Action Side Effects**: Actions causing unexpected state mutations - **Subscription Leaks**: Unmanaged store subscriptions #### Debugging Patterns ```typescript import { defineStore } from 'pinia' export const useDebugStore = defineStore('debug', { state: () => ({ tasks: [], selectedTaskId: null, lastMutation: null, debugMode: true }), actions: { // Add mutation tracking addTask(task) { if (this.debugMode) { console.trace('addTask called with:', task) console.log('State before:', JSON.parse(JSON.stringify(this.tasks))) } this.tasks.push(task) this.lastMutation = { type: 'addTask', timestamp: Date.now(), payload: task } if (this.debugMode) { console.log('State after:', JSON.parse(JSON.stringify(this.tasks))) } }, // Batch mutations for atomic updates updateTasks(updates) { this.$patch((state) => { updates.forEach(({ id, ...changes }) => { const task = state.tasks.find(t => t.id === id) if (task) { Object.assign(task, changes) if (this.debugMode) { console.log(`Task ${id} updated:`, changes) } } }) }) } }, // Debug getters with caching issues getters: { completedTasks: (state) => { console.log('completedTasks getter recalculating') return state.tasks.filter(task => task.completed) }, taskById: (state) => { console.log('taskById getter recalculating') return (id) => state.tasks.find(task => task.id === id) } } }) ``` ### Cached State Issues #### Browser Storage Debugging ```typescript import { useLocalStorage, useSessionStorage } from '@vueuse/core' const debugStorage = () => { const settings = useLocalStorage('app-settings', { theme: 'light', language: 'en' }) // Monitor storage changes window.addEventListener('storage', (e) => { console.log('Storage changed:', e.key, e.oldValue, e.newValue) }) // Debug hydration conflicts const checkStorageConsistency = () => { const raw = localStorage.getItem('app-settings') const parsed = JSON.parse(raw) console.log('Raw storage:', raw) console.log('Parsed storage:', parsed) console.log('Current reactive:', settings.value) if (JSON.stringify(settings.value) !== JSON.stringify(parsed)) { console.warn('Storage inconsistency detected!') } } return { settings, checkStorageConsistency } } ``` #### Memory Leak Detection ```typescript const detectMemoryLeaks = () => { const componentInstances = new WeakSet() const subscriptions = new Set() const trackComponent = (component) => { componentInstances.add(component) console.log('Component tracked:', component.$options.name) } const trackSubscription = (unsubscribe) => { subscriptions.add(unsubscribe) // Auto-cleanup after component unmounts onUnmounted(() => { if (subscriptions.has(unsubscribe)) { unsubscribe() subscriptions.delete(unsubscribe) console.log('Subscription cleaned up') } }) } // Monitor for memory leaks setInterval(() => { console.log('Active subscriptions:', subscriptions.size) console.log('Tracked components:', componentInstances.size || 0) }, 10000) return { trackComponent, trackSubscription } } ``` ### Performance Debugging #### Component Performance ```vue ``` ### Canvas Debugging #### Canvas State Issues ```typescript const debugCanvas = () => { const canvasState = reactive({ nodes: [], selectedNodes: [], viewport: { x: 0, y: 0, zoom: 1 }, isDragging: false }) // Debug canvas mutations const debugCanvasMutation = (action, data) => { console.group(`Canvas ${action}`) console.log('State before:', JSON.parse(JSON.stringify(canvasState))) console.log('Mutation data:', data) action(canvasState, data) console.log('State after:', JSON.parse(JSON.stringify(canvasState))) console.groupEnd() } // Debug selection issues const debugSelection = (nodeIds) => { console.log('Selecting nodes:', nodeIds) console.log('Current selection:', canvasState.selectedNodes) console.log('Available nodes:', canvasState.nodes.map(n => n.id)) const invalidIds = nodeIds.filter(id => !canvasState.nodes.some(n => n.id === id) ) if (invalidIds.length > 0) { console.warn('Invalid node IDs in selection:', invalidIds) } } return { canvasState, debugCanvasMutation, debugSelection } } ``` ### Async Operation Debugging #### Promise and Timer Debugging ```typescript const debugAsync = () => { const activePromises = new Set() const activeTimers = new Set() const debugPromise = (promise, description) => { activePromises.add(promise) console.log(`Promise started: ${description}`) promise .then(result => { console.log(`Promise resolved: ${description}`, result) activePromises.delete(promise) }) .catch(error => { console.error(`Promise rejected: ${description}`, error) activePromises.delete(promise) }) return promise } const debugTimer = (callback, delay, description) => { const timerId = setTimeout(() => { console.log(`Timer fired: ${description}`) callback() activeTimers.delete(timerId) }, delay) activeTimers.add(timerId) console.log(`Timer set: ${description} (${delay}ms)`) return timerId } // Check for leaks setInterval(() => { console.log('Active promises:', activePromises.size) console.log('Active timers:', activeTimers.size) if (activePromises.size > 10) { console.warn('Possible promise leak detected!') } if (activeTimers.size > 5) { console.warn('Possible timer leak detected!') } }, 5000) return { debugPromise, debugTimer } } ``` ### Error Boundary Debugging #### Global Error Handling ```typescript const setupErrorHandling = () => { // Vue error handler app.config.errorHandler = (err, instance, info) => { console.group('Vue Error') console.error('Error:', err) console.error('Component:', instance?.$options.name) console.error('Info:', info) console.error('Props:', instance?.$props) console.error('State:', instance?.$data) console.groupEnd() // Send to error tracking service errorTracking.captureException(err, { tags: { component: instance?.$options.name }, extra: { info, props: instance?.$props } }) } // Unhandled promise rejections window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason) // Prevent default browser behavior event.preventDefault() }) // Global error tracking window.addEventListener('error', (event) => { console.error('Global error:', event.error) }) } ``` ### Network Debugging #### API Request Debugging ```typescript const debugNetwork = () => { const originalFetch = window.fetch window.fetch = async (...args) => { const [url, options = {}] = args const requestId = Math.random().toString(36).substr(2, 9) console.group(`🌐 Request ${requestId}: ${options.method || 'GET'} ${url}`) console.log('Request:', options) const startTime = performance.now() try { const response = await originalFetch(...args) const duration = performance.now() - startTime console.log(`✅ Response ${requestId}: ${response.status} (${duration.toFixed(2)}ms)`) console.log('Headers:', Object.fromEntries(response.headers.entries())) // Clone response to avoid consuming it const clonedResponse = response.clone() const data = await clonedResponse.json().catch(() => clonedResponse.text()) console.log('Data:', data) console.groupEnd() return response } catch (error) { const duration = performance.now() - startTime console.error(`❌ Error ${requestId}: ${error.message} (${duration.toFixed(2)}ms)`) console.groupEnd() throw error } } } ``` ### Debugging Utilities #### State Snapshot ```typescript const createDebugger = () => { const snapshots = [] const maxSnapshots = 10 const takeSnapshot = (label, data) => { const snapshot = { label, timestamp: Date.now(), data: JSON.parse(JSON.stringify(data)), stackTrace: new Error().stack } snapshots.push(snapshot) if (snapshots.length > maxSnapshots) { snapshots.shift() } console.log(`📸 Snapshot: ${label}`, snapshot) } const compareSnapshots = (index1, index2) => { const snap1 = snapshots[index1] const snap2 = snapshots[index2] const diff = findDiff(snap1.data, snap2.data) console.log(`🔍 Comparing snapshots: ${snap1.label} vs ${snap2.label}`) console.log('Differences:', diff) } const findDiff = (obj1, obj2) => { // Simple diff implementation const diffs = [] const keys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]) for (const key of keys) { if (JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) { diffs.push({ key, oldValue: obj1[key], newValue: obj2[key] }) } } return diffs } return { takeSnapshot, compareSnapshots, snapshots } } ``` ### Usage Examples #### Quick Debug Patterns ```typescript // Quick reactivity check const checkReactivity = (obj, property) => { const value = obj[property] obj[property] = value console.log('Reactivity check:', obj[property] === value) } // Performance measurement const measurePerformance = (fn, label) => { const start = performance.now() const result = fn() const end = performance.now() console.log(`⏱️ ${label}: ${(end - start).toFixed(2)}ms`) return result } // Debug store mutations const debugStoreMutations = (store) => { store.$subscribe((mutation, state) => { console.group(`🔄 Store Mutation: ${mutation.type}`) console.log('Payload:', mutation.payload) console.log('New state:', state) console.groupEnd() }) } ``` This comprehensive debugging skill provides systematic approaches to identify and resolve complex state management, reactivity, and performance issues in Vue.js applications with Pinia stores.