From fc6944951b4bb53d64965a0d1930779b4e57e45b Mon Sep 17 00:00:00 2001 From: quanticsoul4772 <71096528+quanticsoul4772@users.noreply.github.com> Date: Mon, 9 Nov 2026 05:35:23 -0800 Subject: [PATCH] refactor(fires): read both age formatters' unit from one helper formatAge and formatAgeLabel repeated the validity guard, the <1h case, the rounding and the 48-hour switch, which is why the rounding fix had to land twice. Both now read { unit, count } from a private ageParts, so they cannot pick different units again. Output is unchanged. Co-Authored-By: Claude Opus 5.5 --- src/layers/firms/model.js | 36 +++++++++++++++++++++------------ src/layers/firms/model.test.mjs | 19 +++++++++++++++++ 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/layers/firms/model.js b/src/layers/firms/model.js index 4f12593f2..00873c2b2 100644 --- a/src/layers/firms/model.js +++ b/src/layers/firms/model.js @@ -535,15 +535,26 @@ export function createModel({ return `${isolate(latPart)} ${isolate(lonPart)}`; } + /** + * Millisecond delta → `{ unit: '<1h' | 'h' | 'd', count }`, or null for + * invalid input. Both age formatters read it, so they pick the same unit. + */ + + function ageParts(deltaMs) { + if (!Number.isFinite(deltaMs) || deltaMs < 0) return null; + const hours = deltaMs / 3600000; + if (hours < 1) return { unit: '<1h', count: 0 }; + const wholeHours = Math.round(hours); + if (wholeHours < 48) return { unit: 'h', count: wholeHours }; + return { unit: 'd', count: Math.round(hours / 24) }; + } + /** Millisecond delta → "<1h" / "Xh" / "Xd", or '' for invalid input. */ function formatAge(deltaMs) { - if (!Number.isFinite(deltaMs) || deltaMs < 0) return ''; - const hours = deltaMs / 3600000; - if (hours < 1) return '<1h'; - const wholeHours = Math.round(hours); - if (wholeHours < 48) return `${wholeHours}h`; - return `${Math.round(hours / 24)}d`; + const age = ageParts(deltaMs); + if (!age) return ''; + return age.unit === '<1h' ? '<1h' : `${age.count}${age.unit}`; } /** Millisecond delta → "<1m ago" / "Xm ago" / "Xh ago" (fresh-feed readout). */ @@ -574,13 +585,12 @@ export function createModel({ /** formatAge for a card: "<1h", "24h", "3d" in the UI language ("24 ч"). */ function formatAgeLabel(deltaMs) { - if (!Number.isFinite(deltaMs) || deltaMs < 0) return ''; - const hours = deltaMs / 3600000; - if (hours < 1) return t('<1h', { context: 'age' }); - const wholeHours = Math.round(hours); - if (wholeHours < 48) - return t('{count}h', { context: 'age', count: wholeHours }); - return t('{count}d', { context: 'age', count: Math.round(hours / 24) }); + const age = ageParts(deltaMs); + if (!age) return ''; + if (age.unit === '<1h') return t('<1h', { context: 'age' }); + return age.unit === 'h' + ? t('{count}h', { context: 'age', count: age.count }) + : t('{count}d', { context: 'age', count: age.count }); } /** Normalized 0..1 confidence → low/nominal/high display bucket. */ diff --git a/src/layers/firms/model.test.mjs b/src/layers/firms/model.test.mjs index a0df2eb2c..2c42227f8 100644 --- a/src/layers/firms/model.test.mjs +++ b/src/layers/firms/model.test.mjs @@ -24,3 +24,22 @@ test('a fire age that rounds to 48 hours reads in days, as 48 hours does', () => setLocale(previousLocale); } }); + +test('both fire age formatters agree on invalid, sub-hour and multi-day ages', () => { + const previousLocale = getLocale(); + const { formatAge, formatAgeLabel } = createModel({ + services: { anchors: {} }, + }); + try { + setLocale('en'); + for (const format of [formatAge, formatAgeLabel]) { + assert.equal(format(-1), ''); + assert.equal(format(Number.NaN), ''); + assert.equal(format(0.5 * HOUR_MS), '<1h'); + assert.equal(format(1 * HOUR_MS), '1h'); + assert.equal(format(72 * HOUR_MS), '3d'); + } + } finally { + setLocale(previousLocale); + } +});