mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-07-04 12:03:13 +02:00
Adds a React/TypeScript frontend (web/) with a Python stdlib HTTP server backend. The UI mirrors the CLI `stats` command in the browser, with real-time sliders for ProtonDB rating, HLTB confidence thresholds, daily play time, per-game time cap, playtime mode, no-HLTB-data fallback, and a target-date planner. A parity badge confirms the client-side totals reproduce the CLI defaults exactly (786 / 67031.1h / 143017.2h / 238447.9h). Python side: - `_web_dataset.py`: offline projection of HLTB/ProtonDB/snapshot caches into a compact, secret-free JSON payload; 100% branch coverage - `_web_server.py`: zero-dependency stdlib HTTP server serving the built Vite bundle and the /api/dataset endpoint; 100% branch coverage - `main.py`: new `serve` command wiring Frontend (Vitest + RTL, 100% branch coverage enforced): - TypeScript port of ProtonDB compound rating rule with full parity - Pure client-side filtering via estimate.ts (no server round-trips) - SVG completion timeline chart, sortable/searchable game table - Steam dark theme; responsive layout Pre-commit: adds `vitest-coverage` hook at pre-push stage requiring 100% branch coverage on the React codebase. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
82 lines
1.8 KiB
TypeScript
82 lines
1.8 KiB
TypeScript
// Shared test factories. Lives under src/test/ which is excluded from the
|
|
// app build and from coverage.
|
|
|
|
import type { Filters, WebDataset, WebGame, WebStateInfo } from '../types'
|
|
|
|
export function makeGame(over: Partial<WebGame> = {}): WebGame {
|
|
return {
|
|
app_id: 1,
|
|
name: 'Game',
|
|
completion_pct: 0,
|
|
playtime_minutes: 60,
|
|
rush_hours: 10,
|
|
leisure_hours: 20,
|
|
worst_hours: 25,
|
|
count_comp: 20,
|
|
comp_100_count: 5,
|
|
hltb_game_id: 0,
|
|
protondb_tier: 'gold',
|
|
protondb_trending_tier: 'gold',
|
|
protondb_score: 0.8,
|
|
...over,
|
|
}
|
|
}
|
|
|
|
export function makeFilters(over: Partial<Filters> = {}): Filters {
|
|
return {
|
|
minCountComp: 15,
|
|
minComp100: 3,
|
|
minConfidenceSum: 18,
|
|
protonMode: 'playable',
|
|
protonMinTier: 'gold',
|
|
protonTreatMissingAsPass: true,
|
|
dailyHours: 4,
|
|
basis: 'leisure',
|
|
maxHoursPerGame: 0,
|
|
playtimeMode: 'all',
|
|
includeNoData: false,
|
|
fallbackHours: 20,
|
|
excluded: new Set<number>(),
|
|
search: '',
|
|
targetDate: '',
|
|
...over,
|
|
}
|
|
}
|
|
|
|
export function makeState(over: Partial<WebStateInfo> = {}): WebStateInfo {
|
|
return {
|
|
current_app_id: null,
|
|
current_game_name: '',
|
|
games_done: 0,
|
|
days_elapsed: 0,
|
|
enforcement_started_at: '',
|
|
pace_games_per_day: 0,
|
|
...over,
|
|
}
|
|
}
|
|
|
|
export function makeDataset(
|
|
games: WebGame[] = [makeGame()],
|
|
over: Partial<WebDataset> = {},
|
|
): WebDataset {
|
|
return {
|
|
games,
|
|
state: makeState(),
|
|
defaults: {
|
|
min_comp_100_polls: 3,
|
|
min_count_comp: 15,
|
|
min_confidence_sum: 18,
|
|
min_playable_tier: 'gold',
|
|
hours_per_day_presets: [2, 4, 6, 8],
|
|
},
|
|
default_summary: {
|
|
qualifying: games.length,
|
|
rush_total: 0,
|
|
leisure_total: 0,
|
|
worst_total: 0,
|
|
},
|
|
generated_at: '2026-05-29T00:00:00+00:00',
|
|
...over,
|
|
}
|
|
}
|