Critical fixes in game_engine.py: - Fix silent error swallowing in _batch_save_inning_rolls (re-raise) - Add per-game asyncio.Lock for race condition prevention - Add _cleanup_game_resources() for memory leak prevention - All 739 tests passing Documentation refactoring: - Created CODE_REVIEW_GAME_ENGINE.md documenting 24 identified issues - Trimmed backend/app/core/CLAUDE.md from 1371 to 143 lines - Trimmed frontend-sba/CLAUDE.md from 696 to 110 lines - Created focused subdirectory CLAUDE.md files: - frontend-sba/components/CLAUDE.md (105 lines) - frontend-sba/composables/CLAUDE.md (79 lines) - frontend-sba/store/CLAUDE.md (116 lines) - frontend-sba/types/CLAUDE.md (95 lines) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
105 lines
3.1 KiB
Markdown
105 lines
3.1 KiB
Markdown
# Components Directory
|
|
|
|
Vue 3 components organized by feature domain. All use `<script setup lang="ts">` with Composition API.
|
|
|
|
## Directory Structure
|
|
|
|
### Game/ - Core Game Display
|
|
| Component | Purpose | Key Props |
|
|
|-----------|---------|-----------|
|
|
| `ScoreBoard.vue` | Sticky header with inning/score/count | gameState |
|
|
| `GameBoard.vue` | Baseball diamond with runners | gameState |
|
|
| `CurrentSituation.vue` | Pitcher vs Batter cards | currentPitcher, currentBatter |
|
|
| `PlayByPlay.vue` | Animated play history feed | plays |
|
|
|
|
**Data Pattern**: These receive `LineupPlayerState` from gameStore, then use `findPlayerInLineup()` to get full player data (name, headshot).
|
|
|
|
### Decisions/ - Strategic Decision Input
|
|
| Component | Purpose | Emits |
|
|
|-----------|---------|-------|
|
|
| `DecisionPanel.vue` | Container for decision workflow | - |
|
|
| `DefensiveSetup.vue` | Infield/outfield positioning | submit |
|
|
| `OffensiveApproach.vue` | Batting action selection | submit |
|
|
| `StolenBaseInputs.vue` | Per-runner steal attempts | submit |
|
|
|
|
**Data Pattern**: Read `currentDecisionPrompt` from store, emit decisions to parent which calls `useGameActions`.
|
|
|
|
### Substitutions/ - Player Replacement
|
|
| Component | Purpose | Emits |
|
|
|-----------|---------|-------|
|
|
| `SubstitutionPanel.vue` | Main substitution container | close |
|
|
| `PinchHitterSelector.vue` | Replace batter | substitute |
|
|
| `PitchingChangeSelector.vue` | Replace pitcher | substitute |
|
|
| `DefensiveReplacementSelector.vue` | Position switch | substitute |
|
|
|
|
**Data Pattern**: Filter `homeLineup`/`awayLineup` for active vs bench players.
|
|
|
|
### UI/ - Reusable Primitives
|
|
| Component | Purpose |
|
|
|-----------|---------|
|
|
| `ActionButton.vue` | Styled action button with loading state |
|
|
| `ButtonGroup.vue` | Radio-button style group selector |
|
|
| `ToggleSwitch.vue` | Boolean toggle with labels |
|
|
|
|
## Component Standards
|
|
|
|
```vue
|
|
<template>
|
|
<!-- Always wrap in single root -->
|
|
<div class="component-name">
|
|
<!-- Content -->
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
// 1. Imports
|
|
import { computed } from 'vue'
|
|
import { useGameStore } from '~/store/game'
|
|
|
|
// 2. Props/Emits with TypeScript
|
|
interface Props {
|
|
gameId: string
|
|
}
|
|
const props = defineProps<Props>()
|
|
|
|
const emit = defineEmits<{
|
|
submit: [data: SomeType]
|
|
}>()
|
|
|
|
// 3. Store access
|
|
const gameStore = useGameStore()
|
|
|
|
// 4. Computed/methods
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Tailwind @apply preferred */
|
|
</style>
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Two-Step Player Lookup
|
|
```typescript
|
|
// GameState has LineupPlayerState (minimal)
|
|
const batterState = computed(() => gameStore.currentBatter)
|
|
|
|
// Get full Lineup with player details
|
|
const batterLineup = computed(() => {
|
|
if (!batterState.value) return null
|
|
return gameStore.findPlayerInLineup(batterState.value.lineup_id)
|
|
})
|
|
|
|
// Access player data
|
|
const batterName = computed(() => batterLineup.value?.player.name ?? 'Unknown')
|
|
const batterHeadshot = computed(() => batterLineup.value?.player.headshot)
|
|
```
|
|
|
|
### Conditional Rendering by Team
|
|
```typescript
|
|
const isMyTurn = computed(() => {
|
|
// Check if current user's team needs to act
|
|
return gameStore.battingTeamId === myTeamId
|
|
})
|
|
```
|