ed7c6232c1
Consolidate new-game wizard steps into reusable components and remove legacy duplicate files. Replace inline player inputs with a chip-first flow and minimal name-entry modal, while improving compact-screen spacing and step-specific selection behavior. Made-with: Cursor
336 lines
11 KiB
Markdown
336 lines
11 KiB
Markdown
# BSC Score - Pool Scoring Application
|
||
|
||
A modern, responsive pool/billiards scoring application built with **Astro** and **Preact**, following best practices for maintainability, performance, and reusability.
|
||
|
||
## ✨ Features
|
||
|
||
- **Multi-game Support**: 8-Ball, 9-Ball, 10-Ball, and 14/1 Endlos
|
||
- **Real-time Scoring**: Live score tracking with undo functionality
|
||
- **Player Management**: Automatic player name history and suggestions
|
||
- **Game Management**: Create, track, and manage multiple games
|
||
- **Responsive Design**: Optimized for mobile and desktop
|
||
- **Progressive Web App**: Offline support and app-like experience
|
||
- **TypeScript**: Full type safety for better development experience
|
||
|
||
## 🏗️ Architecture
|
||
|
||
Everything reusable now lives under `src/lib`, allowing you to embed the core experience inside another React/Preact host without the Astro shell.
|
||
|
||
- **`@lib/domain`** – Pure TypeScript domain model (types, constants, validation, helpers).
|
||
- **`@lib/data`** – Persistence adapters and repositories (IndexedDB, migrations).
|
||
- **`@lib/state`** – Composable hooks that orchestrate domain + data.
|
||
- **`@lib/ui`** – Stateless UI primitives with co-located CSS modules.
|
||
- **`@lib/features/*`** – Feature bundles composing UI + state (game list, detail, lifecycle modals, new-game wizard).
|
||
|
||
The Astro `src/components` folder is now a thin host layer (screens + island bootstrap) that consumes the library.
|
||
|
||
Detailed module docs live in `src/lib/docs/architecture.md` and the individual `README.md` files under each package.
|
||
|
||
## 🚀 Getting Started
|
||
|
||
### Prerequisites
|
||
- Node.js 18+
|
||
- npm or yarn
|
||
|
||
### Installation
|
||
```bash
|
||
# Clone the repository
|
||
git clone <repository-url>
|
||
cd bscscore
|
||
|
||
# Install dependencies
|
||
npm install
|
||
|
||
# Start development server
|
||
npm run dev
|
||
```
|
||
|
||
### Available Scripts
|
||
```bash
|
||
npm run dev # Start development server
|
||
npm run build # Build for production
|
||
npm run preview # Preview production build
|
||
npm run test:record # Record browser interactions with Playwright
|
||
npm run test:e2e # Run all recorded browser automation scripts
|
||
```
|
||
|
||
### Building with Docker
|
||
```bash
|
||
# Build for production using Docker
|
||
docker run -it -v $(pwd):/app -w /app --rm node:latest npx astro build
|
||
```
|
||
|
||
## 🧪 Testing
|
||
|
||
The project uses **Playwright** for browser automation and recording. This allows you to record interactions once and replay them anytime, making it easy to test repetitive workflows.
|
||
|
||
### Quick Start
|
||
|
||
**Recording interactions:**
|
||
```bash
|
||
# Terminal 1: Start dev server
|
||
npm run dev
|
||
|
||
# Terminal 2: Start recording
|
||
npm run test:record
|
||
```
|
||
|
||
**Running recordings:**
|
||
```bash
|
||
npm run test:e2e
|
||
```
|
||
|
||
### Seed test matches via browser console
|
||
|
||
Copy/paste this snippet in your browser devtools console while the app is open. It inserts 10 sample matches (mix of 2/3 players, 8/9/10-ball, completed + active) into IndexedDB and reloads the page.
|
||
|
||
```js
|
||
await (async () => {
|
||
const DB_NAME = 'BSCScoreDB';
|
||
const DB_VERSION = 1;
|
||
const GAMES_STORE = 'games';
|
||
|
||
const openDb = () =>
|
||
new Promise((resolve, reject) => {
|
||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||
request.onsuccess = () => resolve(request.result);
|
||
request.onerror = () => reject(request.error);
|
||
});
|
||
|
||
const now = Date.now();
|
||
const minute = 60 * 1000;
|
||
|
||
const seedGames = [
|
||
{ players: ['Alex', 'Ben'], gameType: '8-Ball', raceTo: 5, scores: [5, 2], status: 'completed', minutesAgo: 190 },
|
||
{ players: ['Chris', 'Dana', 'Eli'], gameType: '8-Ball', raceTo: 6, scores: [4, 6, 2], status: 'completed', minutesAgo: 175 },
|
||
{ players: ['Faye', 'Gus'], gameType: '9-Ball', raceTo: 7, scores: [7, 5], status: 'completed', minutesAgo: 160 },
|
||
{ players: ['Hugo', 'Iris', 'Jade'], gameType: '9-Ball', raceTo: 4, scores: [2, 3, 1], status: 'active', minutesAgo: 145 },
|
||
{ players: ['Kai', 'Lena'], gameType: '10-Ball', raceTo: 8, scores: [8, 6], status: 'completed', minutesAgo: 130 },
|
||
{ players: ['Mona', 'Nico', 'Omar'], gameType: '10-Ball', raceTo: 5, scores: [5, 1, 4], status: 'completed', minutesAgo: 115 },
|
||
{ players: ['Pia', 'Quin'], gameType: '8-Ball', raceTo: 4, scores: [1, 1], status: 'active', minutesAgo: 100 },
|
||
{ players: ['Rex', 'Sara', 'Timo'], gameType: '9-Ball', raceTo: 3, scores: [3, 2, 0], status: 'completed', minutesAgo: 85 },
|
||
{ players: ['Uma', 'Vik'], gameType: '10-Ball', raceTo: 6, scores: [2, 4], status: 'active', minutesAgo: 70 },
|
||
{ players: ['Will', 'Xena', 'Yara'], gameType: '8-Ball', raceTo: 7, scores: [6, 4, 7], status: 'completed', minutesAgo: 55 },
|
||
];
|
||
|
||
const gameRecords = seedGames.map((entry, index) => {
|
||
const createdAtMs = now - entry.minutesAgo * minute;
|
||
const updatedAtMs = createdAtMs + 15 * 1000;
|
||
const id = now + index + 1;
|
||
const isThreePlayer = entry.players.length === 3;
|
||
const winnerIndex = entry.scores.indexOf(Math.max(...entry.scores));
|
||
|
||
const game = {
|
||
id,
|
||
gameType: entry.gameType,
|
||
raceTo: entry.raceTo,
|
||
status: entry.status,
|
||
createdAt: new Date(createdAtMs).toISOString(),
|
||
updatedAt: new Date(updatedAtMs).toISOString(),
|
||
log: [
|
||
{
|
||
type: 'score_change',
|
||
timestamp: new Date(createdAtMs + 5 * 1000).toISOString(),
|
||
description: 'Seed data: score progression',
|
||
},
|
||
...(entry.status === 'completed'
|
||
? [
|
||
{
|
||
type: 'game_complete',
|
||
timestamp: new Date(updatedAtMs).toISOString(),
|
||
description: `Winner: ${entry.players[winnerIndex]}`,
|
||
},
|
||
]
|
||
: []),
|
||
],
|
||
undoStack: [],
|
||
player1: entry.players[0],
|
||
player2: entry.players[1],
|
||
...(isThreePlayer ? { player3: entry.players[2] } : {}),
|
||
score1: entry.scores[0],
|
||
score2: entry.scores[1],
|
||
...(isThreePlayer ? { score3: entry.scores[2] } : {}),
|
||
breakRule: 'winnerbreak',
|
||
breakOrder: isThreePlayer ? [1, 2, 3] : [1, 2],
|
||
currentBreakerIdx: index % entry.players.length,
|
||
};
|
||
|
||
return {
|
||
id,
|
||
game,
|
||
lastModified: updatedAtMs,
|
||
syncStatus: 'local',
|
||
version: 1,
|
||
createdAt: createdAtMs,
|
||
};
|
||
});
|
||
|
||
const db = await openDb();
|
||
|
||
await new Promise((resolve, reject) => {
|
||
const tx = db.transaction([GAMES_STORE], 'readwrite');
|
||
const store = tx.objectStore(GAMES_STORE);
|
||
|
||
for (const record of gameRecords) {
|
||
store.put(record);
|
||
}
|
||
|
||
tx.oncomplete = () => resolve();
|
||
tx.onerror = () => reject(tx.error);
|
||
tx.onabort = () => reject(tx.error);
|
||
});
|
||
|
||
console.log(`Inserted ${gameRecords.length} test matches into IndexedDB.`);
|
||
window.location.reload();
|
||
})();
|
||
```
|
||
|
||
### Features
|
||
|
||
- **Record interactions**: Use Playwright codegen to capture clicks, form fills, and navigation
|
||
- **Replay scripts**: Run recorded scripts automatically
|
||
- **Duplicate & modify**: Copy any script and modify it (e.g., change the last step from clicking 'z' to clicking 'a')
|
||
- **Full scripting power**: Edit generated TypeScript files directly for custom automation
|
||
|
||
### Documentation
|
||
|
||
For detailed instructions on recording, modifying, and running scripts, see:
|
||
- **[tests/recordings/README.md](tests/recordings/README.md)** - Complete workflow documentation
|
||
|
||
## 📁 Project Structure
|
||
|
||
### **Core Components**
|
||
- `src/components/App.tsx` - Astro-bound shell orchestrating library modules
|
||
- `src/components/screens/` - Screen containers consuming `@lib/features`
|
||
- `src/lib/` - Reusable application spine (domain/data/state/ui/features)
|
||
|
||
### **State Management**
|
||
- `@lib/state/useGameState` - Game CRUD operations and persistence
|
||
- `@lib/state/useNavigation` - Application routing and screen state
|
||
- `@lib/state/useModal` - Modal state management helpers
|
||
|
||
### **Business Logic**
|
||
- `@lib/data/gameService` - Game creation, updates, and persistence orchestration
|
||
- `@lib/domain/gameUtils` - Game-related utility functions
|
||
- `@lib/domain/validation` - Input validation and sanitisation
|
||
|
||
### **Type Definitions**
|
||
- `@lib/domain/types` - Game domain types
|
||
- `@lib/ui/types` - UI component types
|
||
- `types/css-modules.d.ts` - CSS modules type support
|
||
|
||
## 🎯 Key Improvements
|
||
|
||
### **From Monolithic to Modular**
|
||
- **Before**: 360-line App component handling everything
|
||
- **After**: Separated concerns with focused, single-responsibility components
|
||
|
||
### **Type Safety**
|
||
- **Before**: JavaScript with PropTypes comments
|
||
- **After**: Full TypeScript with comprehensive type definitions
|
||
|
||
### **State Management**
|
||
- **Before**: All state in one component with prop drilling
|
||
- **After**: Custom hooks with proper encapsulation and reusability
|
||
|
||
### **Reusability**
|
||
- **Before**: Tightly coupled, single-use components
|
||
- **After**: Reusable UI components with variant support
|
||
|
||
### **Performance**
|
||
- **Before**: Client-side only rendering
|
||
- **After**: Astro's islands architecture with optimal hydration
|
||
|
||
### **Developer Experience**
|
||
- **Before**: No structure, mixed concerns
|
||
- **After**: Clear architecture, proper tooling, and documentation
|
||
|
||
## 🛠️ Technology Stack
|
||
|
||
- **Framework**: [Astro](https://astro.build/) - Islands architecture for optimal performance
|
||
- **UI Library**: [Preact](https://preactjs.com/) - Lightweight React alternative
|
||
- **Language**: [TypeScript](https://www.typescriptlang.org/) - Type safety and better DX
|
||
- **Styling**: CSS Modules with design tokens
|
||
- **Build Tool**: Vite (integrated with Astro)
|
||
|
||
## 📱 Progressive Web App
|
||
|
||
The application includes PWA features:
|
||
- Offline support with service worker
|
||
- App manifest for "Add to Home Screen"
|
||
- Optimized for mobile devices
|
||
- Fast loading with proper caching strategies
|
||
|
||
## 🎨 Design System
|
||
|
||
### **Design Tokens**
|
||
- Consistent color palette
|
||
- Standardized spacing and sizing
|
||
- Responsive breakpoints
|
||
- Accessibility-compliant contrast ratios
|
||
|
||
### **Component Variants**
|
||
- Button: `primary`, `secondary`, `danger`
|
||
- Card: `default`, `elevated`, `outlined`
|
||
- Sizes: `small`, `medium`, `large`
|
||
|
||
## 🧪 Best Practices Implemented
|
||
|
||
### **Code Quality**
|
||
- ✅ SOLID principles
|
||
- ✅ DRY (Don't Repeat Yourself)
|
||
- ✅ Single Responsibility Principle
|
||
- ✅ Proper separation of concerns
|
||
- ✅ TypeScript strict mode
|
||
|
||
### **Performance**
|
||
- ✅ Code splitting with Astro islands
|
||
- ✅ Optimized bundle size
|
||
- ✅ Efficient re-rendering with proper hooks
|
||
- ✅ CSS Modules for optimized styling
|
||
|
||
### **Accessibility**
|
||
- ✅ ARIA labels and roles
|
||
- ✅ Keyboard navigation support
|
||
- ✅ Screen reader compatibility
|
||
- ✅ High contrast support
|
||
|
||
### **Maintainability**
|
||
- ✅ Clear file structure
|
||
- ✅ Comprehensive documentation
|
||
- ✅ Type safety throughout
|
||
- ✅ Modular, testable components
|
||
|
||
## 🔧 Configuration
|
||
|
||
### **Astro Configuration**
|
||
- Preact integration with React compatibility
|
||
- TypeScript strict mode
|
||
- Optimized build settings
|
||
- Development server configuration
|
||
|
||
### **TypeScript Configuration**
|
||
- Strict type checking
|
||
- Modern ES2020+ features
|
||
- CSS Modules support
|
||
- Astro-specific types
|
||
|
||
## 📈 Future Improvements
|
||
|
||
- Unit and integration testing (if needed)
|
||
- Internationalization (i18n) support
|
||
- Advanced game statistics and analytics
|
||
- Real-time multiplayer support
|
||
- Game export/import functionality
|
||
|
||
## 🤝 Contributing
|
||
|
||
This codebase follows strict development principles:
|
||
1. Every feature must be type-safe
|
||
2. Components must be reusable and well-documented
|
||
3. Business logic must be separated from UI logic
|
||
4. All changes must follow the established architecture patterns
|
||
|
||
## 📄 License
|
||
|
||
[Include your license information here] |