fix(units): round a converted value where the conversion happens

Every entry in the display-transform registry declares the decimals its
unit is read at. That number was carried to the render layer and read by
exactly one screen out of all the ones that show a converted value, so
everywhere else the raw product of a kilogram and 2.20462262185 reached
the surface. Pounds came out with a dozen digits it never needed.

Move the rounding into `applyDisplayTransform` and
`applyDisplayTransformDelta`, so a surface gets a correctly-scaled number
whether or not it remembers to ask. Patching the weight cell would have
left the same trap behind inches, Fahrenheit, miles per hour and miles.

Two boundaries stay exempt and are documented as such. A transform that
does not rescale is the exact identity and is never rounded, so a stored
78.45 kg keeps rendering as 78.45 on the metric branch and every
untransformed metric is byte-identical. `invertDisplayTransform` — the
entry boundary — keeps full precision, because a value rounded on the way
out and inverted on the way back would drift the stored canonical number
a little further on every save; the entry surfaces quantise to their own
two-decimal canonical dialect instead.

`applyDisplayTransformUnrounded` is the one documented escape hatch, for
arithmetic whose result is not rendered. The target adapter needs it: its
guardrails round inward, and rounding the 30 kg floor to the nearest
tenth of a pound first gives an edge that inverts to 29.98 kg and the
server rejects it.

The weight page and the HealthKit scaffold were folding the factor into
their stat strips by hand. Both now convert through the helper, so the
raw multiplier is only ever handed to the chart, which owns its own
series and formatting.

Formatting is unchanged. Precision is fixed at the conversion and the
surfaces keep the formatter they already used, so switching preference
changes the unit and nothing else about how the number looks.

Refs #627
This commit is contained in:
Marc-André Bombeck committed 2026-07-26 00:46:03 +02:00
1 parent 708846f72b
commit 84d78d2926
6 files changed
+496 -49

No files matched your search

@@ -288,26 +288,24 @@ export function HealthKitMetricPage({
// min / max / median / mean so the numbers and the unit agree. All four are
// ABSOLUTE values, so they take the offset; an untransformed metric keeps
// scale 1 / offset 0 → byte-identical.
// v1.32.39 — a transformed type converts through `toDisplay`, which rounds
// to the transform's own decimals. Hand-rolling `value * scale + offset`
// here is what let a converted stat reach the strip unrounded; the raw
// scale below is now only ever handed to the chart, which folds it into its
// own series and formats at its own precision.
const toStripValue = (value: number) =>
transform
? unitDisplay.toDisplay(effectiveType, value)
: value * resolvedScale + resolvedOffset;
const summary =
rawSummary && (resolvedScale !== 1 || resolvedOffset !== 0)
? {
...rawSummary,
min:
rawSummary.min === null
? null
: rawSummary.min * resolvedScale + resolvedOffset,
max:
rawSummary.max === null
? null
: rawSummary.max * resolvedScale + resolvedOffset,
mean:
rawSummary.mean === null
? null
: rawSummary.mean * resolvedScale + resolvedOffset,
min: rawSummary.min === null ? null : toStripValue(rawSummary.min),
max: rawSummary.max === null ? null : toStripValue(rawSummary.max),
mean: rawSummary.mean === null ? null : toStripValue(rawSummary.mean),
median:
rawSummary.median === null
? null
: rawSummary.median * resolvedScale + resolvedOffset,
rawSummary.median === null ? null : toStripValue(rawSummary.median),
}
: rawSummary;