Verify the Product, Not the Code
For anyone who ships a change and calls it done: green tests, a clean build, and a passing Storybook are not acceptance. This page is the kernel underneath that, the diagnosis, the policy, and the four gates it produces.
What changes, at a glance:
- A green pipeline proves the code is consistent with itself. It does not prove the product works.
- Acceptance means clearing four gates the automated checks cannot see: real data, every surface, the user’s job, the real page at real widths.
- Done is “a skeptical user looking at the real thing has nothing to report,” not “CI is green.”
Green CI is not acceptance#
Tests, the build, and Storybook all check one thing: is the code internally consistent? They cannot see the real payload, the other screens that render the same thing, or whether the person can actually do their job. That blind spot ships defects that are invisible to every check and obvious the instant a human looks at the real page.
Here is what that costs, from an invented but entirely typical case. A finance team gets a new overdue-invoice review screen. It passed unit tests, the build, and Storybook. The reviewer found four defects in the first two minutes of real use, and each was a separate bug report and fix cycle after ship:
- The “Open in billing system” link went to a junk page. It was built against an assumed webhook shape, and the code even asserted the reference id “is always empty.” A real event proved it is not, and the useful id was sitting right there in the payload.
- The invoice rows were clickable in the list and the detail, but not in the activity feed that renders the same rows. The click-through was added to some surfaces, not all.
- Rows read “AUD 0.00 overdue, 41 days” with no customer name. They were wired to a field mapping, not to what a reviewer needs to triage, which is the customer.
- The table columns were wrong and the page did not fill its width, because it was only ever seen in Storybook, whose canvas is wider than the real embedded column.
Every one passed CI. None survived a skeptical human on the real page. That is the gap this page exists to close.
Done is a skeptical user with nothing to report#
Acceptance is a skeptical user looking at the real thing and finding nothing to report. Not a green pipeline, not a passing story, not “it worked when I clicked through it.” The checks are necessary and stay; they are just not sufficient, and treating them as the finish line is what ships the defects above.
This policy rules out four habits, all of which feel like diligence:
- Calling something done because CI is green. Green is the start of acceptance, not the end.
- Verifying against fixtures. Three invented rows prove the code renders, not that it works on the real payload.
- Building to a field mapping. Wiring the fields the spec listed is not the same as letting the person do their job.
- Checking only in Storybook. A component in isolation cannot show a composition, surface, or width bug, which is where the reported problems live.
If your verification did none of the four things below, you have checked the code, not the product.
The four gates the checks can’t see#
Everyone ships a fixture-verified change now and then; the checks are green and the pull to move on is strong. These four gates are what a skeptical user would do, done by you first.
Real data, not fixtures#
Fetch one real record from the actual system and build and verify against it. For any integration, a webhook, an API, a model, never assert how the external system behaves from a guess. Probe it and see the real payload. A confident code comment about a system you did not hit is the most expensive kind of documentation, because the next person trusts it. The junk link above came straight from an assumption written down as fact.
Every surface, not just the one you built#
List every place the entity renders: list, detail, dashboard, activity feed, notification. Then confirm your change, a new field, a click-through, a badge, lands on all of them. A feature applied to three of four surfaces is a bug report waiting, and it is the easiest gate to skip because the surface you were working in looks finished.
The user’s actual job, not the field mapping#
Ask what the person does with this screen, then confirm the rendered result lets them. A reviewer triaging overdue invoices needs the customer, not just the amount and the age. Build to the job, not the field list. This is where product sense meets verification: the same “read the real need, not the literal ask” instinct, applied to your own output before you ship it.
The real page, at real widths#
Load the actual route, not the story, with real data, at 390, 768, and 1280, and read it. Empty gutters, a narrow column, a squeezed table are only visible in the real embedded context, because a story’s canvas is not the page’s. Measure with getBoundingClientRect() on the real page when a width or alignment is in question, do not eyeball it.
How to tell you’ve cleared it#
Before you call anything visual or integrated done, answer four questions with evidence, not from memory:
- Did I build against a real record, and did I probe every external system rather than assume it?
- Does the change land on every surface the entity appears on?
- Can the person actually do their job on this screen, not just see the fields?
- Have I loaded the real route, with real data, at 390 / 768 / 1280, and read it?
If all four are yes, a skeptical user has nothing left to find. If any is no, that is the defect, waiting to be reported back to you.
Related: Product Sense, Prove It Works, Growing Your Product Sense, Using AI