From 5a7a15e1a56c73790f024ecd270c742c2664171e Mon Sep 17 00:00:00 2001 From: kanywst Date: Thu, 7 May 2026 02:06:40 +0900 Subject: [PATCH 1/2] fix(format): round star counts above 999.5k up to M instead of '1000.0k' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The branch-by-magnitude implementation merged in #9 had a rounding gap: 999_999 satisfies the >= 1_000 branch but not >= 1_000_000, so `(999_999 / 1_000).toFixed(1)` produced '1000.0' and the function returned '1000.0k' — the exact failure mode this formatter was introduced to prevent. Delegate to Intl.NumberFormat with compact notation, which handles unit transitions correctly (999_999 -> '1M'). Patch the 'K' suffix to lowercase so the design keeps small-k thousands / capital-M millions. Test asserts the edge case directly. --- src/lib/format.test.ts | 8 ++++++-- src/lib/format.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/lib/format.test.ts b/src/lib/format.test.ts index 49eb7b5..3f67ece 100644 --- a/src/lib/format.test.ts +++ b/src/lib/format.test.ts @@ -9,12 +9,16 @@ describe('formatCompactStars', () => { }); it('formats four-to-six digit counts with k', () => { - expect(formatCompactStars(1_000)).toBe('1.0k'); + expect(formatCompactStars(1_000)).toBe('1k'); expect(formatCompactStars(12_345)).toBe('12.3k'); }); + it('rounds counts in the 999.5k+ window up to M instead of producing 1000k', () => { + expect(formatCompactStars(999_999)).toBe('1M'); + }); + it('formats seven-digit counts with M', () => { - expect(formatCompactStars(1_000_000)).toBe('1.0M'); + expect(formatCompactStars(1_000_000)).toBe('1M'); expect(formatCompactStars(1_500_000)).toBe('1.5M'); }); }); diff --git a/src/lib/format.ts b/src/lib/format.ts index e9b57af..7b0296d 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -1,5 +1,9 @@ +const COMPACT_FORMATTER = new Intl.NumberFormat('en', { + notation: 'compact', + maximumFractionDigits: 1, +}); + +// Intl emits "K" for thousands; the design uses lowercase k while keeping M. export function formatCompactStars(count: number): string { - if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`; - if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k`; - return count.toString(); + return COMPACT_FORMATTER.format(count).replace('K', 'k'); } From 1f1220366de725cca91a35666eb7d92c28f2b229 Mon Sep 17 00:00:00 2001 From: kanywst Date: Thu, 7 May 2026 02:11:08 +0900 Subject: [PATCH 2/2] fix(format): replace all 'K' occurrences globally for ICU-variant safety Per code review feedback. `.replace('K', 'k')` only swaps the first match; while the current 'en' compact output only contains one, future locale or ICU versions could emit more than one (or position it differently). Use `/K/g` so the suffix normalization stays robust. --- src/lib/format.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/format.ts b/src/lib/format.ts index 7b0296d..bd95c3a 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -4,6 +4,8 @@ const COMPACT_FORMATTER = new Intl.NumberFormat('en', { }); // Intl emits "K" for thousands; the design uses lowercase k while keeping M. +// Use a global regex so future locale or ICU variants that produce more than +// one "K" still get normalized. export function formatCompactStars(count: number): string { - return COMPACT_FORMATTER.format(count).replace('K', 'k'); + return COMPACT_FORMATTER.format(count).replace(/K/g, 'k'); }