2026-08-24

Demo-Prep QA: The Bugs That Only Show Up Once You Actually Click Through It

A demo to a potential customer is weeks out, and the instruction for this session was a real course correction: stop extending the multi-industry design system for now — "we will worry about the other business concepts for a later date" — and instead get the one app actually being shown, the original bar/tavern experience, into demonstrably working order. That meant a full run-through on the real Docker stack, as both a customer and an admin, looking for whatever breaks when a real person clicks through it rather than whatever a mocked test asserts.

Bugs a mock can't produce, because they need real data or a real render

Two of the four bugs found only exist once you're looking at a running app with real tenant data behind it, not a unit test with one fixture in front of it. The customer-facing My Stats page was quietly querying the entire, unscoped beer catalog for its style breakdown — meaning a customer at one tavern could see style categories belonging to every other tenant mixed into their own numbers, "Indica" and "Hip Hop" and "Medium Roast" showing up next to categories that actually belonged to their own business. A mocked single-tenant test has no way to notice that, because there's nothing else in the fixture for it to leak. The fix scoped the query to the customer's own tavern using the same tenant filter the admin dashboard's summary calculation already established elsewhere in the codebase — the pattern existed; this one query had just never been brought in line with it.

The second only shows up watching a live interaction happen in sequence: the bottom navigation's progress badge only refreshed when auth state changed, so confirming a beer updated the confirmation itself correctly but left the badge showing a stale count until a manual page reload. No unit test renders a confirm dialog and then watches a separate component for forty-five seconds to see if it goes stale — that's specifically a "does this feel right to a real person using it" bug. The fix mirrors a pattern already in place for auth changes: a new event fires on a successful confirmation, and the badge listens for it and refetches, so the numbers move in the same render as the confirmation itself rather than on the next page load.

Testing the destructive paths without touching the demo data

A second pass covered twelve flows the first one didn't reach — reaching the actual mug goal, batch confirmations, PIN lockout, admin staff-PIN resets, voiding a confirmation, account activation and role changes, the real external-catalog autocompletes. Getting to "reaches the mug goal" without burning real demo data meant a real choice: temporarily lower a live tavern's goal through the admin settings API, or seed a fresh throwaway account already sitting at 200 confirmed beers. The seeded account won, on purpose, so nothing about a real demo tenant's configuration had to change even temporarily — and every throwaway row it touched got cleaned up afterward, the account itself simply deactivated rather than deleted.

Ten of the twelve flows worked correctly outright. One "failure" wasn't actually one: password reset can't be completed end-to-end from a local session, but that's a security property doing exactly its job, not a gap — ASP.NET Identity's token provider never persists or logs the token anywhere a local test could retrieve it, which is the entire point. What could be verified locally — the generic success message that doesn't confirm or deny whether an email exists, and correct rejection of an invalid token — checked out fine.

A control that worked exactly as coded, and still needed to be deleted

The two flows that did turn up real bugs got handled two different ways. Marking a beer out of stock through the customer-facing PIN dialog worked, technically — but the beer then disappeared from search entirely, including from whoever might want to toggle it back, so "mark available again" was functionally unreachable through the same flow that had just removed it. That's not really a bug in the code; the code did what it was told. It's a bug in what the feature was allowed to be. The ruling on it was direct and worth keeping as a standing scope decision, not just a one-off fix: this isn't a beer-inventory system, and the customer's phone is not the place availability gets managed — that already lives correctly in the admin portal's own beer-management screen. The whole capability came out of the customer-facing flow rather than getting patched: the toggle UI, its state and handler, the API client call, and — since nothing else in the app called it — the backend endpoint and its request type, along with every test that existed only to cover code that no longer exists. Deleting a backend endpoint rather than leaving it sitting there unreferenced was an explicit choice made with the person who owns that scope decision, not an assumption made on their behalf.

An autocomplete that only breaks once it has enough real matches to overlap something

The Add/Edit Beer form's name-lookup autocomplete never dismissed itself on blur, on Escape, or on tabbing away — only a direct click on a suggestion closed it. That's easy to read as a minor annoyance until the suggestion list is actually tall enough to sit on top of the Save button, or until Tab, which should move focus to the next field, instead walks into the suggestion list itself. Reproducing it required real suggestions coming back from the live external catalog, not a short mocked list — and confirming it live, through actual browser automation rather than a description of the bug, showed the dropdown's own suggestion list was genuinely intercepting clicks meant for the fields underneath it, not just visually overlapping them. Five tests went in first, written to fail against the existing code before anything got changed: blur, Escape, Tab landing focus on the real next field, a regression check that clicking a suggestion still works, and the same check on the form's other autocomplete field. After the fix, all three dismissal paths closed the dropdown correctly, typed text survived an Escape press instead of getting cleared with it, and clicking an actual suggestion still worked exactly as before.

Grooming the next sprint against the code as it actually stands today

The same session groomed the next piece of work — an admin dashboard redesign — as the first slice of a stakeholder critique from two weeks earlier to actually become real, ticketed issues, rather than trusting that critique document to still describe the code accurately. It didn't, quite: a live re-check found a fifth anomaly detector that exists in the backend but was never wired into the frontend's own badge display, an addition the original critique had missed, plus a claim in that same document — about per-tenant admin accounts still being missing — that had actually been fixed weeks earlier and just never got marked as resolved. Re-verifying a backlog item against the running code before turning it into a ticket, rather than trusting the backlog's own account of itself, is what caught both.

The PM angle

Every real bug in this session falls into the same category this project has run into before and keeps re-learning the same lesson from: a cross-tenant data leak that needs more than one tenant's worth of real data to notice, a stale badge that needs a live sequence of actions to see, an autocomplete that needs a real, tall list of real suggestions to actually overlap anything. None of that is a gap in test coverage in the sense of "write more unit tests" — it's the standing reminder that a test suite verifies the code does what it was told, and clicking through the actual running app is still the only way to find out whether what it was told is what a real person actually needs.

The out-of-stock decision is the one I'd point to on judgment. It would have been easy to patch the reachability bug and call the flow fixed — make "mark available" reachable again from the same dialog, ship it, move on. Instead the question got pushed one level up: should this control exist on the customer's device at all. The answer changed the shape of the fix from "add a button" to "delete a capability," which is a better outcome specifically because a control that shouldn't exist doesn't need a more careful bug fix — it needs to not be there.

Where the project stands

Backend closed the session at 565 passing, frontend at 363, both fixes for the deferred bugs committed and live-verified against the actual Docker stack rather than just asserted green in CI. The bar/tavern app that's actually going to be demoed is in materially better shape than it was at the start of the session — a real data leak closed, a real stale-UI bug closed, a scope question about what belongs on a customer's phone settled and enforced in code, not just in a decision doc. Sprint 22 is groomed and ticketed, five issues deep, waiting on implementation. The multi-industry design system work that got paused for this session is still exactly where Phase 1a left it — paused, not abandoned, until the demo that prompted this detour has come and gone.

Comments

Loading comments...