Fix Gameday lineup row selection and deselect behavior
- Fix bug where clicking to select a player in the middle of the lineup would operate on the last added player instead of the clicked row - Deselect now requires clicking the same row twice (for screenshots) - Clicking the table after deselect re-enables selection mode - Fix main.py to actually launch the TUI app - Add CLAUDE.md with codebase documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9c59c79c98
commit
3adc064a42
90
CLAUDE.md
Normal file
90
CLAUDE.md
Normal file
@ -0,0 +1,90 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
SBA Scout is a TUI (Terminal User Interface) application for SBA fantasy baseball scouting and team management. Built with Textual for the interface, SQLAlchemy for async database operations, and pydantic-settings for configuration.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Install dependencies (use uv, not pip)
|
||||
uv pip install -e .
|
||||
|
||||
# Run the application
|
||||
sba-scout # via console script
|
||||
python main.py # direct execution
|
||||
|
||||
# Lint code
|
||||
ruff check src/
|
||||
ruff check --fix src/ # auto-fix
|
||||
|
||||
# Format code
|
||||
ruff format src/
|
||||
```
|
||||
|
||||
There is no test suite currently (tests/ directory exists but is empty).
|
||||
|
||||
## Architecture
|
||||
|
||||
### Layer Structure
|
||||
|
||||
```
|
||||
src/sba_scout/
|
||||
├── api/ # External API integration
|
||||
├── calc/ # Scoring and statistics calculations
|
||||
├── db/ # SQLAlchemy models and queries
|
||||
├── screens/ # Textual UI screens
|
||||
├── app.py # Main application and DashboardScreen
|
||||
└── config.py # Pydantic settings management
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
1. **API Layer** (`api/`) syncs data from League API → SQLite database
|
||||
2. **CSV Import** (`api/importer.py`) loads batter/pitcher card data from spreadsheets
|
||||
3. **Database Layer** (`db/`) stores teams, players, cards, lineups
|
||||
4. **Calc Layer** (`calc/`) computes standardized matchup scores using league stats
|
||||
5. **UI Layer** (`screens/`, `app.py`) displays data via Textual TUI
|
||||
|
||||
### Key Patterns
|
||||
|
||||
- **Async throughout**: SQLAlchemy async with aiosqlite, httpx async client
|
||||
- **Lazy singletons**: `get_settings()` returns global Settings, database engine initialized on first use
|
||||
- **Context managers**: API client and database sessions use `async with`
|
||||
- **Layered config**: Pydantic defaults → .env file → data/settings.yaml (user editable)
|
||||
|
||||
### Database Models (db/models.py)
|
||||
|
||||
Core models: `Team`, `Player`, `BatterCard`, `PitcherCard`, `Lineup`, `MatchupCache`, `StandardizedScoreCache`, `SyncStatus`, `Transaction`
|
||||
|
||||
### Screens (screens/)
|
||||
|
||||
- `roster.py` - Tabbed roster view (majors/minors/IL)
|
||||
- `matchup.py` - Batter vs pitcher analysis
|
||||
- `lineup.py` - Lineup builder with drag/drop
|
||||
- `gameday.py` - Combined matchup + lineup view
|
||||
- `settings.py` - Configuration UI with YAML persistence
|
||||
|
||||
### Matchup Scoring System (calc/)
|
||||
|
||||
- Standardizes stats to -3 to +3 range based on league averages/stdev
|
||||
- Handedness-aware (vLHP/vRHP for batters, vLHB/vRHB for pitchers)
|
||||
- Weighted composite scores with tier assignment (A/B/C/D/F)
|
||||
- Cached for performance in `StandardizedScoreCache`
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables use `SBA_SCOUT_` prefix with `__` for nesting:
|
||||
- `SBA_SCOUT_API__BASE_URL`
|
||||
- `SBA_SCOUT_API__API_KEY`
|
||||
- `SBA_SCOUT_TEAM__TEAM_ABBREV`
|
||||
|
||||
User settings saved to `data/settings.yaml` (editable via Settings screen or directly).
|
||||
|
||||
## Code Style
|
||||
|
||||
- Line length: 100 characters
|
||||
- Python 3.12+
|
||||
- Ruff rules: E, F, I, N, W, UP (standard + imports + naming + upgrades)
|
||||
4
main.py
4
main.py
@ -1,6 +1,4 @@
|
||||
def main():
|
||||
print("Hello from sba-scout!")
|
||||
|
||||
from sba_scout.app import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -12,9 +12,10 @@ import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar, Optional
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.app import ComposeResult, on
|
||||
from textual.binding import Binding
|
||||
from textual.containers import Horizontal, ScrollableContainer, Vertical
|
||||
from textual.events import Click
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import (
|
||||
Button,
|
||||
@ -91,6 +92,7 @@ class GamedayScreen(Screen):
|
||||
lineup_slots: list[LineupSlot] = []
|
||||
saved_lineups: list[Lineup] = []
|
||||
current_lineup_name: str = ""
|
||||
_last_lineup_row: int | None = None # Track last selected row for deselect toggle
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose the gameday layout with side-by-side panels."""
|
||||
@ -465,15 +467,17 @@ class GamedayScreen(Screen):
|
||||
await self.action_save_lineup()
|
||||
|
||||
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
||||
"""Handle row selection - toggle cursor off if clicking same row."""
|
||||
"""Handle row selection - toggle cursor off only if clicking same row twice."""
|
||||
table = event.data_table
|
||||
if table.id == "lineup-table":
|
||||
# If cursor is "row" type, switch to "none" to hide highlight
|
||||
# User can click again or use arrow keys to restore
|
||||
if table.cursor_type == "row":
|
||||
current_row = event.cursor_row
|
||||
# Only toggle off if clicking the same row that was already selected
|
||||
if table.cursor_type == "row" and current_row == self._last_lineup_row:
|
||||
table.cursor_type = "none"
|
||||
self._last_lineup_row = None
|
||||
else:
|
||||
table.cursor_type = "row"
|
||||
self._last_lineup_row = current_row
|
||||
|
||||
def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
|
||||
"""Re-enable cursor when navigating with keys."""
|
||||
@ -482,6 +486,14 @@ class GamedayScreen(Screen):
|
||||
# Re-enable cursor when user navigates
|
||||
table.cursor_type = "row"
|
||||
|
||||
@on(Click, "#lineup-table")
|
||||
def on_lineup_table_click(self, event: Click) -> None:
|
||||
"""Re-enable cursor when lineup table is clicked while deselected."""
|
||||
lineup_table = self.query_one("#lineup-table", DataTable)
|
||||
if lineup_table.cursor_type == "none":
|
||||
lineup_table.cursor_type = "row"
|
||||
self._last_lineup_row = None
|
||||
|
||||
# =========================================================================
|
||||
# Actions
|
||||
# =========================================================================
|
||||
|
||||
Loading…
Reference in New Issue
Block a user