This commit captures work from multiple sessions building the statistics system and frontend component library. Backend - Phase 3.5: Statistics System - Box score statistics with materialized views - Play stat calculator for real-time updates - Stat view refresher service - Alembic migration for materialized views - Test coverage: 41 new tests (all passing) Frontend - Phase F1: Foundation - Composables: useGameState, useGameActions, useWebSocket - Type definitions and interfaces - Store setup with Pinia Frontend - Phase F2: Game Display - ScoreBoard, GameBoard, CurrentSituation, PlayByPlay components - Demo page at /demo Frontend - Phase F3: Decision Inputs - DefensiveSetup, OffensiveApproach, StolenBaseInputs components - DecisionPanel orchestration - Demo page at /demo-decisions - Test coverage: 213 tests passing Frontend - Phase F4: Dice & Manual Outcome - DiceRoller component - ManualOutcomeEntry with validation - PlayResult display - GameplayPanel orchestration - Demo page at /demo-gameplay - Test coverage: 119 tests passing Frontend - Phase F5: Substitutions - PinchHitterSelector, DefensiveReplacementSelector, PitchingChangeSelector - SubstitutionPanel with tab navigation - Demo page at /demo-substitutions - Test coverage: 114 tests passing Documentation: - PHASE_3_5_HANDOFF.md - Statistics system handoff - PHASE_F2_COMPLETE.md - Game display completion - Frontend phase planning docs - NEXT_SESSION.md updated for Phase F6 Configuration: - Package updates (Nuxt 4 fixes) - Tailwind config enhancements - Game store updates Test Status: - Backend: 731/731 passing (100%) - Frontend: 446/446 passing (100%) - Total: 1,177 tests passing Next Phase: F6 - Integration (wire all components into game page) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
Phase F2 Complete - Game State Display
Status: ✅ COMPLETE Date: 2025-01-10 Time Investment: ~1.5 hours Components Created: 4 major UI components + 1 integrated game page
Summary
Phase F2 delivers a mobile-first, visually engaging game interface that displays real-time game state with WebSocket integration. All components are designed for 60% mobile traffic with smooth animations, vibrant colors, and touch-friendly interactions.
Components Created
1. ✅ ScoreBoard Component
File: components/Game/ScoreBoard.vue (265 lines)
Features:
- Sticky header - Always visible at top of viewport
- Dual layout - Mobile (stacked) and Desktop (horizontal)
- Live game state - Score, inning, count, outs, runners
- Mini diamond - Visual runner indicators with glow effects
- Responsive count display - Color-coded balls (green) and strikes (red)
- Animated outs - Red dots that fill as outs accumulate
- Gradient background - Primary blue gradient for brand consistency
Mobile Features:
- Compact 3-column layout (Away | Inning | Home)
- Touch-friendly spacing (44px minimum)
- Large 4xl font for scores
- Mini diamond visualization (12x12 grid)
Desktop Features:
- Expanded horizontal layout with more detail
- Larger runner diamond with pulse animation
- Better spacing and readability
2. ✅ GameBoard Component
File: components/Game/GameBoard.vue (240 lines)
Features:
- Baseball diamond visualization - Green field with dirt infield
- 3D-style bases - Rotated white squares with shadows
- Runner indicators - Yellow glowing dots on occupied bases
- Pitcher's mound - Central brown circle
- Player badges - Current batter (red "B") and pitcher (blue "P")
- Player names - Displayed below badges with team info
- Mobile cards - Batter/pitcher info cards below diamond on mobile
- Subtle animations - Pulse effect for runners on base
- Grass pattern - Realistic outfield striping effect
Visual Design:
- Gradient green field (600→700)
- Rotated amber diamond for infield dirt
- White base paths with subtle opacity
- Shadows and glows for depth
- Responsive sizing (max-width maintains aspect ratio)
Mobile Optimization:
- Compact player badges (8x8)
- Smaller text (xs, sm)
- Info cards below diamond
- Touch-friendly tap targets
3. ✅ CurrentSituation Component
File: components/Game/CurrentSituation.vue (205 lines)
Features:
- Dual player cards - Pitcher (blue gradient) vs Batter (red gradient)
- Player images - Optional headshots with fallback handling
- Badge indicators - Large circular "P" and "B" badges
- Player details - Name, position, team, manager
- Responsive layout - Stacked on mobile, side-by-side on desktop
- Gradient backgrounds - Blue for pitcher, red for batter
- Empty state - Friendly waiting message when no players
- Fade-in animation - Smooth 0.3s entrance
Mobile Layout:
- Stacked vertically with "VS" indicator
- Compact cards with flex layout
- 12x12 badges
- 14x14 player images
Desktop Layout:
- Side-by-side 2-column grid
- Larger 16x16 badges
- 20x20 player images
- More detailed info display
4. ✅ PlayByPlay Component
File: components/Game/PlayByPlay.vue (280 lines)
Features:
- Real-time feed - Scrollable list of recent plays
- Color-coded plays - Runs (green), Outs (red), Hits (blue)
- Play animations - Slide-in effect for new plays
- Smart filtering - Show recent or show all toggle
- Play metadata - Inning badge, play number, outcome type
- Icon system - Dynamic SVG icons based on play type
- Empty state - Friendly waiting message
- Expandable descriptions - Line-clamp with hover expansion
- Scrollable - Max height with overflow scroll
- Load more - Button to expand from limited to full view
Play Card Elements:
- Left border color (green/red/blue based on outcome)
- Icon badge (runs up arrow, outs X, hits baseball)
- Inning badge (small gray pill)
- Play number (monospace, top-right)
- Description (medium weight, readable)
- Stats row (runs scored, outs recorded)
- Outcome badge (color-coded pill)
Mobile Optimization:
- Compact mode with line-clamp-2
- Show/Hide All toggle
- Touch-friendly tap targets
- Smaller text sizing
5. ✅ Integrated Game Page
File: pages/games/[id].vue (309 lines)
Features:
- Sticky scoreboard - Always visible game state
- WebSocket integration - Real-time updates via composables
- Dual layouts - Mobile (stacked) vs Desktop (3-column grid)
- Connection status - Yellow banner when disconnected
- Loading overlay - Spinner with backdrop blur
- Game state indicators - Pending, Active, Completed states
- Action buttons - Roll Dice, Set Defense, Set Offense
- Disabled states - Gray out when not available
- Auto-join - Connects and joins game on mount
- Clean unmount - Leaves game and resets store
Mobile Layout:
- Vertical stack: Situation → Board → Plays → Actions
- Full-width cards with padding
- Large action button (w-full)
- Compact spacing (gap-6)
Desktop Layout:
- 3-column grid (2 span left, 1 right)
- Left: Situation + Board + Actions
- Right: Sticky play-by-play feed
- Better use of horizontal space
- Larger action buttons
State Management:
- Computed properties from game store
- WebSocket connection status
- Runners state conversion (null checks)
- Loading and connection status refs
Design Principles Applied
1. Mobile-First ✅
- All components designed for mobile first
- Portrait orientation optimized
- Touch targets 44px minimum
- Large, readable text (lg, xl, 2xl+)
- Stacked layouts that scale to desktop
- Responsive breakpoints (lg:)
2. Modern, Fun UX ✅
- Vibrant gradients (blue, green, red)
- Smooth animations (fade-in, slide-in, pulse)
- Glowing effects (runners, outs, shadows)
- Emojis in buttons (🎲, 🛡️, ⚔️)
- Color-coded elements (green runs, red outs, blue hits)
- Friendly empty states with icons and messages
3. Real-Time Engagement ✅
- WebSocket integration ready for live updates
- Instant visual feedback (connection status, loading states)
- Animated state changes (TransitionGroup for plays)
- Persistent context (sticky scoreboard)
4. Accessibility ✅
- High contrast text and backgrounds
- Clear visual hierarchy (headings, spacing)
- Keyboard navigation ready (buttons, links)
- Screen reader friendly (semantic HTML, alt text)
- Dark mode support (dark: classes throughout)
Technical Implementation
Composables Used
useWebSocket()- Connection management, auto-reconnectuseGameActions()- Type-safe action emittersuseGameStore()- Game state and play history
Store Integration
// Game state computed from store
const gameState = computed(() => gameStore.gameState)
const playHistory = computed(() => gameStore.playHistory)
const canRollDice = computed(() => gameStore.canRollDice)
// Runners state conversion
const runnersState = computed(() => ({
first: gameState.value?.runners.first !== null,
second: gameState.value?.runners.second !== null,
third: gameState.value?.runners.third !== null
}))
WebSocket Lifecycle
onMounted(() => {
connect() // Establish connection
watch(isConnected, async (connected) => {
if (connected) {
await actions.joinGame('player')
await actions.requestGameState()
}
})
})
onUnmounted(() => {
actions.leaveGame()
gameStore.resetGame()
})
File Inventory
New Files Created (5 files, 1,299 lines total):
components/Game/
├── ScoreBoard.vue (265 lines)
├── GameBoard.vue (240 lines)
├── CurrentSituation.vue (205 lines)
└── PlayByPlay.vue (280 lines)
pages/games/
└── [id].vue (309 lines - rewritten)
Lines of Code:
- ScoreBoard: 265
- GameBoard: 240
- CurrentSituation: 205
- PlayByPlay: 280
- Game Page: 309
- Total: 1,299 lines
Visual Showcase
ScoreBoard (Sticky Header)
┌─────────────────────────────────────────┐
│ AWAY INNING HOME │
│ 5 7 3 │
│ ▼ BOTTOM │
│ │
│ Count: 2-1 Outs: ●●○ Runners: ◆ │
└─────────────────────────────────────────┘
GameBoard (Baseball Diamond)
2nd ◆
/ \
/ \
3rd ◆ ◆ 1st
\ /
\ /
Home ⬥
[P] Pitcher
vs
[B] Batter
PlayByPlay (Feed)
┌──────────────────────────────┐
│ ● Inning 7 #45 │
│ Mike Trout singles to CF │
│ ↑ 1 Run • Single │
├──────────────────────────────┤
│ ● Inning 7 #44 │
│ Strikeout swinging │
│ ✗ 1 Out • K │
└──────────────────────────────┘
Testing Checklist
Manual Testing (Recommended)
- Dev server running (http://localhost:3001)
- TypeScript compiling without errors
- All components rendering
- Test on mobile device (375px width)
- Test on tablet (768px width)
- Test on desktop (1024px+ width)
- Dark mode toggle works
- WebSocket connection establishes
- Game state updates in real-time
- Play-by-play feed updates
- Runners update on diamond
- Score updates in header
- Action buttons enable/disable correctly
Browser Testing
- Chrome (latest)
- Safari (iOS)
- Firefox (latest)
- Edge (latest)
Next Steps (Phase F3)
Phase F3: Decision Input Workflow (Week 3)
Now that game state displays beautifully, we need to add interactive decision inputs:
- Defensive positioning - Infield/outfield depth selection
- Pitcher focus - Targeting specific hitters
- Stolen base attempts - Per-runner SB toggles
- Offensive approach - Swing/bunt/hit-and-run
- Turn indicators - Whose decision is needed
- Decision validation - Client-side checks before submit
- Mobile-friendly forms - Large buttons, clear labels
Components to Create:
components/Decisions/DefensiveSetup.vuecomponents/Decisions/PitcherFocusInput.vuecomponents/Decisions/StolenBaseInputs.vuecomponents/Decisions/OffensiveApproach.vuecomponents/Decisions/DecisionPanel.vuecomponents/UI/ActionButton.vuecomponents/UI/ButtonGroup.vue
Estimated Time: 4-6 hours
Performance Metrics
Build Stats:
- TypeScript compilation: ✅ 0 errors
- Vite client build: ✅ 20ms
- Vite server build: ✅ 26ms
- Nitro build: ✅ 364ms
Component Size:
- Total components: 4
- Total lines: 990 (excluding game page)
- Average per component: 248 lines
- Reusability: High (league-agnostic)
Success Criteria
Phase F2 is COMPLETE when:
- ✅ ScoreBoard displays all game state (score, inning, count, outs, runners)
- ✅ GameBoard shows baseball diamond with player positions
- ✅ CurrentSituation displays current batter vs pitcher
- ✅ PlayByPlay shows feed of recent plays
- ✅ All components mobile-responsive (375px - 1920px+)
- ✅ WebSocket integration ready for real-time updates
- ✅ Game page layout optimized for mobile and desktop
- ✅ Loading and connection states handled gracefully
- ✅ TypeScript compiles without errors
- ✅ Dev server running successfully
All criteria met! ✅
Lessons Learned
- Mobile-first saves time - Designing for mobile first makes desktop scaling easier
- Gradients add polish - Simple gradients make UI feel modern without heavy assets
- Animations enhance UX - Subtle pulse/fade effects make UI feel alive
- Color coding works - Green (runs), Red (outs), Blue (hits) intuitive
- Empty states matter - Friendly messages improve first-time experience
- Sticky scoreboard is gold - Always-visible context improves gameplay
Known Issues
Toast Notification Positioning Bug
Issue: Toast notifications only render when positioned at screen center (top-1/2, left-1/2). Bottom positioning fails silently.
Symptoms:
- ✅ Works:
fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 - ❌ Fails:
fixed bottom-8 left-1/2 -translate-x-1/2 - ❌ Fails: Using
<Transition>wrapper around toast div
Attempted Solutions:
- Changed z-index from 50 → 100 → 9999 (no effect)
- Removed Transition wrapper (still fails at bottom)
- Changed positioning units (px, rem, vh - all fail)
- Verified v-if toastMessage reactive state (works)
- Console logs show toast state active (works)
Root Cause: Unknown - possibly Tailwind transform conflict, Nuxt SSR hydration issue, or viewport calculation bug
Current Workaround:
- Demo page uses center-screen position (working)
- Production components should implement UI store toast system instead
- File:
frontend-sba/pages/demo.vue:165-177
Priority: Low (demo-only issue, not affecting production components)
Next Steps for Fix:
- Test with Pinia UI store toast system
- Try absolute positioning instead of fixed
- Debug with browser DevTools element inspector
- Check if
<Teleport>to body helps
Status: ✅ Phase F2 Complete - Ready for Phase F3 (with known toast positioning bug documented) Next Session: Implement decision input workflow Backend Integration: Ready (730/731 tests passing, all WebSocket handlers complete) Frontend Progress: Phase F1 (70%) + Phase F2 (100%) = ~85% through Week 2 goals
Document Version: 1.1 Last Updated: 2025-01-10 (Added Known Issues section) Contributors: Claude (Jarvis), Cal Corum