From 1d2fc97eee529602850d1d9402f83ca1294a995d Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Wed, 16 Sep 2026 23:39:21 -0400 Subject: [PATCH] Compact the log's slice column, show value impact, expand the payload on click MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things about the change log, from using it. The slice column grew to a block per entry once it listed lines, which is too much for a table you scan. It is one truncated line now: a single slice reads as its fields, and a dragged region as "5 slices · omon = 07 - Dec … 11 - Apr" -- enough to recognise the entry. There was no indication of what an entry did to the numbers, which is the first thing you want from a change log. The route already returned value_total and it simply was not rendered. Added, signed and coloured, so a list of adjustments reads as a list of impacts. And clicking a row now opens slice and params as formatted JSON -- the exact payload that went over the route, including the increments and the plug, which no amount of summarising in the cell was going to convey. Co-Authored-By: Claude Opus 5 (1M context) --- ui/src/views/Forecast.jsx | 81 ++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx index df581e7..7358f8d 100644 --- a/ui/src/views/Forecast.jsx +++ b/ui/src/views/Forecast.jsx @@ -64,6 +64,8 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio // volume flat, 'volume' keeps price flat. Mirrors the Excel form's // Plug Price / Plug Volume choice. const [scalePlug, setScalePlug] = useState(() => localStorage.getItem('pf_scale_plug') || 'price') + // which change-log row has its payload open, if any + const [expandedLog, setExpandedLog] = useState(null) // what a target/percentage is measured against: the rows this operation can // write, or everything the pivot shows for the slice (excluded rows included) const [targetBasis, setTargetBasis] = useState('selected') @@ -1160,6 +1162,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio async function openLog() { setShowLog(true) + setExpandedLog(null) setLogLoading(true) try { // Baseline and reference loads are segment construction, not forecasting. @@ -1411,6 +1414,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio Slice Tag Note + Value Rows @@ -1424,23 +1428,14 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio {entry.operation} - - {(() => { - const lines = fmtSliceLines(entry.slice) - if (lines.length === 0) return '—' - // the raw payload is on the title, for the case where the - // rendering has flattened something worth seeing - return ( -
- {lines.map((l, i) => ( -
- {lines.length > 1 && {i + 1}.} - {l} -
- ))} -
- ) - })()} + + + 0 ? 'text-green-700' : entry.value_total < 0 ? 'text-red-600' : 'text-gray-400'}`}> + {entry.value_total == null ? '—' + : entry.value_total.toLocaleString(undefined, { maximumFractionDigits: 0 })} + {entry.row_count ?? '—'}