INC-15product

We gave a chart a fixed height, and its volume panes were added on top of it

In the chart library, spec.size.height on charts with stacked panes (candlestick volume and extra panes, and the stacked dual chart) set only the main pane's height. The panes underneath were added on

Verbatim symptom

Before 1.3.6 it was the main pane's height and the panes under it were added on top

# The chart that was taller than its box

A figure that overflows its container is a bug you can spot in about two seconds. Working out why it overflows took a lot longer than that, which is the whole reason I'm writing this down.

Symptom

The report was simple. A host application gave the plot a box, subtracted the surrounding chrome (header, footer, the usual), passed what was left as spec.size.height, and got back a figure that was taller than the box it had been handed. Not by a lot, necessarily. Just enough that the footer got pushed or covered, which is the sort of thing that makes a dashboard look broken even when every number on it is right.

It only happened on charts with stacked panes. Candlestick charts with a volume pane or extra panes underneath, and the stacked dual chart. A plain single-pane chart behaved perfectly, which is partly why it sat there for as long as it did.

The docs even described the behaviour, if you read them closely. Before 1.3.6 it was the main pane's height and the panes under it were added on top. That's the exact wording of the observed behaviour: the requested height went to the main pane, and everything else was stacked on top of that number, not inside it.

What we thought it was

First guess: the host was doing its arithmetic wrong. Honestly, that's a very believable diagnosis. Hosts subtract chrome all the time, and chrome is the kind of thing that changes (a header gets a second row, a footer grows a legend) without anyone telling the code that does the subtracting. If a figure comes out too tall, "you gave it the wrong number" is the obvious first suspect.

So I went looking at the host side. Measured the chrome, checked the subtraction, checked it again. The numbers added up. The host was handing over exactly the box minus the chrome, which is what the option name suggests it should do.

Second guess was a rounding problem. Sub-pixel heights can drift when you divide a box across several panes, and a drift of a pixel or two can look like an overflow if you squint. That one was believable too, but it didn't survive contact with the evidence. The overflow tracked the number of panes under the main one, not the box size. Add a pane, get more overflow. Rounding doesn't care how many panes you have. Something that scales with pane count is adding, not drifting.

That was the moment the diagnosis flipped. The host wasn't wrong. The option meant something narrower than its name.

Root cause

The culprit lives in the layout code at src/index.ts:220@eca309e^, the version just before the fix. There, spec.size.height was applied to the main pane and nothing else. The panes beneath it, volume and the extra panes plus the second chart in the stacked dual layout, were sized separately and then added on top of that main height, along with the gaps between them.

So the total figure height was the main pane, plus every pane under it, plus every gap. The requested height only ever described the first term of that sum.

You can see how this happens without anyone being careless. For a single-pane chart, "the main pane's height" and "the chart's height" are the same number, so the option name is accurate. The moment you add a pane underneath, the two quantities split apart, and the option quietly keeps meaning the old one. The behaviour was documented in docs/API.md:367@eca309e, but a doc line describing a surprising rule doesn't make the rule less surprising. It just makes it findable after you've already been bitten.

The host's reading was the natural one. The library's reading was the literal one. Both were defensible, and they disagreed by exactly the height of everything under the main pane.

The fix

The fix, in commit eca309e, changes the contract so the number means what a host expects it to mean. The layout now runs again until the main pane, every pane under it and the gaps between them add up to the requested height, within rounding.

That "again" matters. You can't just shrink the main pane by a fixed amount, because the other panes and the gaps have their own sizing rules, and changing one changes the others. So the stack gets laid out, measured, adjusted and laid out again until the total lands on the requested height. Within rounding, because you can't split whole pixels perfectly across panes, and pretending otherwise would just reintroduce the arithmetic problem from the other direction.

The docs entry changed with it, so the written description matches the new behaviour. And the date it landed was 2026-10-15.

There's also a new sweep test. It checks that no plot or pane box overlaps the header or footer, and it runs in every style. That's the part I care about most, actually, because it tests the symptom itself, not one particular chart.

Why it happened

The bug survived because every individual piece of it was reasonable. The option had a plausible name. The behaviour was documented. Single-pane charts, which are the common case and the one people test first, were correct. Review looks at a diff and asks whether the diff does what it says; nobody in that review was positioned to ask whether the name matched what a host would assume.

The deeper issue is that nothing in the test suite expressed the invariant a host actually relies on: the figure fits in the box I gave it. The tests checked that panes rendered, that series drew, that the main pane had the height asked for. All true. All beside the point.

How to avoid it

If you ship a library with a size option, write down what the total output size is, not what one component's size is, and test that total directly. Put the invariant in a test that doesn't care how the internals are arranged: for any input, the output fits in the box.

Be suspicious of any option where the name describes the whole and the code applies it to a part. It works until the day the part and the whole stop being the same thing, and then it goes on working, wrongly, for a long time.

And sweep across every configuration you ship, not a representative one. The overflow lived in the stacked layouts. A test that only ever drew a single pane would have passed forever.

Every commit, file reference and date above is checked against this incident's Noctem evidence pack before publication. Evidence: charts-fixed-height-overflowed-its-box.

Also in the museum

The other 23 failures