Page Assembly Errorfix
Error Fix: ReferenceError from a Closure That Was Never a Closure
TLDR: _detectAutoZones referenced pageWidth from the outer assemblePage function. Because _detectAutoZones is a module-level function and not a nested function, pageWidth was never in its scope. The error fires on every PDF load, the stacktrace points at the worker message handler instead of the actual line, and the fix is adding one parameter.
Symptom
Error loading PDF 1: Error: pageWidth is not defined
at worker.onmessage (fileUpload.js:83:24)
The error appears in the file upload handler, not in pageAssembler.js or _detectAutoZones. The worker catches unhandled errors and re-emits them as { type: 'error', error: err.message }. The message handler in fileUpload.js surfaces that string. The actual throw site is invisible from the stacktrace.
Every PDF load fails at the same point: after regions are classified, before any HTML is emitted.
Root Cause
assemblePage() defines pageWidth at line 261:
const pageWidth = viewport.width || 612;
_detectAutoZones() is declared as a module-level function:
function _detectAutoZones(regions, numCols) {
// ...
const col0 = g.list.filter(r => r.columnIndex === 0 ||
(r.colIdx === undefined && r.bbox?.x < pageWidth/2)); // THROWS
Because _detectAutoZones is not nested inside assemblePage, it does not close over assemblePage's local variables. pageWidth at this call site is a free variable with no binding -- ReferenceError.
The architecture spec described the FEATURE_LAYOUT heuristic as needing the page midpoint without specifying that this meant passing the value through the function signature. The implementation reached for the variable by name without checking whether it was in scope.
The Fix
Add pageWidth as a third parameter to _detectAutoZones:
// Before
function _detectAutoZones(regions, numCols) {
// After function _detectAutoZones(regions, numCols, pageWidth) {
Update the call site in assemblePage:
// Before
const autoZones = _detectAutoZones(regions, numCols);
// After const autoZones = _detectAutoZones(regions, numCols, pageWidth);
No other changes needed. The value flows through from viewport.width at the call site.
Why the Stacktrace Hides the Real Location
geometryWorker.js runs in a Web Worker. The worker's top-level error handler is:
} catch (err) {
self.postMessage({ type: 'error', error: err.message || String(err) });
}
err.message is "pageWidth is not defined". The stack is not forwarded -- only the message string. fileUpload.js receives { type: 'error', error: '...' } and throws new Error(error). The throw site in fileUpload.js is what the DevTools stacktrace shows.
The real throw site -- _detectAutoZones inside pageAssembler.js -- appears nowhere in the visible stack. To find it: grep the codebase for the exact identifier named in the error message (pageWidth), then check each reference site to verify it is in scope.
Guard
Any module-level function that the spec describes as using a page-level value must receive that value as a parameter. Free variable references inside module-level functions are always bugs -- there is no outer scope to close over.
In this project: assemblePage receives all page context (viewport, pageWidthPt, etc.). Any helper it calls must receive the specific values it needs via its parameter list. This is already the pattern for _renderRegion, _buildList, and _buildStandaloneList. _detectAutoZones was the one exception.
ESLint's no-undef rule would have caught this at lint time. If the project gains a lint step, add it.