Engineering Journal
Pdf Processor
Pdf Processor

Path Reconciler Errorfix

2026-05-30

Error Fix: Two Silent Bugs in the Path Reconciler Refactor

TLDR: After refactoring ctmAdapter.js to export extractSubpaths instead of extractPaths, one file was missed and one case block was missing braces. Both errors are silent in different ways -- one is a module resolution error that only fires at import time, the other is a JavaScript scoping trap that may or may not throw depending on execution path.


Bug 1: Missed File in Refactor

Symptom

Uncaught SyntaxError: The requested module
'/tools/pdf-processor/src/extraction/vector/ctmAdapter.js'
does not provide an export named 'extractPaths' (at pdfAnalyzer.js:17:10)

The error appears the first time the analyzer runs, in the browser console, with no indication of which file contains the stale import.

Root Cause

ctmAdapter.js was refactored: extractPaths renamed to extractSubpaths, return shape changed from { segments, imageMeta, filledRects } to { subpaths, imageMeta, filledRects }. Three files needed updating: geometryWorker.js, pdfAnalyzer.js, and anything else importing from ctmAdapter.

geometryWorker.js was updated. pdfAnalyzer.js was missed.

The Fix

// pdfAnalyzer.js — before
import { extractPaths } from './ctmAdapter.js';
// ...
const segments = extractPaths(opList, vp, OPS);

// pdfAnalyzer.js — after import { extractSubpaths } from './ctmAdapter.js'; import { reconcile } from './pathReconciler.js'; // ... const { subpaths, filledRects: rawFilledRects } = extractSubpaths(opList, vp, OPS); const { segments } = reconcile(subpaths, rawFilledRects, vp);

pdfAnalyzer.js also needed to import reconcile from the new pathReconciler.js because the analysis pipeline uses segments for geometry counts -- it is not enough to just rename the import.

Guard

When renaming an export, search all files importing from that module before implementing. In this project: grep -r "extractPaths" tools/pdf-processor/src. Module resolution errors only fire at load time, not at the line that uses the value -- so the stacktrace points at the import statement, not the call site.


Bug 2: Lexical Declaration in Unbraced Case Clause

Symptom

Potential silent behavior divergence or syntax error depending on JS engine and strict mode. The specific pattern:

// ctmAdapter.js — before
case OPS.closePath:
    bufferSeg(rawPendingX, rawPendingY, subpathStartX, subpathStartY);
    rawPendingX = subpathStartX; rawPendingY = subpathStartY;
    const [x, y] = toViewport(subpathStartX, subpathStartY);  // PROBLEM
    pendingX = x; pendingY = y;
    currentSubpath.closed = true;
    break;

const inside an unbraced case clause shares the lexical scope of the entire switch block. Other cases in the same switch also declare const [x, y] inside braced blocks. This creates a scoping conflict: the braced const [x, y] declarations are block-scoped to their own case bodies, but the unbraced one is switch-scoped and can shadow or collide with them depending on execution order.

Root Cause

The refactor added a const destructuring to an existing case that had been written without braces (acceptable when no variable declarations are needed). The new const made the braces necessary.

The Fix

// ctmAdapter.js — after
case OPS.closePath: {
    bufferSeg(rawPendingX, rawPendingY, subpathStartX, subpathStartY);
    rawPendingX = subpathStartX; rawPendingY = subpathStartY;
    const [cpx, cpy] = toViewport(subpathStartX, subpathStartY);
    pendingX = cpx; pendingY = cpy;
    currentSubpath.closed = true;
    break;
}

Two changes: add braces around the case body, rename [x, y] to [cpx, cpy] to avoid any appearance of shadowing the [x, y] names used in other cases above.

Guard

Any time a const or let is added to a case clause, add braces if they are not already there. ESLint's no-case-declarations rule catches this automatically -- if it is not in the lint config, add it.


Lesson

Both bugs would have been caught by a TypeScript build step. The first would be a type error at compile time (unknown export). The second would be a scoping error. The pdf-processor is a vanilla JS Vite project with no TS checking -- in that context, the only reliable catch is a search-before-rename discipline and consistent bracing of case clauses.

Read this post in the full Engineering Journal →