§1The problem
Organising a pickup game every week (who's coming, even teams, score and history) was done in a group chat and from memory.
§2What I built
- PWAInstallable web app with 4 tabs: players, match, history and stats.
- Player levelVisible in the app but hidden on shared cards.
- Team drawLevel suggested by an explainable heuristic (never applied on its own).
- Roster importFrom the group chat messages, using an AI model.
- SharingShareable match card (image) and video with clips, like match.day.
- Watch appApple Watch and iPhone (record, log goals); a mock server to test without a watch.
§3The hard parts
5 entries- 01
Rewrite as a monorepo shared with match.day
Server, web and shared types, done in phases with tests.
- 02
Database migration
A backup before migrating and a hash of each migration.
- 03
Offline
Service worker, queue and local copy, with safe batched resending.
- 04
Two cameras
The iPhone looks for the other camera when it opens.
- 05
Local access guard
Host allowlist, cross-site requests refused, IP addresses fully validated.
§4Result
In production on my home server (web and API). Every stage of the plan, including the watch app and the two cameras, is done and was tested with the person who wears the watch. Sharing the match card was tested on a real iPhone and works.
§5Stack
- Next.js 16
- React 19
- TypeScript 7
- Tailwind 4
- Zustand
- SQLite (better-sqlite3)
- ffmpeg
- SwiftUI / watchOS
- pm2
- Tailscale