Vue 3 with Composition API, Pinia, Vue Router, Tailwind v4: - Vite build with @/ path aliases and backend proxy - TypeScript strict mode with proper type imports - Pinia stores (auth, game) with setup syntax and persistence - Vue Router with auth guards and lazy-loaded routes - Tailwind v4 with custom theme (Pokemon types, dark UI) - Vitest configured for component testing - ESLint v9 flat config with Vue/TypeScript support Pages: Home, Login, Register, Campaign, Collection, DeckBuilder, Match Components: AppHeader with auth-aware navigation Types: Card, GameState, Player, Deck, Campaign types Also adds frontend-code-audit skill with patterns for: - Error handling (unhandled promises, empty catches) - Security (XSS, token storage, input validation) - Architecture (Composition API, Pinia patterns, Phaser rules) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
104 lines
3.8 KiB
YAML
104 lines
3.8 KiB
YAML
# Frontend Error Handling Anti-Patterns
|
|
# Patterns that hide errors or fail to provide user feedback
|
|
|
|
name: Error Handling
|
|
description: |
|
|
Detects patterns where errors are silently swallowed, promises unhandled,
|
|
or users left without feedback when operations fail. These patterns lead
|
|
to confusing UX and hard-to-debug issues.
|
|
|
|
patterns:
|
|
- id: unhandled-promise
|
|
description: Async function called without await or .catch()
|
|
grep_pattern: '^\s+\w+\([^)]*\)\s*$'
|
|
file_glob: '*.{vue,ts}'
|
|
context_lines: 3
|
|
verdict_hints:
|
|
ISSUE_IF: "Async function (fetch, API call) with no error handling"
|
|
OK_IF: "Sync function; or fire-and-forget with documented reason"
|
|
|
|
- id: empty-catch-block
|
|
description: Catch block that does nothing with the error
|
|
grep_pattern: 'catch\s*\([^)]*\)\s*\{\s*\}'
|
|
file_glob: '*.{vue,ts}'
|
|
context_lines: 3
|
|
verdict_hints:
|
|
ISSUE_IF: "Error completely swallowed - user gets no feedback"
|
|
OK_IF: "Never OK - at minimum log or set error state"
|
|
|
|
- id: catch-only-console
|
|
description: Catch block that only logs to console
|
|
grep_pattern: 'catch.*\{[\s\n]*console\.(log|error|warn)'
|
|
multiline: true
|
|
file_glob: '*.{vue,ts}'
|
|
context_lines: 5
|
|
verdict_hints:
|
|
ISSUE_IF: "Error logged but UI shows success/no feedback to user"
|
|
OK_IF: "Also sets error state or shows toast notification"
|
|
|
|
- id: fetch-no-error-check
|
|
description: Fetch without checking response.ok
|
|
grep_pattern: 'fetch\([^)]+\)[\s\S]*?\.json\(\)'
|
|
multiline: true
|
|
file_glob: '*.{vue,ts}'
|
|
context_lines: 7
|
|
verdict_hints:
|
|
ISSUE_IF: "Response not checked for errors before parsing JSON"
|
|
OK_IF: "Uses wrapper that handles errors; or response.ok checked"
|
|
|
|
- id: missing-loading-state
|
|
description: Async operation without loading indicator
|
|
grep_pattern: 'async.*\{[\s\S]*?await.*fetch'
|
|
multiline: true
|
|
file_glob: '*.vue'
|
|
context_lines: 10
|
|
verdict_hints:
|
|
ISSUE_IF: "No isLoading ref set before/after async operation"
|
|
OK_IF: "Loading state managed by composable; or instant operation"
|
|
|
|
- id: missing-error-state
|
|
description: API call without error state handling
|
|
grep_pattern: 'await.*(fetch|api|axios)'
|
|
file_glob: '*.vue'
|
|
context_lines: 8
|
|
verdict_hints:
|
|
ISSUE_IF: "No error ref or error handling visible in component"
|
|
OK_IF: "Error handling in composable; or uses error boundary"
|
|
|
|
- id: silent-store-action
|
|
description: Store action that catches but doesn't surface errors
|
|
grep_pattern: 'catch.*\{[\s\S]*?return (false|null|undefined|\[\]|\{\})'
|
|
multiline: true
|
|
file_glob: 'stores/*.ts'
|
|
context_lines: 6
|
|
verdict_hints:
|
|
ISSUE_IF: "Store swallows error, returns falsy - caller can't show error"
|
|
OK_IF: "Also sets store.error state that UI can display"
|
|
|
|
- id: onmounted-no-error-handling
|
|
description: onMounted with async call but no error handling
|
|
grep_pattern: 'onMounted\(\s*async'
|
|
file_glob: '*.vue'
|
|
context_lines: 10
|
|
verdict_hints:
|
|
ISSUE_IF: "Async onMounted without try/catch - errors crash silently"
|
|
OK_IF: "Has try/catch with error state; or uses error boundary"
|
|
|
|
- id: watch-async-no-catch
|
|
description: Watch callback with async but no error handling
|
|
grep_pattern: 'watch\([^)]+,\s*async'
|
|
file_glob: '*.vue'
|
|
context_lines: 8
|
|
verdict_hints:
|
|
ISSUE_IF: "Async watch without error handling"
|
|
OK_IF: "Has try/catch; or errors handled by called function"
|
|
|
|
- id: socket-no-error-handler
|
|
description: Socket.io without error event handler
|
|
grep_pattern: 'socket\.(on|emit)\('
|
|
file_glob: '*.{vue,ts}'
|
|
context_lines: 10
|
|
verdict_hints:
|
|
ISSUE_IF: "Socket used without socket.on('error') or socket.on('connect_error')"
|
|
OK_IF: "Error handlers registered elsewhere (socket setup file)"
|