From 744342d519d002b7cc133113d9a54d9f99b89265 Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Thu, 17 Sep 2026 03:22:42 -0400 Subject: [PATCH] =?UTF-8?q?Count=20row=20depth=20the=20way=20group=5Fby=5F?= =?UTF-8?q?depth=20does=20=E2=80=94=20levels,=20not=20boundaries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EXPAND buttons did nothing because their 0..3 were written against view.set_depth(), which is 0-based, while the config field is 1-based. server.cpp does ctx1->set_depth(row_pivot_depth - 1) // one-sided ctx2->set_depth(HEADER_ROW, row_pivot_depth - 1) for both contexts, so group_by_depth counts the levels to show. Passing 0 asked the engine for set_depth(-1), and every other button was off by one level. The fork's own depth_test.mjs uses group_by_depth: 1 as its working control, which is the same arithmetic seen from the outside. So the toolbar sends d + 1, and subtracts one again when reading a saved layout back for the button state. Co-Authored-By: Claude Opus 5 (1M context) --- ui/src/views/Forecast.jsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index 977294b..4238363 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -676,7 +676,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio // restore() has already applied group_by_depth; this only syncs the // toolbar. expand_depth is the legacy key, from when depth was imperative // and had to be stored beside the config rather than in it. - if (cfg.group_by_depth != null) setExpandDepth(cfg.group_by_depth) + if (cfg.group_by_depth != null) setExpandDepth(cfg.group_by_depth - 1) else if (cfg.expand_depth != null) await applyDepth(cfg.expand_depth) } else { const sourceDefault = sources.find(s => String(s.id) === String(sid))?.default_layout @@ -832,11 +832,18 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio async function applyDepth(d) { const viewer = viewerRef.current if (!viewer) return - // A partial restore({ group_by_depth }) on its own did nothing, so the whole - // config goes back with the field changed. table is dropped: it is loaded by - // reference, and a stale name in the config fails the lookup. + // group_by_depth counts levels, where view.set_depth() counts the boundary + // below them -- server.cpp does ctx->set_depth(row_pivot_depth - 1) for both + // the one- and two-sided contexts. So the toolbar's 0..3, which were written + // against the imperative call, are one less than the config wants: depth 0 + // (grand total only) is group_by_depth 1, and passing 0 asked the engine for + // set_depth(-1). + // + // The whole config goes back rather than a partial update, which had no + // effect on its own. table is dropped: it is loaded by reference, and a stale + // name in a restored config fails the lookup. const { table: _t, ...cfg } = await viewer.save() - await viewer.restore({ ...cfg, group_by_depth: d }) + await viewer.restore({ ...cfg, group_by_depth: d + 1 }) setExpandDepth(d) } @@ -906,7 +913,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio cfg.plugin_config = { ...(cfg.plugin_config || {}), edit_mode: 'SELECT_REGION' } await viewer.restore(cfg) adoptSplit(cfg.split_full?.length ? cfg.split_full : cfg.split_by, (cfg.split_by || []).length) - if (cfg.group_by_depth != null) setExpandDepth(cfg.group_by_depth) + if (cfg.group_by_depth != null) setExpandDepth(cfg.group_by_depth - 1) else if (cfg.expand_depth != null) await applyDepth(cfg.expand_depth) setActiveLayoutId(layout.id) await persistLayout(versionId, cfg)