From de6064b4f2664b076480a0d84310d8b0c3619793 Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Wed, 16 Sep 2026 23:21:53 -0400 Subject: [PATCH] Show where a scale edit lands, for all three measures at once Each ledger row derived only from its own input, so typing a dollar figure left the units and price rows blank -- which are precisely the two numbers that say whether you just asked for a volume change or a price change. The plug toggle decided it and then showed you nothing. Adds a Result line under the edit rows carrying value, units and price together, resolved the same way the server resolves the increments: a price input multiplies through volume, a dollar input with plug = volume scales units in proportion, anything else holds units. Measures that actually move are picked out in blue against the ones that hold, so plug = price reads as price moving alone and plug = volume as price standing still. Co-Authored-By: Claude Opus 5 (1M context) --- ui/src/components/OperationPanel.jsx | 61 ++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/ui/src/components/OperationPanel.jsx b/ui/src/components/OperationPanel.jsx index d5373fd..8dcb1aa 100644 --- a/ui/src/components/OperationPanel.jsx +++ b/ui/src/components/OperationPanel.jsx @@ -296,6 +296,51 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se } const dollarsOnly = filled('value') && !filled('units') && !filled('price') + // Where the edit lands, for all three measures at once. The rows above each + // show only their own input, so a dollar figure entered with plug = volume + // left units and price blank -- exactly the two numbers you need to see to + // know whether you asked for a price change or a volume change. + const numOf = (key, field) => { + const raw = derived[key]?.[field] + const n = parseFloat(raw) + return isFinite(n) ? n : null + } + + const outcome = (() => { + const curValue = basisOf('value') ?? 0 + const curUnits = basisOf('units') ?? 0 + if (!filled('value') && !filled('units') && !filled('price')) return null + + let newValue, newUnits + + if (filled('price') && !filled('value')) { + // price is the input; dollars fall out of price x volume + const targetPrice = numOf('price', 'new') + newUnits = filled('units') ? numOf('units', 'new') : curUnits + newValue = targetPrice != null && newUnits != null ? targetPrice * newUnits : null + } else { + newValue = filled('value') ? numOf('value', 'new') : curValue + if (filled('units')) newUnits = numOf('units', 'new') + else if (dollarsOnly && scalePlug === 'volume') newUnits = curValue === 0 ? null + : curUnits * (newValue / curValue) + else newUnits = curUnits + } + + if (newValue == null || newUnits == null) return null + const newPrice = newUnits === 0 ? null : newValue / newUnits + const curPriceLocal = curUnits === 0 ? null : curValue / curUnits + return { + value: newValue, + units: newUnits, + price: newPrice, + moved: { + value: Math.abs(newValue - curValue) > 1e-9, + units: Math.abs(newUnits - curUnits) > 1e-9, + price: curPriceLocal != null && newPrice != null && Math.abs(newPrice - curPriceLocal) > 1e-9, + }, + } + })() + const setEdit = (key, field, raw) => setScaleInputs(prev => ({ ...prev, [key]: { field, raw } })) @@ -407,6 +452,22 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se })} ))} + + {outcome && ( + <> + {rule}{measures.map(m =>
)} + + Result + {measures.map(m => ( + + + {fmtNum(outcome[m.key], m.dp)} + + + ))} + + + )}