← Portfolio

The lab · Golf

Card Caddie

Live, handicap-aware scoring for a golf trip.

Everyone in the group scores from their phone and the leaderboard moves within ten seconds. Stableford, Skins, Nassau and Net Stroke settle themselves, so nobody opens a calculator at the bar.

Status
Live
Domain
Golf
Since
2026
Role
Design, engineering

The problem

A golf trip runs on a spreadsheet by the grill and a group-chat running tally. Nobody agrees on the net scores, and the Nassau gets settled by whoever shouts loudest. GHIN, GolfShot and 18Birdies are built for one golfer, not four friends playing three rounds in a weekend.

What was built

  • Trips with rosters, scheduled rounds and per-round group assignments.
  • An 18-hole entry grid sized for one thumb on a tee box.
  • Handicap-adjusted net scoring from the WHS Course Handicap, with per-player tees adjusted relative to the group.
  • Four formats computed on the server: Stableford, Skins with carries, Nassau, and Net Stroke. Scramble rounds score by team.
  • Leaderboards that refresh silently across every phone in the group.
  • Phone sign-in by SMS code. A player added by phone number claims their slot later from a link.
  • Solo rounds, a feed of finished rounds with kudos and comments, and public or private profiles.

How it was built

  1. 01

    Write the contract first

    Every endpoint lives in one OpenAPI file. The client hooks and the validators are generated from it, so the phone and the server cannot disagree about a score.

    • OpenAPI
    • Orval
  2. 02

    Keep the math on the server

    All four formats live in one scoring module with unit tests. Every phone renders the same leaderboard because only one place computes it.

    • TypeScript
    • Vitest
  3. 03

    Design for the cart path

    Built for one hand, outdoor sunlight and a player who will not read instructions. The look borrows from printed scorecards: tabular numerals, dashed rules, cream on forest green.

  4. 04

    Test it on a trip

    It ships before a golf weekend and gets fixed after one. 349 commits so far.

    • Claude Code
    • Replit

Stack

Frontend
  • React 19
  • Vite 7
  • Wouter
  • TanStack Query
  • Tailwind CSS 4
  • shadcn/ui
  • PWA
Backend
  • Express 5 with Pino logging
  • Drizzle ORM on Postgres, 14 tables
API
  • An OpenAPI spec as the source of truth
  • Orval generates the React Query hooks and Zod validators from it
Services
  • Twilio Verify for SMS codes
  • GolfCourseAPI for tees, pars and stroke indexes
  • Replit Autoscale

How it works: one player taps a score into the grid. The server stores it, recomputes net scores from each player's Course Handicap and tee, and rebuilds the leaderboard for every format on the round. Every other phone in the group picks up the new standings on its next refresh.