Our analyzer skipped every real file and said so only to the console
TLDR: A defensive size limit was set from toy inputs and never re-checked against a real one. The real file was 7848 elements, the limit was 1200, and the analysis it was protecting takes 380 milliseconds. For months the feature reported success and did nothing.
The report
A user imported an Arduino MEGA2560 schematic, a real board file exported from EAGLE, and told us the editor recognised no components, no wires, no nets. For a tool whose stated job is electrical schematic analysis, that is the whole product missing.
They also said it was slow and that wires were nearly impossible to select.
What we found first
The drawing rendered perfectly. Every trace, every symbol, in the right colours. The failure was entirely in the analysis.
Fifteen lines into the pipeline:
const elementCount = svgEl.querySelectorAll('*').length;
if (elementCount > 1200) {
console.warn([GeoEngine] Skipped pipeline: ${elementCount} elements exceeds safe limit.);
const d = this.displays[this.activeDisplayIdx];
if (d) { d.analyzed = true; d.svgContent = ... }
return;
}
The file had 7848 elements. The pipeline never ran.
Note the fourth line. It sets analyzed = true on the way out. The display is marked as having been analysed by the branch that declines to analyse it, so nothing downstream ever asks again.
Two failures, and the second is worse
The threshold was wrong. We measured the pipeline it was protecting by running it on the file it refused. All four phases, on 7848 elements: 380 milliseconds. The guard was trading the entire feature against a third of a second.
That number was never taken. The limit came from an era of hand-drawn test diagrams where 1200 felt generous, and no one revisited it when the tool started accepting PDFs from CAD software. Every real engineering drawing clears 1200 on import. The guard did not fire on the exceptional case. It fired on the only case that mattered.
The failure was invisible. console.warn and a silent return. No toast, no badge, no empty-state text. The user saw a drawing appear, saw no components in the panel, and reasonably concluded the analyzer could not understand their file. The truth, that it had declined to look, was in a console they had no reason to open.
A wrong threshold is a tuning bug and takes ten seconds to fix. A silent bail is a design bug: it converts every future threshold mistake into a mystery.
Why the number looked reasonable
Because the document is mostly not a document.
Of 7848 elements, 6280 were a single contiguous run of filled hairline rectangles: the Creative Commons and Arduino logos in the title block, placed as bitmaps and exported by scanline tracing, one rectangle per scan row. Four fifths of the file was two pieces of decorative artwork.
So the guard was being tripped by a logo, and refusing to analyse a circuit because of it. Counting decoration toward a complexity budget is its own bug, and it was hiding inside the first one.
The fix
Do not count decoration. Runs of consecutive filled hairline rects sharing a fill colour are traced artwork, detected on the PDF operator stream where "consecutive" is still knowable. They get marked as decoration and excluded from the budget and the analysis.
Raise the limit to something measured. 20000, against a pipeline that does 7848 in 380ms.
Say it out loud. If the limit is ever hit, a toast names the count and the limit and states plainly that wires and components were not detected.
After that, on the same file: 1272 wires, 193 components, 303 junctions, 888 milliseconds from file picker to analysed.
What we would do differently
Test the guard, not just the happy path. We had tests for the pipeline. We had none asserting that a representative real file actually reaches it. A guard is a branch, and an untested branch that returns early is the most dangerous kind.
Make every bail visible. Any code path that silently produces an empty result needs a user-facing explanation. If it is not worth telling the user about, it is not worth doing silently either.
Re-measure inherited limits. 1200 had no comment saying where it came from or what it cost. Every magic threshold should carry the measurement that produced it, because the thing it was protecting against gets faster and the inputs get bigger, and nobody notices when the two cross.
Suspect the input before the algorithm. We nearly went looking for a classifier bug. The classifier was fine. Four fifths of the input was a picture of a logo.