diff --git a/webview-ui/src/components/DataGrid.svelte b/webview-ui/src/components/DataGrid.svelte
index f5e9d50..b851c16 100644
--- a/webview-ui/src/components/DataGrid.svelte
+++ b/webview-ui/src/components/DataGrid.svelte
@@ -259,6 +259,19 @@
const selectedRange = $derived(gridStore.selectedRange);
const frozenCols = $derived(gridStore.frozenCols);
+ // Visible position of the column that stretches to absorb any leftover
+ // horizontal space, so the grid spans the full pane with no blank
+ // "phantom column" on the right. We grow the last visible column unless it's
+ // frozen (sticky positioning + flex-grow don't mix cleanly). When the columns
+ // already overflow the pane, flex-grow has nothing to add and the column keeps
+ // its natural width (horizontal scroll kicks in as before).
+ const fillColPos = $derived.by(() => {
+ const cols = gridStore.visibleSchema;
+ const last = cols.length - 1;
+ if (last < 0) return -1;
+ return frozenCols.has(cols[last].index) ? -1 : last;
+ });
+
// Returns the sticky `left` offset (in px) for a frozen column at visual position `colPos`.
// Row-number column is 56px + 1px border = 57px. Sums widths of all frozen cols before this one.
function getFrozenLeft(col: import('@shared/schema.js').ColumnSchema, colPos: number): number {
@@ -465,6 +478,7 @@
class="header-cell"
class:header-cell-selected={colSel}
class:col-frozen={isFrozen}
+ class:col-fill={colPos === fillColPos}
class:drag-over={dragOverPos === colPos && dragFromPos !== colPos}
role="columnheader"
tabindex="-1"
@@ -519,6 +533,7 @@
onCellClick(e, row, col.index)}
ondblclick={() => startEdit(row, col.index)}
@@ -827,6 +843,17 @@
box-sizing: border-box;
}
+ /* The last visible column stretches to absorb leftover horizontal space so the
+ grid fills the pane with no blank phantom column on the right. The inline
+ `width` acts as the flex-basis and flex-shrink stays 0, so the column only
+ ever grows past its natural width — when columns overflow the pane there's
+ nothing to absorb and it keeps its set width (horizontal scroll as before). */
+ .header-cell.col-fill,
+ .cell.col-fill,
+ .cell-input.col-fill {
+ flex-grow: 1;
+ }
+
/* ── Coloured mode ──────────────────────────────────────────────────────
When the user enables column colours, two things change:
1. Column separators get a slightly stronger border so the pastels
diff --git a/webview-ui/src/stores/grid.svelte.ts b/webview-ui/src/stores/grid.svelte.ts
index 1c6dc88..0b0848a 100644
--- a/webview-ui/src/stores/grid.svelte.ts
+++ b/webview-ui/src/stores/grid.svelte.ts
@@ -102,6 +102,11 @@ class GridStore {
// Auto-fitted column widths, keyed by column name. Computed once after data load.
private _autoWidths = new Map();
+ // Guards the one-shot auto-width pass for worker-backed formats (JSON/Parquet/
+ // Arrow/Excel), which never populate _csvAllRows and so miss the CSV/raw-rows
+ // auto-width path. Reset on every worker READY so a new dataset (or Excel sheet
+ // switch) re-fits its columns.
+ private _autoWidthsDone = false;
// Viewport
visibleStartRow = $state(0);
@@ -1523,6 +1528,7 @@ class GridStore {
this.schema = msg.payload.schema;
this.totalRows = msg.payload.totalRows;
this.filteredRows = msg.payload.totalRows;
+ this._autoWidthsDone = false;
if (msg.payload.availableSheets) {
this.availableSheets = msg.payload.availableSheets;
this.selectedSheet = msg.payload.selectedSheet ?? msg.payload.availableSheets[0] ?? '';
@@ -1534,6 +1540,14 @@ class GridStore {
case 'CHUNK': {
const { requestId, rows, startRow, filteredTotal } = msg.payload;
+ // Worker-backed formats (JSON/Parquet/Arrow/Excel) never populate
+ // _csvAllRows, so the CSV/raw-rows auto-width path never runs for them.
+ // Fit widths once from the first chunk (natural order, no filter/sort on
+ // initial load) so columns size to their content like CSV does.
+ if (startRow === 0 && !this._autoWidthsDone && rows.length > 0 && this.schema.length > 0) {
+ this._computeAutoWidths(this.schema, rows);
+ this._autoWidthsDone = true;
+ }
this._storeChunk(startRow, rows, requestId);
this.filteredRows = filteredTotal;
const resolve = this._pendingRequests.get(requestId);