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 (
-
+ )]
+ })}
)}
@@ -1708,6 +1721,38 @@ function fmtSliceLines(slice) {
]
}
+// One line, whatever the shape. The full payload is a click away, so this only
+// has to say enough to recognise the entry: what varied, and how much of it.
+function LogJson({ label, value }) {
+ const empty = value == null || (typeof value === 'object' && Object.keys(value).length === 0)
+ return (
+