Found while implementing objectui#4596 (comment repair in packages/core/src/utils). Out of that card's surface — recorded rather than folded in.
The site
packages/plugin-dashboard/src/recordFields.tsx:189-192, the percent branch of the record-field formatter:
if (typeof fmt === 'string' && /%/.test(fmt) && typeof value === 'number') {
const decimals = (fmt.match(/0\.(0+)%/) || [undefined, ''] as any)[1].length;
const normalized = value > 1 ? value / 100 : value;
return formatPercent(normalized * 100, decimals, displayLocale);
}
For value <= 1 the pair is a correct fraction-to-points scaling (value * 100). For value > 1 it is (value / 100) * 100 — an identity that is not value-preserving in binary floating point, and the result is then handed to formatPercent, whose own percentDisplayValue passes a magnitude at or above 1 through unchanged.
This is the same artefact objectui#4590 (PR #4595) removed from inside formatPercent, re-introduced one call frame upstream. formatPercent now renders percentage points directly via style: 'percentPoints' with no division, so the division here is both lossy and pointless.
Measured
Values v = i/1000 for i in 1001..200000 (all v > 1, so all take the divided branch), rendered through the same Intl options formatPercent uses (style: 'unit' / unit: 'percent' / unitDisplay: 'narrow') at precisions 0/1/2:
- 19,978 of 199,000 values have
(v / 100) * 100 !== v.
- 1,108 rendered strings actually move, every one a last-digit off-by-one.
First movers, all at 2 decimals — [value, direct, via the round trip]:
1.605 -> "1.61%" vs "1.60%"
1.655 -> "1.66%" vs "1.65%"
1.705 -> "1.71%" vs "1.70%"
1.785 -> "1.79%" vs "1.78%"
1.835 -> "1.84%" vs "1.83%"
Half-up on the authored decimal is the left column, so the round trip is the artefact. Same failure mode objectui#4590 recorded: a last-digit off-by-one, which is the shape least likely to be noticed and most likely to be trusted.
Suggested shape (not a ruling)
The division has no work to do — the branch only needs to decide whether the stored number is a fraction or already points:
return formatPercent(value > 1 ? value : value * 100, decimals, displayLocale);
Worth checking against percentDisplayValue first: formatPercent already applies that helper, so this call site may be duplicating a scaling decision that @object-ui/core owns as its single source of truth (percentDisplayValue's doc comment states exactly that intent). If so, the right fix may be to drop the local normalisation entirely rather than to repair it. That is a design question for whoever picks this up, not something to guess at here.
Scope note
Not a duplicate of objectui#4590, which closed the division inside formatPercent. This is a separate caller doing its own division before the call. Nothing in objectui#4596's fenced surface touches plugin-dashboard.
Found while implementing objectui#4596 (comment repair in
packages/core/src/utils). Out of that card's surface — recorded rather than folded in.The site
packages/plugin-dashboard/src/recordFields.tsx:189-192, the percent branch of the record-field formatter:For
value <= 1the pair is a correct fraction-to-points scaling (value * 100). Forvalue > 1it is(value / 100) * 100— an identity that is not value-preserving in binary floating point, and the result is then handed toformatPercent, whose ownpercentDisplayValuepasses a magnitude at or above 1 through unchanged.This is the same artefact objectui#4590 (PR #4595) removed from inside
formatPercent, re-introduced one call frame upstream.formatPercentnow renders percentage points directly viastyle: 'percentPoints'with no division, so the division here is both lossy and pointless.Measured
Values
v = i/1000foriin1001..200000(allv > 1, so all take the divided branch), rendered through the sameIntloptionsformatPercentuses (style: 'unit'/unit: 'percent'/unitDisplay: 'narrow') at precisions 0/1/2:(v / 100) * 100 !== v.First movers, all at 2 decimals —
[value, direct, via the round trip]:Half-up on the authored decimal is the left column, so the round trip is the artefact. Same failure mode objectui#4590 recorded: a last-digit off-by-one, which is the shape least likely to be noticed and most likely to be trusted.
Suggested shape (not a ruling)
The division has no work to do — the branch only needs to decide whether the stored number is a fraction or already points:
Worth checking against
percentDisplayValuefirst:formatPercentalready applies that helper, so this call site may be duplicating a scaling decision that@object-ui/coreowns as its single source of truth (percentDisplayValue's doc comment states exactly that intent). If so, the right fix may be to drop the local normalisation entirely rather than to repair it. That is a design question for whoever picks this up, not something to guess at here.Scope note
Not a duplicate of objectui#4590, which closed the division inside
formatPercent. This is a separate caller doing its own division before the call. Nothing in objectui#4596's fenced surface touchesplugin-dashboard.