diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index 2ae51d6..861bd3c 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -973,7 +973,6 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio // throw here would abort the rest of initViewer silently. try { await ensureRowLabelWidth() - await fitRowLabelsNow() } catch (err) { console.error('[pf-layout] threw', err) } @@ -1255,9 +1254,16 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio // A minimum leaves the default spacing alone where it is already reasonable and // only intervenes where a column came back unusably narrow. Anything already // wider — set by dragging, or recorded in a layout — is untouched. - // Three characters, measured in the grid's own font rather than guessed in - // pixels, so it holds up if the theme or zoom changes. - const MIN_ROW_LABEL_CHARS = 3 + // Measured in the grid's own font rather than guessed in pixels, so it holds + // up if the theme or zoom changes. + // + // The same floor goes under every row-header column, which is what makes it + // safe for the indentation: a level's indent is the previous column's width, + // so a uniform floor steps evenly, where a per-column fit does not. Three + // characters was too mean to read a label through; this is the one number to + // turn if the labels are still squished, and the cost of turning it is that + // every level indents by that much more. + const MIN_ROW_LABEL_CHARS = 12 async function ensureRowLabelWidth() { const grid = (await viewerRef.current?.getPlugin())?.regular_table @@ -1293,6 +1299,18 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio // Every level is measured, because which column is the outermost depends on // the depth on show, but only that one is resized — see below. // + // UNUSED, and parked for the second time. It was called from initViewer and + // applyLayout for a while, and the trouble is that a level's indent *is* the + // previous column's width: widening any row-header column to fit its content + // deepens the inset of everything below it. Which column got widened depended + // on how deep the tree happened to be drawn when the layout loaded, so one + // level per layout came back deeply inset while the rest stepped evenly — + // deterministic, but unreadable as a pattern from the outside. Fitting and + // even Excel-style increments cannot both come from column widths. Doing it + // properly means pinning every level but the last to a fixed increment and + // letting a long label overflow rightwards into the last column, which is a + // change to the grid CSS as well as to this. + // // Measured with a canvas rather than the DOM: the cell is clipped, so reading // its box back gives the width it was allotted, not the width of its text — // the same circularity that stops headers sizing themselves. @@ -1354,20 +1372,6 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio await grid.draw({ invalid_columns: true }) } - // fitRowLabels takes the grid; this finds it, so the restore paths can call it - // the same way they call ensureRowLabelWidth. - // - // Deliberately not routed through the Fit widths button: that one goes via - // regular-table's own auto-size, which measures the leaf header's rendered box - // over the rows currently in the viewport. fitRowLabels measures the label - // text on a canvas instead, so it is not blinded by a clipped cell and does - // not depend on what happens to be scrolled into view. - async function fitRowLabelsNow() { - const grid = (await viewerRef.current?.getPlugin())?.regular_table - if (!grid?.saveColumnSizes) return - await fitRowLabels(grid) - } - const GRID_CSS = ` /* Let headers and row labels size their own column. * @@ -1580,7 +1584,6 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio else if (cfg.expand_depth != null) await applyDepth(cfg.expand_depth) await ensureRowLabelWidth() - await fitRowLabelsNow() // The persisted copy is taken after the restore, so it carries whatever // cleanLayout dropped on the way in rather than the stale original -- and