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) <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-21 22:27:31 -04:00
parent a642c65d1d
commit 75f662d12a

View File

@ -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 = (() => {