Skip to content

Commit fd141c8

Browse files
committed
fix(columns): getColumnGeometry honors count when widths is absent (SD-2629)
The balancing migration (bd2ccf1) builds its geometry input directly from columnCount/columnGap/columnWidth and only attaches a widths array in explicit mode. In equal mode it passed {count, gap, width} with no widths, and getColumnGeometry fell back to a single [width] column - so every column index past 0 clamped onto column 0 and balanced content stacked on the left margin (col2 x 432 -> 96 on the last page of equal-width multi-column docs). A geometry must have exactly count columns. Expand the scalar width to count equal columns when no widths array is present rather than collapsing to one. normalizeColumnLayout already emits one width per column, so the normal engine path is unchanged; this only hardens the hand-built case. The layout corpus caught it (9 equal-width multi-column docs regressed; all restored).
1 parent 8f2cdac commit fd141c8

2 files changed

Lines changed: 21 additions & 1 deletion

File tree

packages/layout-engine/contracts/src/column-layout.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,18 @@ describe('getColumnGeometry + geometry helpers (SD-2629)', () => {
203203
expect(geom[0].gapAfter).toBe(999);
204204
expect(geom[1].x).toBe(300 + 999);
205205
});
206+
207+
it('expands an equal-mode layout with no widths array to `count` columns (SD-2629 regression)', () => {
208+
// A hand-built equal-mode layout (column-balancing) carries only the scalar `width`, no widths
209+
// array. Geometry must still yield `count` columns; collapsing to a single column mapped every
210+
// index past 0 onto column 0's x, stacking balanced multi-column content on the left margin.
211+
const geom = getColumnGeometry({ count: 2, gap: 48, width: 288 });
212+
expect(geom).toEqual([
213+
{ index: 0, x: 0, width: 288, gapAfter: 48 },
214+
{ index: 1, x: 336, width: 288, gapAfter: 0 },
215+
]);
216+
expect(getColumnX(geom, 1, 96)).toBe(432);
217+
});
206218
});
207219

208220
describe('columnLayoutsEqual', () => {

packages/layout-engine/contracts/src/column-layout.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,16 @@ export function normalizeColumnLayout(
182182
* widths and per-column `gaps`, falling back to the uniform gap when no per-column gaps exist.
183183
*/
184184
export function getColumnGeometry(normalized: NormalizedColumnLayout): ColumnGeometry[] {
185+
// A geometry must have exactly `count` columns. normalizeColumnLayout always emits one width per
186+
// column, but a hand-built equal-mode layout may carry only the scalar `width` with no widths array
187+
// (e.g. column-balancing constructs its input directly). Expand that to `count` equal columns
188+
// instead of collapsing to a single [width] column, which would map every column index past 0 onto
189+
// column 0's x and stack later columns on the left margin. (SD-2629)
190+
const count = Number.isFinite(normalized.count) ? Math.max(1, Math.floor(normalized.count)) : 1;
185191
const widths =
186-
Array.isArray(normalized.widths) && normalized.widths.length > 0 ? normalized.widths : [normalized.width];
192+
Array.isArray(normalized.widths) && normalized.widths.length > 0
193+
? normalized.widths
194+
: new Array(count).fill(normalized.width);
187195
return buildColumnGeometry(widths, normalized.gap, Boolean(normalized.withSeparator), normalized.gaps);
188196
}
189197

0 commit comments

Comments
 (0)