A scorepad for the trick-taking card game Judgement (also known as Oh Hell or Kachuful): bids, tricks, dealer rotation, trump suit, and a lifetime leaderboard for your group — all narrated by a theatrical courtroom judge, because scorekeeping should be a little fun.
- Court (live game) — tap-to-select chips for bids and tricks (no fiddly number keyboard), automatic dealer and trump rotation, an optional "no free pass" hook rule, undo for misentered rounds, and a manual dealer override for when the app and the actual table disagree.
- Jury (players) — a persistent roster with lifetime stats per player.
- Ranks (leaderboard) — lifetime score, trials played, and judgement rate (how often a player's bid exactly matched their tricks won) across every game.
- Archive (history) — every completed game saved with final standings, so closing one out doesn't erase it.
- Wisdom (tips) — bite-sized strategy tips you can mark as mastered, with a rank that climbs from First-Time Juror to Chief Justice of Judgement.
All data is stored locally in the browser (localStorage) — nothing leaves your device.
npm install
npm run devThen open the printed local URL. For a production build:
npm run build
npm run previewsrc/
main.jsx # React entry point + observability bootstrap
App.jsx # top-level layout + tab routing
env.d.ts # ambient types (Vite env, injected globals)
constants/
theme.js # colors, fonts, shared style tokens, global CSS
copy.js # every user-facing string (Judge Trumpington's voice)
lib/
storage.js # persistence adapter (Capacitor Preferences -> localStorage -> memory)
gameLogic.js # pure game rules: round plans, scoring, hook rule
validation.js # load-time parsing, sanitization, and shape validation
logger.js # leveled ring-buffered logger + crash-reporter hook
hooks/
useJudgementGame.js # all app state + actions, no rendering logic
components/
ErrorBoundary.jsx # themed crash screen with diagnostics export
layout/ # Header, TabBar
ui/ # Card, Button, NumberChips, Toast, ConfirmModal
panels/ # RosterPanel, SetupPanel, PlayPanel, LeaderboardPanel,
# HistoryPanel, TipsPanel
Every lib/ and hooks/ module has a colocated *.test.js/*.test.jsx.
The split follows one rule: state and rules live in hooks/ and lib/, presentation lives in components/, and words live in constants/copy.js. If you want to reskin the voice from courtroom to something else, copy.js is the only file you need to touch. If you want to change how scoring or the hook rule works, lib/gameLogic.js is the only file you need to touch.
- Round structure: deal down from a starting hand size to 1 card, optionally climbing back up afterward.
- Scoring: configurable bonus for an exact bid, points per trick, and an optional penalty for missed bids.
- Hook rule ("no free pass"): the dealer can't bid a number that would make every bid at the table add up exactly to the cards dealt.
- Logging & observability:
src/lib/logger.js(leveled, ring-buffered, crash-reporter hook),src/components/ErrorBoundary.jsx(themed crash screen with diagnostics export), global error/rejection handlers insrc/main.jsx. - iOS-durable storage: on native iOS the storage adapter automatically uses Capacitor Preferences instead of localStorage (which iOS may evict). See
src/lib/storage.js. - Tests:
npm testruns Vitest suites (59 tests) covering game rules, data validation, the storage fallback chain, the logger, and the state hook. - Type safety:
npm run typecheck— TypeScriptcheckJsover JSDoc-annotated source, no.tsmigration needed. - Security & data integrity: every persisted key is safe-parsed and shape-validated at load, with corrupt data quarantined rather than crash-looping; inputs are sanitized and clamped; a CSP locks the webview down. Threat model and full control list in SECURITY.md.
- CI: GitHub Actions runs typecheck, tests, build, and a dependency audit on every push (
.github/workflows/ci.yml).
The repo is Capacitor-ready. The complete runbook — from npx cap add ios through App Store review — is in DEPLOYMENT.md.
- Every color and font should come from
src/constants/theme.js— no one-off hex codes or font-family strings in components. - Every user-facing string should come from
src/constants/copy.js— no inline copy in JSX. src/lib/gameLogic.jshas no React or storage dependencies, so its functions are unit-tested in isolation (gameLogic.test.js).
Released under the MIT License.