2026-07-23

Sprint 7 Closes: Letting Customers Search Outside the List, and Learning From What They Ask For

Sprint 7 of the beer app closed today. Where Sprint 6 was six small UX fixes born from watching someone actually use the app, this one is smaller in issue count — three stories — but it's the first sprint that points the product outward, past the tavern's own ~200-beer list, into every beer that isn't on it yet.

Reusing Sprint 3's integrations instead of rebuilding them

Sprint 3 built two external lookups — Open Brewery DB for brewery info, Catalog.beer for beer-level pre-fill — but wired them up as admin-only tools for filling out the beer form faster. The services underneath, IBreweryLookupService and ICatalogBeerService, already did the actual work: query an external API, cache the result, degrade to an empty result on any failure rather than throwing. Sprint 7's customer-facing "look up any beer" search is a new controller in front of the exact same two services, not a new integration. That's the payoff of building the admin version with a clean interface in the first place — the customer-facing version didn't need to touch a single line of the lookup logic itself, just add a route, an authorization check, and a rate limit that the admin-only version never needed because only trusted accounts could hit it.

Why a search feature needed its own rate limit and its own log table

Letting any signed-in customer trigger a live call to an external API on every keystroke is a different risk profile than an admin doing the same thing occasionally while filling out a form. This is the first place in the codebase that needed real per-user rate limiting, so it's the first place ASP.NET Core's built-in RateLimiter shows up — a fixed window, twenty requests a minute, partitioned by the customer's own user ID rather than by IP, so one customer hammering the search box can't affect anyone else's ability to use it. Every search also gets logged — query text, which customer, whether it matched something already in the tavern's own catalog — not for enforcement, but because a customer typing a beer name that isn't on the list is exactly the signal a real product manager would want to see in aggregate later. That log table sat unused by any UI for most of this sprint; it only pays off in the third story.

A recommendation is not a request queue

The one-device rule from the Customer Phone Experience planning work killed an earlier "I'm drinking this" request-queue concept, and it would have been easy to accidentally re-introduce something request-queue-shaped here under a different name. The actual feature stays much smaller: a customer can submit a beer name (optionally a brewery, a note, and a link back to whichever external search result they were looking at) with nothing else required, and an admin sees a filterable list they can mark Reviewed, Added, or Declined. No reason is required on those status changes, unlike voiding a confirmation — a declined beer recommendation doesn't carry the same audit weight as erasing a customer's drink history, so it follows the lighter no-reason pattern the availability toggle already established back in Sprint 5 rather than borrowing the heavier one from a different feature just because both are "admin marks something in a table."

Turning search logs into a report instead of a UI feature

The third story is the one that makes the search log table worth having built: an admin-only endpoint that aggregates every logged search that didn't match anything in the tavern's own catalog, grouped by normalized query text, ordered by how often it comes up. That's a direct answer to "what should we actually add to the list next" — grounded in what real customers typed while standing at the bar, not a guess. It follows the same shape as the anomaly detection and dashboard summary work from Sprint 5: a public static method that takes an explicit now parameter instead of reading the clock internally, so a test can assert on a specific window of time without needing to wait for it or fake the system clock.

The PM angle

This sprint's real dependency chain wasn't technical difficulty, it was information flow: the search feature had to exist and log data before the demand report had anything to aggregate, and the recommendation feature needed the search feature's results to prefill from before it was worth building the "prefill from a search hit" convenience at all. Building all three in the one grooming-to-build pass — rather than as three independently useful features — kept that ordering visible instead of discovering it as a blocker mid-sprint. It's also the first sprint where the interesting engineering decision was almost entirely about reuse discipline: recognizing that two admin-only services from four sprints ago were already the right shape for a customer-facing feature, and that the temptation to add a reason-guard here or a request-queue there was worth naming and rejecting explicitly rather than drifting into by pattern-matching against the nearest existing screen.

Where the project stands

Seven sprints built now, six fully closed and this one open as a pull request pending review and merge. Suites stand at 271 backend and 175 frontend, both green, plus a live pass against the Docker stack: real Catalog.beer and Open Brewery DB results for an actual query, a recommendation submitted and triaged through to Added, the demand report surfacing an unmatched search, and the rate limit correctly tripping at the twentieth request in a minute. Sprint 8 — a bundle of smaller admin and engagement UX follow-ups — is already groomed and ready to build next, once this one merges.

Comments

Loading comments...