From 75f662d12af74e46d13ea78bbe84f7115443b268 Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Mon, 21 Sep 2026 22:27:31 -0400 Subject: [PATCH] Stop reading numbers back out of formatted text The Result row re-parsed the ledger's own display strings, which fmtNum grouped with toLocaleString. parseFloat stops at the first separator, so "3,456,789" came back as 3 -- a plausible small number rather than an error, which is why it read as a formatting quirk instead of a wrong answer. A 3% change on sales_usd previewed as 3. It showed only where the new value was derived rather than typed: the field you type into keeps your raw text, so entering a target directly looked right while the % and Change rows did not. derive now returns the three figures as numbers alongside the strings, and outcome reads those. Nothing has to know how the display is grouped. The write was never affected -- buildPayload reads scaleInputs[*].raw, which LedgerInput has always kept comma-free -- so this was a wrong preview of a correct write. Co-Authored-By: Claude Opus 5 (1M context) --- ui/src/components/OperationPanel.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ui/src/components/OperationPanel.jsx b/ui/src/components/OperationPanel.jsx index 7bf8172..a498961 100644 --- a/ui/src/components/OperationPanel.jsx +++ b/ui/src/components/OperationPanel.jsx @@ -258,6 +258,12 @@ function derive(current, edit, dp = 2) { new: fmtNum(next, dp), change: fmtNum(change, dp), pct: pct == null ? '—' : fmtNum(pct, 1), + // The same three as numbers, for anything that needs to compute with them. + // The strings above are grouped by toLocaleString, and parseFloat stops at + // the first separator -- "3,456,789" reads back as 3 -- so a caller that + // re-parsed the display got a plausible small number rather than an error. + // Nothing should have to know that; the numbers travel alongside. + n: { new: next, change, pct }, } out[edit.field] = edit.raw // keep what you typed exactly as typed return out @@ -442,9 +448,8 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se // 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 n = derived[key]?.n?.[field] + return n != null && isFinite(n) ? n : null } const outcome = (() => {