From 2bbffa71daa0cb747b5138a94723d5135a9dd73b Mon Sep 17 00:00:00 2001 From: barckcode Date: Thu, 10 Sep 2026 23:45:17 +0100 Subject: [PATCH 1/5] fix(limits): floor the token display, never round it up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UX non-blocking on #48, and it closes a pair that was left half-done: the member portal guards this with `Math.floor` and the PUBLIC site did not. `maximumFractionDigits: 0` truncates nothing — it ROUNDS. Measured before and after: value before after 1048576 1M 1M 1500000 2M 1M 1900000 2M 1M Harmless at today's window, which rounds down anyway. But this page is read by people who have not paid yet, so it must never advertise MORE than the backend allows — and that is exactly the reasoning already written into cloud-ui's formatContextWindow. The guard belonged on the more public surface too, and I only put it on the private one. `roundingMode: 'floor'`, one option, no change to any published number. The other figures are unaffected: 3,000M / 3.000M and 400M render as before. `npm run build` clean; vitest 727 passed across 50 files. Co-Authored-By: Claude Opus 5 --- src/lib/rateLimits.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/rateLimits.ts b/src/lib/rateLimits.ts index 21ad93e..b2426e3 100644 --- a/src/lib/rateLimits.ts +++ b/src/lib/rateLimits.ts @@ -103,9 +103,17 @@ export function formatTokens(tokens: number, lang: DocsLocale = 'en'): string { // side. Rounding the DISPLAY is the right layer: the published constant // stays equal to the backend, so the test below can keep asserting // equality rather than a floor. + // + // `roundingMode: 'floor'` because this page is read by people who have not + // paid yet, so it must never advertise MORE than the backend allows. + // Truncating alone is not the default: `maximumFractionDigits` ROUNDS, so + // 1.5M would have printed "2M". Harmless at today's 1,048,576, and the + // exact trap the member portal already guards against with Math.floor — + // the guard belonged on the more public surface too. const fmt = new Intl.NumberFormat(lang === 'es' ? 'es-ES' : 'en-US', { useGrouping: 'always', maximumFractionDigits: 0, + roundingMode: 'floor', }); if (tokens >= 1_000_000) return `${fmt.format(tokens / 1_000_000)}M`; if (tokens >= 1_000) return `${fmt.format(tokens / 1_000)}K`; From e54233c3aa43be3aef248451ec2f0bc5f888ad79 Mon Sep 17 00:00:00 2001 From: barckcode Date: Thu, 10 Sep 2026 23:50:57 +0100 Subject: [PATCH 2/5] docs(limits): note that the published window is the provider's total UX follow-up, same sentence as devops#312 and enterprise-api#48. "exactly what the Novita primary serves" was the wrong frame: Novita's `context_size` covers input AND output together, so 1,048,576 is the provider's total budget, not a servable input window. A prompt at the very top leaves no room for the reply, nothing on our side filters for it (`_pre_call_checks` is off), and the caller gets the provider's error on a number this page publishes. Comment only, no published number changes. `npm run build` clean; vitest 727 passed across 50 files. Co-Authored-By: Claude Opus 5 --- src/lib/rateLimits.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/rateLimits.ts b/src/lib/rateLimits.ts index b2426e3..b937e70 100644 --- a/src/lib/rateLimits.ts +++ b/src/lib/rateLimits.ts @@ -67,7 +67,11 @@ export const DEFAULT_RATE_LIMITS: RateLimitsConfig = { // numbers the member portal publishes. // // contextTokens mirrors the backend EXACTLY: 1,048,576, raised from 500,000 - // on 2026-09-10 and exactly what the Novita primary serves. It renders as + // on 2026-09-10. It equals the Novita primary's `context_size`, which + // covers input AND OUTPUT together — so it is the provider's total budget, + // not a servable input window: a prompt at the very top leaves no room for + // the reply. Nothing filters for that on our side, so a caller who uses the + // full figure gets the provider's error on a number this page publishes. It renders as // "1M" because formatTokens rounds the display, which is the layer that // should do it — publishing a rounded 1,000,000 here would leave the site // 4.8% below the source it claims to mirror, and would turn the test below From 0dc2ba77ad1a744cbde40c77d905e9eaefdff00b Mon Sep 17 00:00:00 2001 From: barckcode Date: Fri, 11 Sep 2026 09:29:48 +0100 Subject: [PATCH 3/5] test(limits): assert the floor with a value that can tell it apart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QA blocker, and it is the rule I have been applying all chain turned back on me: a guard that cannot fail is not a guard. `formatTokens(1_048_576)` renders "1M" whether the formatter floors or rounds, so the 727 green tests said nothing about the fix — they pass identically if `roundingMode: 'floor'` is deleted. There was no test anywhere referencing `1_500_000`, `floor` or `roundingMode`. It matters more here than in the member portal, which QA also spotted: `roundingMode` is an Intl.NumberFormat option, and a runtime that does not support it IGNORES IT SILENTLY — no error, just rounding again. So the question is not "is the word still in the source" but "does this runtime actually floor", and only an assert answers that. Verified by removing the option: `expected '2M' to be '1M'`. Restored: green. The second test pins the figures that must NOT change (1,048,576 → 1M, 3,000M / 3.000M, 400M), so a future rounding change cannot quietly move them. Also closed a seam QA found, the third of this shape in the chain: inserting the caveat paragraph left "It renders as" dangling at the end of the new block. The heuristic worth keeping — when you insert a block, read the sentence immediately before and immediately after it. `npm run build` clean; vitest 729 passed across 50 files. Co-Authored-By: Claude Opus 5 --- src/lib/rateLimits.ts | 6 ++++-- src/tests/lib/rateLimits.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/lib/rateLimits.ts b/src/lib/rateLimits.ts index b937e70..175da91 100644 --- a/src/lib/rateLimits.ts +++ b/src/lib/rateLimits.ts @@ -71,8 +71,10 @@ export const DEFAULT_RATE_LIMITS: RateLimitsConfig = { // covers input AND OUTPUT together — so it is the provider's total budget, // not a servable input window: a prompt at the very top leaves no room for // the reply. Nothing filters for that on our side, so a caller who uses the - // full figure gets the provider's error on a number this page publishes. It renders as - // "1M" because formatTokens rounds the display, which is the layer that + // full figure gets the provider's error on a number this page publishes. + // + // It renders as "1M" because formatTokens rounds the display down, which is + // the layer that // should do it — publishing a rounded 1,000,000 here would leave the site // 4.8% below the source it claims to mirror, and would turn the test below // from an equality into a floor. The equality is the mechanism: it is what diff --git a/src/tests/lib/rateLimits.test.ts b/src/tests/lib/rateLimits.test.ts index 3bc56ea..a29a300 100644 --- a/src/tests/lib/rateLimits.test.ts +++ b/src/tests/lib/rateLimits.test.ts @@ -6,6 +6,7 @@ import { windowedModelBody, windowedModelHeadline, windowedModelNote, + formatTokens, } from '../../lib/rateLimits'; /** @@ -32,6 +33,30 @@ const GLM = { periodCapTokens: 3_000_000_000, }; +describe('formatTokens rounds DOWN', () => { + // 1_500_000, not the live 1_048_576: the live value renders "1M" whether + // the formatter floors or rounds, so an assert on it cannot fail if the + // guard is removed. This is the smallest value that tells them apart. + // + // It matters more here than in the member portal. `roundingMode` is an + // Intl.NumberFormat option, and a runtime that does not support it IGNORES + // IT SILENTLY — no error, just rounding again. So this is not "is the word + // still in the source", it is "does this runtime actually floor", and only + // an assert can answer that. + it('never advertises more than the backend allows', () => { + expect(formatTokens(1_500_000)).toBe('1M'); + expect(formatTokens(1_900_000)).toBe('1M'); + expect(formatTokens(1_500_000, 'es')).toBe('1M'); + }); + + it('leaves the other published figures untouched', () => { + expect(formatTokens(1_048_576)).toBe('1M'); + expect(formatTokens(3_000_000_000)).toBe('3,000M'); + expect(formatTokens(3_000_000_000, 'es')).toBe('3.000M'); + expect(formatTokens(400_000_000)).toBe('400M'); + }); +}); + describe('rateLimits — glm5.3 windowed limits', () => { const glm = DEFAULT_RATE_LIMITS.windowedModels.find((m) => m.model === 'glm5.3'); From d29352946f42198b3739a63348fe6ceb5ff5517e Mon Sep 17 00:00:00 2001 From: barckcode Date: Fri, 11 Sep 2026 09:30:41 +0100 Subject: [PATCH 4/5] style(limits): reflow the comment left ragged by the previous insert --- src/lib/rateLimits.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/rateLimits.ts b/src/lib/rateLimits.ts index 175da91..9cbf23b 100644 --- a/src/lib/rateLimits.ts +++ b/src/lib/rateLimits.ts @@ -74,10 +74,9 @@ export const DEFAULT_RATE_LIMITS: RateLimitsConfig = { // full figure gets the provider's error on a number this page publishes. // // It renders as "1M" because formatTokens rounds the display down, which is - // the layer that - // should do it — publishing a rounded 1,000,000 here would leave the site - // 4.8% below the source it claims to mirror, and would turn the test below - // from an equality into a floor. The equality is the mechanism: it is what + // the layer that should do it. Publishing a rounded 1,000,000 here instead + // would leave the site 4.8% below the source it claims to mirror, and would + // turn the test below from an equality into a floor. The equality is the mechanism: it is what // makes a backend change fail here instead of shipping quietly. windowedModels: [ { From a54fddebaf5bfd742b188ea6c0ee50d59e0ce128 Mon Sep 17 00:00:00 2001 From: barckcode Date: Fri, 11 Sep 2026 09:31:34 +0100 Subject: [PATCH 5/5] style(limits): wrap the last over-long comment line --- src/lib/rateLimits.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/rateLimits.ts b/src/lib/rateLimits.ts index 9cbf23b..5add960 100644 --- a/src/lib/rateLimits.ts +++ b/src/lib/rateLimits.ts @@ -76,8 +76,9 @@ export const DEFAULT_RATE_LIMITS: RateLimitsConfig = { // It renders as "1M" because formatTokens rounds the display down, which is // the layer that should do it. Publishing a rounded 1,000,000 here instead // would leave the site 4.8% below the source it claims to mirror, and would - // turn the test below from an equality into a floor. The equality is the mechanism: it is what - // makes a backend change fail here instead of shipping quietly. + // turn the test below from an equality into a floor. The equality is the + // mechanism: it is what makes a backend change fail here instead of + // shipping quietly. windowedModels: [ { model: 'glm5.3',