Files
bscscore/README.md
T
Frank Schwenk ed7c6232c1 refactor: streamline new-game player selection ux
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
2026-04-14 15:22:56 +02:00

336 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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]