Every PDF load crashed with the same ReferenceError. The stack trace pointed nowhere useful, and the specification that guided the code looked perfectly reasonable on paper. It said to check a feature flag, and to compare each region against half of pageWidth. It described what should happen. It failed to describe where pageWidth was supposed to come from, and that single omission was enough to bring the whole pipeline down.
Architecture documents are good at explaining behavior. They are often terrible at explaining boundaries. A sentence that says "the function checks x against pageWidth" is not a technical contract; it is a narrative that hides a dependency inside plain English. When a developer reads that sentence and writes a module-level function that references pageWidth by name, the code looks correct because it satisfies the description. Then the runtime tries to resolve the name, finds nothing in scope, and throws.
The Refactor That Should Have Been Simple
I saw this exact pattern during a page assembly refactor. The specification listed two apparently clean requirements:
- Check
FEATURE_LAYOUT. - Compare each region against
pageWidth / 2.
The developer followed the instructions faithfully. They extracted a utility function at module scope and dropped pageWidth directly into the function body without qualifying it as a parameter. The spec had not stated that pageWidth must arrive through an argument list. It had not stated that the function lived at module scope, where pageWidth was no longer visible. It simply assumed the implementer understood the execution context implicitly.
The result was a ReferenceError on every PDF load. Because the variable was absent from the module scope, the function threw immediately. Had the spec explicitly named the function boundary and its inputs, the developer would have passed pageWidth in, and the bug would have been structurally impossible. Instead, the instruction acted like a trap, inviting the implementer to reach out into a parent scope that did not exist.
The Four Meanings of "Uses"
The deeper issue is that prose lacks a type system. When a specification says "the function uses X," the sentence is ambiguous in at least four specific ways in a modern JavaScript codebase:
- The function receives X as a formal parameter.
- The function reads X from a module-level variable declared in the same file.
- The function closes over X from a nested parent scope.
- The function extracts X from a larger object passed into it.
Every one of these options satisfies the wording of the spec. Every one of them passes static analysis. But only one of them is correct for a given boundary, and the wrong choice leaks assumptions across that boundary in a way that compiles silently.
Developers usually choose the path of least resistance at the moment of writing. If pageWidth happens to be sitting in an outer scope, they will read it from there rather than alter a function signature. A closure hides the dependency. The code works on the first run, passes the suite, and ships. Weeks later, someone moves that same function to a different file for reuse, or to improve readability. The parent scope vanishes. The code breaks, and the breakage looks like a new regression even though the root cause was the original hidden dependency.
Web Workers Erase the Evidence
This problem becomes genuinely vicious once Web Workers enter the architecture. When an error occurs inside a worker, the browser strips away the information you need most.
Here is what actually happens. Inside a worker, an uncaught exception fires an ErrorEvent. If the worker forwards that error to the main thread, the typical pattern is to grab the message string and post it across the boundary. The main thread receives that string, constructs a fresh Error object from it, and logs or re-throws it. What appears in DevTools is the reconstructed error inside the main thread’s message handler. The original filename, line number, and stack trace are discarded. The real location of the failure becomes invisible.
So when the missing pageWidth triggered a ReferenceError inside the worker, the main thread reported only the text "pageWidth is not defined" at the site where the message was handled. The actual module-level function sat in
