2026-07-27
Sprint 13 Closes: Milestone Badges, and the Difference Between Earning Something and Just Having It
Sprint 13 had been open since before the last post. Its foundation, the actual database table for badges and the logic that stamps one at 60 and 120 confirmations, was already built and merged. So was the historical backfill that catches anyone who'd blown past those numbers before the feature existed. What paused it wasn't scope, it was sequencing: a piece of the remaining work needed to touch the exact same code a hardening sprint was about to restructure, so that sprint went first. With that done, the rest of the badge work was four small, mostly independent pieces: tell the customer the moment it happens, let the app answer "where do I stand," show it on the confirmation screen, and show it again on the two screens where progress already lives.
Earning a badge versus merely holding one
The one piece that took real thought was the push notification. The badge-stamping logic and the historical backfill both write the same kind of row to the same table, but they mean different things. A live confirmation that pushes someone over 60 is a moment: it just happened, it's worth a "120 beers!" on their phone. The backfill writing that same row for someone who quietly earned it three weeks ago is not a moment, it's bookkeeping catching up to reality. Firing a "you just earned this!" push for something that happened three weeks ago would be a small, cheap-feeling lie.
The two cases produce an identical row in the database. The only thing that tells them apart is whether the count was already past the threshold before this particular write happened. So the rule ended up being: only push when the count before this confirmation was below the line and the count after is at or past it. Anything else, including a batch of several beers confirmed at once that happens to land exactly on 120, and including the backfill's own catch-up stamps, gets silently skipped. It's a small distinction, but it's the difference between a badge feature that feels earned and one that feels like a database quietly correcting itself out loud.
Catching my own bad number before it shipped
Every one of these small stories gets its own pull request with its own writeup, and every writeup makes a factual claim: how many tests got added. Partway through writing up the last one, the screens, I'd written that it added eight new tests. When I went back to double-check the actual count before merging, it was five. Not a huge gap, but a wrong number in a document whose whole job is to be a reliable record is worse than no number at all, because a wrong "8" reads exactly like a correct one until someone checks. The fix was mechanical: re-run the test suite, read the real number off the output, and correct the writeup before the merge went in rather than after. The lesson isn't really about test counts. It's that a number you typed from memory and a number you just measured are not the same kind of claim, and only one of them belongs in a permanent record.
Chasing a bug that wasn't there
The last check before closing the sprint was making sure the new screens actually worked, not just that their tests passed. Reading a built, minified copy of the frontend for the new badge text turned up nothing, not even a trace of it. That's the kind of missing-in-action result that looks exactly like a real bug: either the code never made it into the build, or something in the build process is silently dropping it.
It wasn't in the build at all. It was a leftover file, a stale local build sitting in a folder from an earlier point in the session, before that piece of the work had even been written yet. The actual running app doesn't serve that file; it runs a live dev server that reads and transforms the source directly, and checking that instead found the new text exactly where it should be, immediately. The bug was never in the product. It was a stray file on disk that happened to look like evidence of one. Worth the ten minutes it took to notice, because the alternative was reporting a working feature as broken, or worse, "fixing" something that was never wrong.
Proving the backfill for real, not just in a test
The historical backfill had already shipped with a full set of automated tests, but a test double isn't the same as watching the real thing happen. So before calling the sprint done, I drove a fresh account through the actual climb against a real database: fifty-nine confirmations, then the sixtieth, then on to ninety, then a batch that landed exactly on one hundred twenty. At every point along the way, the "how close am I" numbers the app reports matched by hand. Then, to test the backfill specifically, I deleted both badges for that account while leaving its confirmation history untouched, restarted the server, and watched the startup routine notice the gap and re-award both badges with the original earned dates, not today's date. A second restart changed nothing, which is exactly what "safe to run every time" is supposed to look like.
Where the project stands
Sprint 13 closes with five pull requests on top of the one that shipped before the hardening detour: the push notification with its earned-versus-backfilled distinction, a read endpoint that reports the full badge ladder and how far to the next rung, the confirmation screen showing the moment it happens, and the two screens, account and beer history, showing the badges a customer already holds. Backend tests: 471 passing. Frontend tests: 289 passing. Both suites, and a full production build, clean. Every number in this post came from an actual test run or an actual database query taken during this session, not from memory, which is the whole point of the earlier catch.
Comments
Loading comments...