mantimon-tcg/.claude/skills/frontend-code-audit/patterns/architecture.yaml
Cal Corum b9b803da66 Scaffold Vue 3 + TypeScript frontend (Phase F0)
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>
2026-01-30 09:23:53 -06:00

205 lines
7.3 KiB
YAML

# Frontend Architecture Anti-Patterns
# Patterns that violate Mantimon TCG frontend architecture principles
name: Architecture
description: |
Detects violations of the project's frontend architecture principles:
- Vue Composition API patterns (no Options API)
- Pinia store patterns (setup syntax, proper actions)
- Phaser integration (rendering only, no game logic)
- Component organization (composables for reuse)
- Backend authority (client never authoritative for game state)
patterns:
# Vue Composition API
- id: options-api-usage
description: Options API instead of Composition API
grep_pattern: 'export default \{[\s\S]*?(data\(\)|methods:|computed:|watch:)'
multiline: true
file_glob: '*.vue'
context_lines: 5
verdict_hints:
ISSUE_IF: "Using Options API - project uses Composition API only"
OK_IF: "Never OK - convert to <script setup> with Composition API"
- id: missing-script-setup
description: Script without setup attribute
grep_pattern: '<script lang="ts">\s*$'
file_glob: '*.vue'
context_lines: 2
verdict_hints:
ISSUE_IF: "Missing setup - should use <script setup lang=\"ts\">"
OK_IF: "Rare cases needing defineComponent; document why"
- id: options-api-definecomponent
description: defineComponent with options object
grep_pattern: 'defineComponent\(\{[\s\S]*?(data|methods|computed):'
multiline: true
file_glob: '*.vue'
context_lines: 5
verdict_hints:
ISSUE_IF: "Options API via defineComponent"
OK_IF: "Never OK - use <script setup>"
# Pinia Store Patterns
- id: store-options-syntax
description: Pinia store using options syntax instead of setup
grep_pattern: 'defineStore\([^,]+,\s*\{[\s\S]*?state:'
multiline: true
file_glob: 'stores/*.ts'
context_lines: 5
verdict_hints:
ISSUE_IF: "Options syntax - project uses setup store syntax"
OK_IF: "Never OK - convert to setup syntax with ref/computed"
- id: store-mutation-outside-action
description: Store state mutated directly from component
grep_pattern: 'Store\(\)\.\w+\s*='
file_glob: '*.vue'
context_lines: 3
verdict_hints:
ISSUE_IF: "Direct state mutation - use store action instead"
OK_IF: "Assigning to local ref that shadows store property"
- id: store-no-error-state
description: Store without error state for async operations
grep_pattern: 'defineStore.*\{[\s\S]*?async.*fetch[\s\S]*?\}'
multiline: true
file_glob: 'stores/*.ts'
context_lines: 20
verdict_hints:
ISSUE_IF: "Async store without error ref for failure handling"
OK_IF: "Error state exists; or errors handled by caller"
# Phaser Integration
- id: phaser-game-logic
description: Game logic in Phaser scene (should be backend)
grep_pattern: '(calculateDamage|resolveTurn|checkWin|applyEffect)'
file_glob: 'game/**/*.ts'
context_lines: 5
verdict_hints:
ISSUE_IF: "Game logic in Phaser - backend is authoritative"
OK_IF: "Animation/display logic with same name; not actual game rules"
- id: phaser-direct-api
description: Phaser scene making API calls directly
grep_pattern: '(fetch|axios|api\.)'
file_glob: 'game/**/*.ts'
context_lines: 4
verdict_hints:
ISSUE_IF: "Phaser calling API - should go through Vue layer"
OK_IF: "Asset loading (images, audio); not game data"
- id: phaser-store-import
description: Phaser importing store directly (use bridge)
grep_pattern: 'import.*from.*stores/'
file_glob: 'game/**/*.ts'
context_lines: 3
verdict_hints:
ISSUE_IF: "Direct store import - use event bridge pattern"
OK_IF: "Type-only import (import type); bridge.ts file"
- id: phaser-modifies-state
description: Phaser scene modifying game state
grep_pattern: 'gameState\.\w+\s*='
file_glob: 'game/**/*.ts'
context_lines: 4
verdict_hints:
ISSUE_IF: "Phaser modifying state - should only render"
OK_IF: "Local animation state; not shared game state"
# Component Organization
- id: api-call-in-component
description: Direct fetch/API call in component (use composable)
grep_pattern: 'fetch\(|axios\.|api\.'
file_glob: 'components/**/*.vue'
context_lines: 5
verdict_hints:
ISSUE_IF: "API call in component - extract to composable"
OK_IF: "Component is in pages/; or uses composable internally"
- id: large-component
description: Component with many lines (consider splitting)
grep_pattern: '</script>'
file_glob: '*.vue'
context_lines: 0
verdict_hints:
ISSUE_IF: "Script section >150 lines - split into composables"
OK_IF: "Page component; or logic is cohesive and documented"
- id: prop-drilling
description: Props passed through multiple levels
grep_pattern: 'defineProps.*\{[\s\S]*?(Props|\.\.\.)'
multiline: true
file_glob: '*.vue'
context_lines: 10
verdict_hints:
ISSUE_IF: "Props drilled >2 levels - use provide/inject or store"
OK_IF: "Props are component-specific; not passed through"
# Type Safety
- id: any-type-usage
description: Using 'any' type
grep_pattern: ':\s*any\b|as\s+any\b|<any>'
file_glob: '*.{vue,ts}'
context_lines: 2
verdict_hints:
ISSUE_IF: "Explicit any - defeats TypeScript benefits"
OK_IF: "Temporary during migration; or external lib without types"
- id: type-assertion-abuse
description: Type assertion (as) that might hide errors
grep_pattern: '\s+as\s+[A-Z]\w+'
file_glob: '*.{vue,ts}'
context_lines: 3
verdict_hints:
ISSUE_IF: "Assertion used to bypass type error"
OK_IF: "Narrowing from unknown; or after runtime check"
- id: missing-prop-types
description: Props without TypeScript types
grep_pattern: 'defineProps\(\[\s*["\']'
file_glob: '*.vue'
context_lines: 3
verdict_hints:
ISSUE_IF: "Array prop syntax - no type safety"
OK_IF: "Never OK - use defineProps<{...}>()"
# Import Organization
- id: relative-import-deep
description: Deep relative imports (use @ alias)
grep_pattern: 'from ["\']\.\.\/\.\.\/\.\.\/'
file_glob: '*.{vue,ts}'
context_lines: 2
verdict_hints:
ISSUE_IF: "Deep relative path - use @/ alias"
OK_IF: "Never OK for 3+ levels - always use @/"
- id: missing-type-import
description: Type import without 'import type'
grep_pattern: 'import \{[^}]*(Type|Interface|Props)[^}]*\} from'
file_glob: '*.{vue,ts}'
context_lines: 2
verdict_hints:
ISSUE_IF: "Type imported as value - use 'import type'"
OK_IF: "Also used as value (class, enum); not pure type"
# Backend Authority
- id: client-state-authority
description: Client treating its state as authoritative
grep_pattern: '(emit|send)\([^)]*\{[^}]*(hp|damage|winner|gameState)'
file_glob: '*.{vue,ts}'
context_lines: 5
verdict_hints:
ISSUE_IF: "Sending computed game state to server - server is authority"
OK_IF: "Sending action intent (playCard, attack); server computes result"
- id: local-game-calculation
description: Calculating game outcomes client-side
grep_pattern: '(damage|hp|energy)\s*[-+*]=|Math\.(random|floor).*damage'
file_glob: '*.{vue,ts}'
context_lines: 5
verdict_hints:
ISSUE_IF: "Game calculation on client - server must compute"
OK_IF: "Animation preview; actual result from server"