Sole developer · 2025-10–present

ClaySmasher

A local-first performance tracker for clay-target shooting

72
releases shipped
iOS, macOS, and Android, promoted from beta as the exact tested binary
as of Sep 2026
82
merged pull requests
as of Sep 2026
10
shooting disciplines scored
including Commonwealth Down-the-Line's non-standard two-barrel model
as of Sep 2026
390
test files
behind a CI coverage ratchet
as of Sep 2026

The app

Clay-target shooters keep score on paper. ClaySmasher keeps it on the phone (every stand, every station, every discipline) and then tells you things paper cannot: how you shoot in a crosswind, whether the new choke actually helped, which station you have been quietly losing for a month.

It runs entirely on the device. SQLite, no backend, no account, no telemetry. There is no server to leak anything because there is no server.

The ClaySmasher app icon.

v1.3.2 · iOS, macOS, Android

  • Analytics screen showing hit-rate trends across recent sessions.

    Analytics

  • A list of logged shooting sessions with dates, locations and scores.

    Sessions

  • Stand-by-stand scoring in progress during a round.

    Live scoring

Domain depth is the hard part

Ten disciplines, each with its own scoring rules, and they disagree with each other. Commonwealth Down-the-Line awards different points for a first-barrel and a second-barrel kill, which breaks the assumption every other discipline makes that a hit is a hit. Getting that wrong is not a rounding error. It produces scores a competitor would immediately know were fake.

That one discipline has its own written implementation reference in the repository, and it earned it: an AI coding agent implemented it once from the rules alone, the result was merged, and it was wrong. It got reverted and reimplemented against the written reference. The reference exists because of the failure.

The pipeline around it

The interesting engineering is what ships the app.

trapper is a 2,700-line release CLI unifying setup, code generation, sharded testing, and multi-platform Fastlane release across iOS, macOS, and Android. Its beta-to-production model promotes the exact binary that was tested rather than rebuilding from source, because a rebuild is a different artifact, however identical the inputs look.

Generated files cannot be hand-edited. A PreToolUse hook blocks any write to *.g.dart, *.freezed.dart, *.mocks.dart, or pubspec.lock outright. This is not a lint warning after the fact; the edit does not happen. If the generated output is wrong, you fix the generator.

Two custom review agents run with deliberately narrow powers. An architecture reviewer gets read-only tools and a turn limit, and reports file-and-line boundary violations. A localization reviewer cross-references all six locale files against each other. Neither can write.

Every pull request is gated by an automated review against six named blocking rules drawn from a versioned constitution (ten-plus principles, amended across eight months of shipping).

A postmortem, encoded

There is an Android release-build job in CI that exists for one reason: integration tests run in debug mode, and a debug build hid an enableEdgeToEdge receiver-type bug that broke an actual release. The job is the fix. The bug is the reason anyone would tolerate the extra CI minutes.