Summary
formatCurrency in packages/fields/src/index.tsx renders a currency amount whose fractional part has one significant digit with one decimal place instead of two. 1234.5 renders as $1,234.5.
Its own doc comment states the opposite contract:
Trailing .00 is dropped when the value is a whole number — Salesforce convention: $1,234.50 keeps cents; $1,234 does not.
So the intended behaviour is documented, and the code does not deliver it.
Mechanism
const isWhole = Number.isFinite(value) && value === Math.trunc(value);
const maxFrac = isWhole ? 0 : 2;
// ...
minimumFractionDigits: 0, // ← the defect
maximumFractionDigits: maxFrac,
maximumFractionDigits is correctly switched on wholeness, but minimumFractionDigits is a constant 0. For a non-whole amount that gives the range [0, 2], and Intl then emits the shortest representation — so a genuine cents value of .50 is printed as .5. The intended pairing is minimumFractionDigits: isWhole ? 0 : 2.
Measured directly against the construction:
1234.5 => $1,234.5 (expected $1,234.50)
1234.56 => $1,234.56 (correct)
1234 => $1,234 (correct — the whole-number case the comment describes)
Only the "non-whole, one significant decimal digit" case is wrong, which is why it survived: the whole-number and two-digit cases both look right.
Impact
Reachable on any currency-typed field whose amount ends in a zero cent digit — prices like 19.90, 1234.50, 0.50. Those render as $19.9, $1,234.5, $0.5. This is money on a record page and in grid cells, so it reads as a data error rather than a formatting one.
Consumers of the same function inherit it: CurrencyCellRenderer (packages/fields/src/index.tsx), ObjectGrid (packages/plugin-grid/src/ObjectGrid.tsx), recordFields (packages/plugin-dashboard), and ObjectGantt (packages/plugin-gantt).
Not fixed here, and why
Found while implementing #4033 (number renderers hardcoding Intl.NumberFormat('en-US')). It is out of that card's scope — it is neither a locale defect nor a grouping defect, and #4033's ruling is deliberately narrow — so it is filed rather than folded in.
PR for #4033 pins the current behaviour in a control test (packages/fields/src/__tests__/NumberCellRenderer.grouping.test.tsx, "keeps grouping and the fractional part when the amount is not whole") with a comment pointing here, so this issue's fix will flip a test that is deliberately red-on-fix rather than silently changing an unwatched surface.
Suggested fix
minimumFractionDigits: isWhole ? 0 : 2, plus a pin for the .50 case. Worth checking CurrencyField's formatAmount (packages/fields/src/widgets/CurrencyField.tsx) at the same time — it uses precision for both bounds and so does not have this bug, which makes the two paths disagree today.
Generated by Claude Code
Summary
formatCurrencyinpackages/fields/src/index.tsxrenders a currency amount whose fractional part has one significant digit with one decimal place instead of two.1234.5renders as$1,234.5.Its own doc comment states the opposite contract:
So the intended behaviour is documented, and the code does not deliver it.
Mechanism
maximumFractionDigitsis correctly switched on wholeness, butminimumFractionDigitsis a constant0. For a non-whole amount that gives the range[0, 2], andIntlthen emits the shortest representation — so a genuine cents value of.50is printed as.5. The intended pairing isminimumFractionDigits: isWhole ? 0 : 2.Measured directly against the construction:
Only the "non-whole, one significant decimal digit" case is wrong, which is why it survived: the whole-number and two-digit cases both look right.
Impact
Reachable on any currency-typed field whose amount ends in a zero cent digit — prices like
19.90,1234.50,0.50. Those render as$19.9,$1,234.5,$0.5. This is money on a record page and in grid cells, so it reads as a data error rather than a formatting one.Consumers of the same function inherit it:
CurrencyCellRenderer(packages/fields/src/index.tsx),ObjectGrid(packages/plugin-grid/src/ObjectGrid.tsx),recordFields(packages/plugin-dashboard), andObjectGantt(packages/plugin-gantt).Not fixed here, and why
Found while implementing #4033 (number renderers hardcoding
Intl.NumberFormat('en-US')). It is out of that card's scope — it is neither a locale defect nor a grouping defect, and #4033's ruling is deliberately narrow — so it is filed rather than folded in.PR for #4033 pins the current behaviour in a control test (
packages/fields/src/__tests__/NumberCellRenderer.grouping.test.tsx, "keeps grouping and the fractional part when the amount is not whole") with a comment pointing here, so this issue's fix will flip a test that is deliberately red-on-fix rather than silently changing an unwatched surface.Suggested fix
minimumFractionDigits: isWhole ? 0 : 2, plus a pin for the.50case. Worth checkingCurrencyField'sformatAmount(packages/fields/src/widgets/CurrencyField.tsx) at the same time — it usesprecisionfor both bounds and so does not have this bug, which makes the two paths disagree today.Generated by Claude Code