diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 9ee7892..e6d1a52 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -31,15 +31,15 @@ Requirements for Phase 1 (Development Focus: Live Tournament Management). Each m ### Tournament Clock -- [ ] **CLOCK-01**: Countdown timer per level with second-granularity display, millisecond-precision internally -- [ ] **CLOCK-02**: Separate break durations with distinct visual treatment -- [ ] **CLOCK-03**: Pause/resume with visual indicator across all displays -- [ ] **CLOCK-04**: Manual advance forward/backward between levels -- [ ] **CLOCK-05**: Jump to any level by number -- [ ] **CLOCK-06**: Total elapsed time display -- [ ] **CLOCK-07**: Configurable warning thresholds (e.g., 60s, 30s, 10s) with audio and visual alerts -- [ ] **CLOCK-08**: Clock state authoritative on Leaf; clients receive ticks via WebSocket (1/sec normal, 10/sec final 10s) -- [ ] **CLOCK-09**: Reconnecting clients receive full clock state immediately +- [x] **CLOCK-01**: Countdown timer per level with second-granularity display, millisecond-precision internally +- [x] **CLOCK-02**: Separate break durations with distinct visual treatment +- [x] **CLOCK-03**: Pause/resume with visual indicator across all displays +- [x] **CLOCK-04**: Manual advance forward/backward between levels +- [x] **CLOCK-05**: Jump to any level by number +- [x] **CLOCK-06**: Total elapsed time display +- [x] **CLOCK-07**: Configurable warning thresholds (e.g., 60s, 30s, 10s) with audio and visual alerts +- [x] **CLOCK-08**: Clock state authoritative on Leaf; clients receive ticks via WebSocket (1/sec normal, 10/sec final 10s) +- [x] **CLOCK-09**: Reconnecting clients receive full clock state immediately ### Blind Structure @@ -314,15 +314,15 @@ Which phases cover which requirements. Updated during roadmap reorganization. | EXPORT-02 | Phase 2 | Pending | | EXPORT-03 | Phase 2 | Pending | | EXPORT-04 | Phase 2 | Pending | -| CLOCK-01 | Phase 1 | Pending | -| CLOCK-02 | Phase 1 | Pending | -| CLOCK-03 | Phase 1 | Pending | -| CLOCK-04 | Phase 1 | Pending | -| CLOCK-05 | Phase 1 | Pending | -| CLOCK-06 | Phase 1 | Pending | -| CLOCK-07 | Phase 1 | Pending | -| CLOCK-08 | Phase 1 | Pending | -| CLOCK-09 | Phase 1 | Pending | +| CLOCK-01 | Phase 1 | Complete | +| CLOCK-02 | Phase 1 | Complete | +| CLOCK-03 | Phase 1 | Complete | +| CLOCK-04 | Phase 1 | Complete | +| CLOCK-05 | Phase 1 | Complete | +| CLOCK-06 | Phase 1 | Complete | +| CLOCK-07 | Phase 1 | Complete | +| CLOCK-08 | Phase 1 | Complete | +| CLOCK-09 | Phase 1 | Complete | | BLIND-01 | Phase 1 | Pending | | BLIND-02 | Phase 1 | Pending | | BLIND-03 | Phase 1 | Pending | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 3e5b2ff..67baf8a 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -141,7 +141,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Tournament Engine | 2/14 | Executing | - | +| 1. Tournament Engine | 4/14 | Executing | - | | 2. Display Views + Player PWA | 0/TBD | Not started | - | | 3. Core Sync + Platform Identity | 0/TBD | Not started | - | | 4. Digital Signage + Events Engine | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 816a8a2..df191fd 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -8,7 +8,7 @@ progress: total_phases: 1 completed_phases: 0 total_plans: 14 - completed_plans: 3 + completed_plans: 4 --- # Project State @@ -32,18 +32,18 @@ Progress: [███░░░░░░░] 29% ## Performance Metrics **Velocity:** -- Total plans completed: 3 +- Total plans completed: 4 - Average duration: 10min -- Total execution time: 0.50 hours +- Total execution time: 0.63 hours **By Phase:** | Phase | Plans | Total | Avg/Plan | |-------|-------|-------|----------| -| 01-tournament-engine | 3 | 30min | 10min | +| 01-tournament-engine | 4 | 38min | 10min | **Recent Trend:** -- Last 5 plans: 01-01 (15min), 01-02 (10min), 01-10 (5min) +- Last 5 plans: 01-01 (15min), 01-02 (10min), 01-10 (5min), 01-04 (8min) - Trend: accelerating *Updated after each plan completion* @@ -69,6 +69,10 @@ Recent decisions affecting current work: - [01-10]: ESM type:module required in package.json for SvelteKit/Vite compatibility - [01-10]: frontend/build/ tracked in git (not gitignored) for go:embed - [01-10]: Catppuccin colors defined as CSS custom properties rather than @catppuccin/palette JS package +- [01-04]: Clock ticker uses 100ms resolution with broadcast gating (not two separate tickers) +- [01-04]: Crash recovery always restores clock as paused (operator must explicitly resume) +- [01-04]: Overtime mode defaults to repeat (last level repeats indefinitely) +- [01-04]: State change callback is async to avoid holding clock mutex during DB writes ### Pending Todos @@ -86,5 +90,5 @@ None yet. ## Session Continuity Last session: 2026-03-01 -Stopped at: Completed 01-10-PLAN.md (SvelteKit Frontend Scaffold + Theme + Clients) +Stopped at: Completed 01-04-PLAN.md (Clock Engine) Resume file: None diff --git a/.planning/phases/01-tournament-engine/01-04-SUMMARY.md b/.planning/phases/01-tournament-engine/01-04-SUMMARY.md new file mode 100644 index 0000000..4010398 --- /dev/null +++ b/.planning/phases/01-tournament-engine/01-04-SUMMARY.md @@ -0,0 +1,153 @@ +--- +phase: 01-tournament-engine +plan: 04 +subsystem: api +tags: [go, clock, websocket, ticker, state-machine, warnings, chi, rest-api] + +# Dependency graph +requires: + - phase: 01-tournament-engine + provides: LibSQL database with blind_levels table, WebSocket hub, chi HTTP server, JWT auth middleware +provides: + - Server-authoritative clock engine with state machine (stopped/running/paused) + - Drift-free ticker with 1/sec normal and 10/sec final 10s broadcast + - Clock registry for multi-tournament support (thread-safe) + - Clock API routes (start, pause, resume, advance, rewind, jump, get, warnings) + - Configurable warning thresholds with WebSocket events + - Clock state persistence to DB for crash recovery + - ClockSnapshot for reconnecting clients +affects: [01-tournament-engine, display-views, player-pwa] + +# Tech tracking +tech-stack: + added: [] + patterns: + - Clock state machine with mutex-protected transitions + - Ticker goroutine with 100ms resolution using monotonic clock + - Broadcast rate adaptation (1/sec normal, 10/sec final 10s) + - State change callbacks for async DB persistence + - Crash recovery restores as paused (operator must explicitly resume) + - ClockSnapshot as single source of truth for all client communication + +key-files: + created: + - internal/clock/engine.go + - internal/clock/ticker.go + - internal/clock/registry.go + - internal/clock/warnings.go + - internal/clock/engine_test.go + - internal/clock/warnings_test.go + - internal/clock/registry_test.go + - internal/server/routes/clock.go + modified: + - internal/server/server.go + - cmd/leaf/main.go + - cmd/leaf/main_test.go + - internal/auth/pin.go + +key-decisions: + - "Clock ticker uses 100ms resolution with broadcast gating (not two separate tickers) for simplicity" + - "Crash recovery always restores clock as paused -- operator must explicitly resume for safety" + - "Overtime mode defaults to repeat (last level repeats indefinitely) with configurable stop option" + - "State change callback is async (goroutine) to avoid holding clock mutex during DB writes" + - "Clock routes use chi sub-router with floor role requirement on mutations" + +patterns-established: + - "AuditRecorder interface for decoupled audit trail recording (avoids circular imports)" + - "StateChangeCallback pattern for async persistence on meaningful state changes" + - "Registry pattern for managing multiple concurrent engines per tournament" + +requirements-completed: [CLOCK-01, CLOCK-02, CLOCK-03, CLOCK-04, CLOCK-05, CLOCK-06, CLOCK-07, CLOCK-08, CLOCK-09] + +# Metrics +duration: 8min +completed: 2026-03-01 +--- + +# Phase 1 Plan 04: Clock Engine Summary + +**Server-authoritative tournament clock with state machine, drift-free 100ms ticker, configurable warnings, multi-tournament registry, REST API, and DB persistence -- verified with 25 unit tests** + +## Performance + +- **Duration:** 8 min +- **Started:** 2026-03-01T02:48:21Z +- **Completed:** 2026-03-01T02:56:31Z +- **Tasks:** 2 +- **Files modified:** 12 + +## Accomplishments +- Complete clock state machine (stopped/running/paused) with all transitions and guard conditions +- Drift-free ticker using Go monotonic clock at 100ms resolution with adaptive broadcast rate +- Multi-tournament clock registry with independent engines and per-engine ticker goroutines +- Full REST API with role-based access (floor+ for mutations, any auth for reads) +- Configurable warning thresholds (default 60s/30s/10s) with WebSocket events +- Clock state persisted to DB on every meaningful state change for crash recovery +- Hand-for-hand mode support (clock pauses, per-table deduction via seating engine) +- Overtime handling (repeat last level or stop -- configurable) + +## Task Commits + +Each task was committed atomically: + +1. **Task D1: Clock engine state machine and ticker** - `9ce05f6` (feat) +2. **Task D2: Warnings, API routes, tests, and server wiring** - `ae90d9b` (feat) + +## Files Created/Modified +- `internal/clock/engine.go` - ClockEngine state machine, Level/Warning/Snapshot structs, all operations +- `internal/clock/ticker.go` - StartTicker with 100ms resolution and adaptive broadcast rate +- `internal/clock/registry.go` - Thread-safe ClockRegistry managing multiple engines +- `internal/clock/warnings.go` - Warning system documentation (logic in engine.go) +- `internal/clock/engine_test.go` - 16 tests: state machine, countdown, auto-advance, pause/resume, jump, rewind, hand-for-hand, snapshot, overtime, crash recovery +- `internal/clock/warnings_test.go` - 5 tests: threshold detection, no re-emit, reset on level change, defaults, custom +- `internal/clock/registry_test.go` - 4 tests: get/create, get, remove, shutdown +- `internal/server/routes/clock.go` - 8 API endpoints with DB integration and role-based access +- `internal/server/server.go` - Clock registry wired into server constructor and route registration +- `cmd/leaf/main.go` - Clock registry created during startup, shutdown on exit +- `cmd/leaf/main_test.go` - Test setup updated with clock registry parameter +- `internal/auth/pin.go` - Fix missing crypto/rand import (auto-fix) + +## Decisions Made +- Clock ticker uses single 100ms timer with broadcast gating rather than two separate tickers (simpler, same result) +- Crash recovery always restores as paused -- safer than auto-resuming with potentially stale remaining time +- Overtime defaults to repeat mode (most common in poker tournaments) +- State change callback runs in a goroutine to prevent DB latency from blocking the clock ticker +- AuditRecorder is an interface (not direct import) to avoid circular dependency between clock and audit packages + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] Missing crypto/rand import in auth/pin.go** +- **Found during:** Task D2 (compiling routes package which depends on auth) +- **Issue:** internal/auth/pin.go uses `rand.Read` but was missing `crypto/rand` import, preventing compilation of the routes package +- **Fix:** Added `"crypto/rand"` to the import block +- **Files modified:** internal/auth/pin.go +- **Verification:** `go build ./...` passes, `go vet ./...` passes +- **Committed in:** ae90d9b (Task D2 commit) + +--- + +**Total deviations:** 1 auto-fixed (1 blocking) +**Impact on plan:** Single missing import in a dependency. No scope creep. + +## Issues Encountered +None + +## User Setup Required +None - no external service configuration required. + +## Next Phase Readiness +- Clock engine fully operational for all tournament clock requirements +- API endpoints ready for frontend integration (Plan K/L) +- Clock registry supports multi-tournament operation (MULTI-01) +- Warning events ready for display views (Phase 2) +- DB persistence ensures clock survives server restart + +## Self-Check: PASSED + +All 8 created files verified present. Both commit hashes (9ce05f6, ae90d9b) found in git log. SUMMARY.md exists at expected path. + +--- +*Phase: 01-tournament-engine* +*Completed: 2026-03-01*