- NO.
- 011
- DATE
- Updated 2026-07-20
- READ
- ~6 min
- KIND
- Case study
- STATUS
- Reviewed
One Screenshot, a Cascade of Fixes
One iPhone screenshot, one afternoon, a dozen commits: desktop TOC, mobile pill, a reversed test verdict, footer rebuild. A fix counts when behavior changes.
One iPhone screenshot, one afternoon. Two things looked wrong in it: the table-of-contents pill at the bottom of an article was sitting on top of the footer, and the footer's own information hierarchy was a mess. Following those two symptoms ended in a dozen commits — desktop TOC, mobile interaction, the test suite, the footer, one after another.
This is not a tutorial. It is a debugging record, and what it is really about is one discipline: a fix is accepted when it drives a change in real behavior, not when the code looks right.
Three desktop defects that were not in the screenshot
Start with the odd part. The screenshot came from a phone, and I fixed the desktop first — because one earlier commit claimed to have "properly fixed" three desktop TOC problems, so I re-checked them on a wide screen and found two of them had never taken effect.
The desktop TOC is a sticky side rail. It should stop below the header while scrolling and scroll internally when it gets too long. Its scroll chain broke on a wrapper element: min-height: auto on a flex container prevents children from shrinking, so internal scrolling never triggers. The commit that claimed the fix changed the TOC component itself and never touched that wrapper — so the "fix" existed in the code and not on the page.
The lesson appears for the first time here: claiming a fix is not verifying a fix. A commit saying "three problems properly fixed" is not evidence that three problems are actually fixed.
The mobile pill: from edge trigger to state machine
Now the actual subject of the screenshot. While reading on mobile, a table-of-contents pill sits at the bottom of the screen and should get out of the way when you reach the end of the article (comments and footer). The original implementation used IntersectionObserver as an edge trigger — hide the pill at the instant the element enters the viewport. The problem is that an edge trigger fires once: keep scrolling up and down a little inside the end zone and that "entered" event never comes again, so the pill's state gets stuck — and it sits on the footer.
The fix was to answer a different question. The original asked "at which instant should the pill hide," and tied the answer to a one-off event. The new one asks "is the reader in the end zone right now," and the answer is a state that keeps updating as you scroll.
Concretely: maintain a boolean inEndZone (is the reader currently inside the comments/footer region), and the pill's display rule reduces to one line — visible when !inEndZone, hidden when inEndZone. The observer no longer performs an action; it only reports state, reassigning that boolean whenever the end zone's position relative to the viewport changes:
- End zone enters the viewport →
inEndZone = true, pill hides. - End zone leaves through the top (the reader kept scrolling down and is now inside the footer below it) → still
true, stays hidden. - End zone leaves through the bottom (the reader scrolled back up into the body) →
false, pill returns.
The contrast is the point. In the old implementation, "hide" happened once, at the instant of entering the end zone; scrolling back and forth near the end never replays that instant, so the pill stays in the wrong state. In the new one, ask "what is inEndZone right now" at any moment and the answer matches where the reader actually is. Behavior moved from "perform an action at one instant" to "the state is self-consistent at every instant" — which is the general principle for continuous interactions like scroll and visibility: describe the world with state, do not drive actions with events.
Five "environment problems" were real bugs
With those two changed, five tests in the Playwright suite were red. An earlier audit had recorded them as environment problems, on the grounds that "they fail on main too, consistent with the baseline."
That phrase is dangerous. It kept an inaccurate verdict alive for two days. The screenshot reversed it: those five failures had two layers of cause. One was assertions frozen on old behavior — the "proper fix" commit had changed the interaction without updating the matching assertions, so they were verifying behavior that no longer existed. The other was a genuine end-zone bug in the application, namely the pill above. Stacked, the two look like flaky infrastructure. They were two real problems.
With both layers fixed, the suite went from an honest 4/9 back to 12/12 green. The discipline here: "red in the same way as the baseline" is not evidence of "no problem." It may only mean the baseline was already sick. Green is what restores the gate.
Footer rebuild
Only then came the screenshot's original symptom: the footer's information hierarchy. The old footer piled navigation, copyright, licence, and stats together with no semantic grouping. It became three semantic blocks — source-first navigation, one line for copyright/licence/privacy, one colophon line — stacking vertically on mobile in source-first order, with a reserved clearance band at the bottom of narrow screens so the fixed back-to-top button no longer parks on top of any text.
What this is actually about
A dozen commits, starting from one screenshot. What connects them is not a particular piece of CSS or an observer, but two disciplines:
-
Verification-driven fixes. The two desktop items that were "changed but never took effect," and the five tests that were "red but recorded as environment problems," are two presentations of the same illness — both treated "the code looks right" as "the problem is solved." The acceptance criterion has to be real behavior: the TOC actually scrolls, the pill actually gets out of the way, the tests turn green because behavior is correct.
-
Honest records, including reversals. If "consistent with baseline, judged an environment problem" had been left standing, everyone who picked this up afterwards would have worked from a wrong baseline. When reversing it I did not delete the original note; I added a dated correction — because "we judged this wrong and later corrected it" is exactly the thing this blog should be keeping.
How large a cascade one screenshot triggers depends on whether you are willing to follow the first symptom all the way down, and on whether you will write it down when the trail ends at "we were wrong earlier."
Comments
Comments are powered by GitHub Discussions. Sign in with GitHub to comment. Open the matching Discussion