Every figure arrived at 200 by 200 and nobody noticed for weeks
TLDR: One side of a cross tool handoff computed a width and height, used them, and forgot to put them in the payload. The other side had a fallback constant for missing dimensions. The result was a pipeline that delivered every image at 200 by 200 pixels, stretched to fit, with no error anywhere.
What happened
Our PDF extractor sends figures to the schema editor. Each figure travels as an item carrying a raster crop and, when the extractor managed to recover real geometry, a vector scene alongside it.
The vector case worked. A scene states its own width and height as part of its contract, so the receiver had the dimension handed to it.
The raster only case did not. And raster only is the common case, because most figures in a real document are pictures rather than recoverable line work.
The two halves
On the sending side, the resolver computed the crop dimensions from the region bounding box and used them to build the scene:
const w = Math.round(bbox.w), h = Math.round(bbox.h);
// ... w and h used to construct the scene ...
return {
kind: tag.kind,
name: p${page} · ${tag.value || tag.kind},
origin: _originOf(tag),
raster: raster || null,
scene,
};
The numbers were right there. They were correct. They were never put on the object.
Nothing caught it, because when a scene was present the scene carried the same numbers, so the payload was complete for the case anyone tested.
On the receiving side:
return window.GxScene.toSvg(
scene || { width: it.width, height: it.height, nodes: [] },
{ backdrop: it.raster || null });
With no scene, it.width is undefined. The renderer coerces a non number to a default of 200, and paints the backdrop image at 200 by 200 with preserveAspectRatio="none".
So a 1400 by 380 schematic became a 200 by 200 square. Not scaled down: squashed, because none means fill the box and forget the aspect ratio.
Why it survived review
Both halves are defensible in isolation, which is the signature of this class.
The sender omits a field. Omitting a field is not an error, it is what optional fields are for, and a reviewer reading that function sees a well formed object.
The receiver defaults a missing number. Defaulting is what you are told to do instead of crashing on bad input, and 200 is a perfectly sensible number for a renderer that has to draw something.
Neither side is wrong. The contract between them was never written down, so nobody was checking that "optional" on one side meant the same thing as "defaultable" on the other.
Why it stayed invisible
A stretched figure looks like a figure.
There is no red box, no console warning, no dropped item, no reduced count in the toast. The import reported success and the correct number of artboards appeared. The only symptom was that the artwork looked a bit wrong, and "a bit wrong" is what a low resolution crop of a PDF figure looks like anyway.
The bug was reported as behaviour, not as a defect: images "get thrown away from their original dimension when something changes". That is an accurate description of a document with no dimension of its own, arrived at without any of the above.
The fix, in three parts
State the dimension. The sender now ships width and height on every item. The numbers already existed; the change is five lines including the comment explaining why they are not optional.
Refuse rather than default. The receiver resolves the dimension from stated values, then the scene's extent, then the raster's own intrinsic pixel size by decoding it. If all three fail it declines to mount the item and says so, rather than drawing a square.
Stop distorting. The standalone raster path now uses xMidYMid meet. If a dimension is ever slightly off, the figure is letterboxed instead of squashed, which is a failure you can see.
The third fix matters least and would have been the tempting one to ship alone. It makes the symptom milder without touching the cause.
The invariant underneath
The deeper problem was that an imported figure had no page of its own, so its size was read off the editor viewport, which moves. That is fixed separately and generally: every document mounted into the editor now gets a page rectangle naming its own extent, created in one place that every import path passes through.
The dimension fix stops the wrong number arriving. The page fix stops the number being borrowed from the camera once it does.
What we would do differently
Write down what optional means. Our payload shape said width?: number. It did not say what a receiver is entitled to do when it is absent. Those are different questions and only one of them was answered.
Treat a default as a branch that needs a test. it.width || 200 is a conditional wearing a costume. If you would write a test for the if, write one for the ||.
Be suspicious of tests that only cover the rich case. We tested the handoff with vector geometry present because that is the interesting path. The dull path, a plain image with nothing clever about it, is the one most documents actually take.