BE Teck Notes Back to be-teck.com

4 min read

The complaint names the wrong thing

One link was reported broken twice in a single day. Nothing was ever wrong with that link. Two unrelated faults were eating clicks and looked identical.

The report was: the settings link does not work.

It was a completely accurate observation and a completely wrong diagnosis, and it was made twice in a single day, about two faults that had nothing to do with each other. Nothing was ever wrong with that link.

The first one: a sheet nobody could see#

A greeting panel mounted the moment it had something to show, while it was still marked as not yet visible. In that state it painted at zero opacity, covered the entire window, sat above everything else on the page, and still accepted every click.

It was meant to be switched to visible by an animation frame. Animation frames do not fire while a browser tab is in the background. This is an application people leave open in a background tab for hours at a stretch, so for those people the frame simply never came, and an invisible sheet stayed spread across the whole window taking every tap.

The page looked entirely normal. Nothing on it responded, anywhere, until it was reloaded. Settings looked like the culprit because the profile icon happens to be the first thing most people try.

Two fixes, and the order they went in matters more than either of them.

First, the general one: an overlay that cannot be seen may not take a click. That is now true everywhere in the application, regardless of what else goes wrong around it, and regardless of why some future overlay ends up invisible.

Only second, the specific one: the greeting no longer waits on a frame that is never coming.

Doing the specific fix alone would have made the symptom disappear and left the shape exactly where it was. The next transparent overlay would be written in a different file, later, by somebody who had never heard this story, and it would produce exactly the same confusion.

The second one: two handlers, and the blind one won#

Later the same day, the same words from the same person. This time it really was the menus.

Menus in this application are rendered into the end of the document rather than in place, so that the header cannot clip them. The component that owns them closes on an outside click, correctly, by checking whether the click landed inside the panel it actually painted.

But four of those menus had also kept an older handler from before they were moved, one that checked whether the click landed inside the component's own wrapper. Once the menu was being rendered elsewhere, its items were no longer inside that wrapper. So a click on a menu item looked to the old handler like a click on the outside world, and it closed the menu on the press, a beat before the click could reach the link underneath.

Two handlers watching for the same event. One of them blind to where the menu now lived. The blind one won the race.

The fix was deletion. The four components stopped keeping their own handler, and the one place that logic belongs does it for all of them.

Why this is worth writing down#

A symptom is a location, not a cause. It tells you where somebody was standing when the work stopped, which is genuinely useful, and it tells you almost nothing about why.

More usefully: the same symptom can have several different causes queued up behind each other. Fix the first, ask "is it working now?", and the honest answer from the person now standing in front of the second one is no. This reads, to everybody involved, as the fix having failed. It is a bad moment. It is also the ordinary state of affairs in any system old enough to have been changed by more than one person, and expecting it makes it much less alarming.

The useful discipline is to separate what was observed from what was concluded, even when the person reporting it is certain, and especially when they are right about the observation. Two things were true at once here: the link was not working, and nothing was wrong with the link.

The check that was wrong about itself#

One more piece, because it is the part we would most like to have skipped.

The automated check written to stop the invisible overlay coming back was wrong on its first attempt. The long comment explaining the fix had been placed between the two things the check needed to compare, which pushed one of them outside the window it was looking at. So the check passed. It passed with the bug sitting directly in front of it, and it would have gone on passing for as long as anyone cared to trust it.

That was only found because the bug was deliberately put back, to watch the check go red. It did not. The check now strips comments before it looks, and it was re-tested the same way.

A test you have never seen fail is a test you are trusting on its own word. It takes about a minute to break the thing on purpose and confirm that something notices. Almost nobody does it, which is why so many green suites are green for reasons unrelated to the code being correct.

The shape#

The people reporting problems are describing where they were standing. That is their job, and it is a real contribution. Turning that into a cause is ours, and the work is mostly resisting the first plausible explanation for long enough to check whether the thing named in the complaint was ever involved at all.

The check that passed with the bug in front of it has a longer treatment in a test that describes the code will defend a bug, and the habit of explaining a refusal rather than simply making one is in the app should tell you why.

Have a gap worth closing?

If something in your daily work is broken in a way everybody has stopped complaining about, that is exactly what we want to hear.

Write to hello@be-teck.com

More notes

  • 4 min read

    Written, and never wired

    The code to undo a wrong stock entry already existed and had no callers. Nobody had asked for the button, because a workaround was already doing the job.

  • 4 min read

    Filed as chatter

    A document was waiting on one person's signature, and the request reached them as a line in a digest. Not a training gap. A row in a routing table.