fix(read): render wide SQLite tables as vertical blocks

The ASCII table renderer in `sqlite-reader.ts` shrank columns down to
`MIN_COLUMN_WIDTH=1` to fit the 120-cell budget. With ~20+ columns
(the reporter had 33) every multi-char cell collapsed to a lone `…`
and the final per-line `truncateToWidth(..., MAX_RENDER_WIDTH)` then
chopped the right edge — so the read tool returned a table of nothing
but ellipses with the rightmost cells missing entirely.

Bump the per-column floor to 3 (so cells always show at least two real
glyphs alongside the ellipsis) and, when the column count alone forces
the floor over budget, fall back to a per-row vertical block layout —
mirroring `psql`'s expanded display mode. Each row becomes a
`column: value` group with column names padded so colons align and
the value line truncated to the same 120-cell budget.

Fixes #3107
This commit is contained in:
roboomp
2026-06-20 07:55:09 +00:00
parent 62756efb8f
commit ed37f076ef
3 changed files with 93 additions and 3 deletions
@@ -370,6 +370,36 @@ describe("SQLite tool support", () => {
}
});
it("falls back to vertical row blocks when the column count exceeds the horizontal budget (#3107)", () => {
const columns = ["_id", ...Array.from({ length: 32 }, (_, i) => `col_${i + 1}`)];
const row: Record<string, unknown> = { _id: 7 };
for (let i = 1; i <= 32; i++) row[`col_${i}`] = `value_${i}`;
const rendered = renderTable(columns, [row], {
totalCount: 1,
offset: 0,
limit: 20,
table: "wide_columns",
dbPath: sqlitePath,
});
// Horizontal layout would shrink every column to width 1 and chop the
// right edge — i.e., the line would look like `| … | … | … | …` (>=2
// ellipses chained by ` | `). Vertical mode renders one `col: value`
// per line, so that signature must NOT be present.
expect(rendered).not.toMatch(/…(?: \| …){2,}/);
// Each declared column must appear with its real value on its own line.
expect(rendered).toContain("── Row 1 ──");
expect(rendered).toContain("_id : 7");
expect(rendered).toContain("col_1 : value_1");
expect(rendered).toContain("col_32: value_32");
for (const line of rendered.split("\n")) {
expect(Bun.stringWidth(line)).toBeLessThanOrEqual(120);
}
});
it("inserts rows through the write tool with JSON5 content", async () => {
const dbPath = await stampFreshDb("write-insert.sqlite");
await writeTool.execute("sqlite-write-insert", {