Skip to content

Commit de42304

Browse files
fix(dashboard): pack remaining grid widgets after sixel extraction
When sixel-eligible widgets are stripped out of the framebuffer, the remaining widgets kept their original `layout.y`, so `renderGrid` still allocated blank bands for the removed rows and the sixel images were always appended after the whole grid (changing vertical order). Compress `layout.y` values by dense rank while preserving relative order within the same original band, so the grid packs contiguously from y=0 with no holes and no reordering of left/right widgets on the same row.
1 parent 18f6f3f commit de42304

2 files changed

Lines changed: 39 additions & 4 deletions

File tree

packages/cli/src/lib/formatters/dashboard.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1855,7 +1855,8 @@ export function formatDashboardWithData(data: DashboardViewData): string {
18551855
: data.widgets;
18561856

18571857
if (gridWidgets.length > 0) {
1858-
lines.push(...renderGrid(gridWidgets, termWidth));
1858+
const packed = packGridY(gridWidgets);
1859+
lines.push(...renderGrid(packed, termWidth));
18591860
}
18601861
for (const w of sixelWidgets) {
18611862
lines.push(...renderSixelWidget(w, termWidth));
@@ -1865,6 +1866,40 @@ export function formatDashboardWithData(data: DashboardViewData): string {
18651866
return lines.join("\n");
18661867
}
18671868

1869+
/**
1870+
* Return a shallow copy of the widgets with their `layout.y` renumbered so
1871+
* the remaining widgets pack contiguously from y=0 without blank bands.
1872+
* Widgets without a layout are left untouched. Relative order within the
1873+
* same original y-band is preserved (important for left/right widgets on
1874+
* the same row).
1875+
*/
1876+
function packGridY(
1877+
widgets: DashboardViewWidget[]
1878+
): DashboardViewWidget[] {
1879+
const ys = Array.from(
1880+
new Set(
1881+
widgets
1882+
.map((w) => w.layout?.y)
1883+
.filter((y): y is number => typeof y === "number")
1884+
)
1885+
).sort((a, b) => a - b);
1886+
if (ys.length === 0) {
1887+
return widgets;
1888+
}
1889+
const rank = new Map<number, number>(ys.map((y, i) => [y, i]));
1890+
1891+
return widgets.map((w) => {
1892+
if (!w.layout) {
1893+
return w;
1894+
}
1895+
const r = rank.get(w.layout.y);
1896+
if (r === undefined) {
1897+
return w;
1898+
}
1899+
return { ...w, layout: { ...w.layout, y: r } };
1900+
});
1901+
}
1902+
18681903
/**
18691904
* Whether a widget should render as an inline sixel image: opt-in is on, the
18701905
* data is a plain (non-categorical) timeseries, and the terminal supports

packages/cli/test/lib/formatters/dashboard-sixel-integration.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,13 @@ describe("dashboard sixel integration", () => {
180180

181181
const output = formatDashboardWithData(data);
182182
const lines = output.split("\n");
183-
// The DCS image must not share a terminal row with the neighboring widget's
184-
// borders — the sixel line carries no box-drawing characters.
183+
// The DCS image must not share a terminal row with any bordered widget.
185184
const sixelLine = lines.find((l) => l.includes(`${ESC}P`));
186185
expect(sixelLine).toBeDefined();
187186
expect(sixelLine).not.toContain("│");
188187
expect(sixelLine).not.toContain("─");
189-
// The character grid (big-number widget) still renders above the image.
188+
// The character grid (big-number widget) still renders; the sixel image
189+
// appears after the grid (full-width, no hole in the packed layout).
190190
expect(output).toContain("Big Number");
191191
expect(output).toContain("Sixel Chart");
192192
});

0 commit comments

Comments
 (0)