2026-07-21

Sprint 3 Closes: Search, Real Beer Data, and a Phone That Actually Works

Sprint 3 of the beer app closed today. Seven stories, all done: search-first browsing with availability filters, beer-nerd stats (ABV, IBU, style family) enriched from two outside data sources, and a mobile UX repair pass that made the phone experience actually trustworthy instead of just responsive. This sprint had more moving parts than the last one, and three habits carried it.

A spike that behaves like a spike

The plan called for a hit-rate spike before integrating Catalog.beer, a community beer database: search a sample of the tavern's real list against it, and only build the integration if most of the list actually resolved. It would have been easy to skip the measurement and just wire it up — the API looked solid in the docs. Instead I signed up for a free account, ran the tavern's eight seeded beers through the real API, and counted: six clean matches, one close call (a brewery's Hefeweizen filed under a slightly different name), one miss. That's a real go decision, backed by a real number, not an assumption dressed up as one.

The interesting part wasn't the number. It was that the spike's own hypothesis was wrong in an informative way. The plan predicted small, local breweries would be the misses. The actual misses were well-known European breweries — the dataset just skews American craft and macro. Writing that down changes what I'd trust this data source for next time, which is the entire point of running a spike instead of skipping to the build.

Two integrations, one resilience pattern

This sprint added the app's first two external API dependencies: Open Brewery DB for brewery details, and Catalog.beer for beer-level pre-fill. Both follow the same shape, deliberately: a typed HTTP client, a server-side cache so repeated searches don't hammer someone else's free-tier API, and a hard rule that any failure — a timeout, a 404, a malformed response — degrades to "no data" instead of breaking the page the customer is looking at. A brewery card that doesn't load is a minor cosmetic gap. A beer detail page that 500s because a third party's API hiccupped is a real bug.

The Catalog.beer key also needed to never touch git, since it's a real secret tied to a real account. It lives in an untracked .env file, read through configuration with an empty default committed to the repo, so the feature just quietly does nothing if the key isn't present rather than crashing. Small habit, but it's the difference between "safe by default" and "safe if nobody forgets a step."

Hiding a button is not the same as removing access

The mobile UX story turned up a gap that's easy to miss: the nav bar hid the "Add Beer" link from customers, but the route itself didn't check who was asking. A customer who typed the URL directly still saw a fully working-looking form, one that could only ever fail once it hit the server's own authorization check. The fix was one guard clause in the form component itself, matching a pattern already used elsewhere in the app — but finding it meant not trusting "the nav doesn't show it" as equivalent to "the customer can't get there." Those are different claims, and only one of them is actually enforced.

The same story fixed a second stale-state bug: signing in didn't update the nav until a manual page reload, because the header read the user's role once at first render and never again. The browser's built-in mechanism for "something changed in storage" only fires in other browser tabs, not the one that made the change — so the fix was a small custom event the app dispatches itself on login, logout, and registration. Neither bug was visible in a quick click-through. Both were real.

The PM angle

This sprint's scope was more heterogeneous than Sprint 2's — a search feature, two external integrations, and a UX cleanup sweep don't share much surface area. Scope-based sprints handled that fine: each story still shipped independently, tested, live-verified, and merged on its own, and the sprint closed when the bundle was done rather than when a calendar ran out. The spike story is worth calling out on its own merits, though — a spike with a real, non-obvious finding is worth more to a roadmap than a "yes it works" assumption, even when the answer turns out to be yes.

Where the project stands

Three sprints in, and the customer's phone is now the actual product surface it was designed to be: search-first, informative, honest about what it doesn't know yet, and no longer leaking a customer-facing form that only admins should see. Test suites stand at 131 backend and 99 frontend, all green, every story live-verified against the running stack — several against the real external APIs, not stand-ins. Next up: grooming Auth II, social sign-in and password reset, so getting an account doesn't require inventing a password on a barstool.

Comments

Loading comments...