refactor(ui): name the head budget and the truncation suffix

Addresses the budget comment on #1058 by the second route the review
offered -- defining the constant as the head budget rather than reserving
the suffix inside it.

TOAST_ERROR_BUDGET read as though it bounded `display`, but `display` is
the head plus " …", so it could be 402. Renamed to HEAD_BUDGET and
documented as bounding the message rather than the rendered string, with
the suffix now a named constant instead of a literal in the template.

Reserving the two characters was the alternative, but nothing downstream
has a hard limit -- the description box scrolls -- so it would have
shortened the message to satisfy a round number.

Verified: head <= 400 and display <= 402 on every truncating input,
including no-space text, a short first line, sentence-boundary backoff
and the real 4795-char error, with the untouched-when-not-truncated and
exact-`full` invariants still holding.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Tomas Rimkus
2026-10-04 00:00:15 +00:00
committed by capy-ai-staging[bot]
co-authored by Claude Opus 5
parent e3b9c98977
commit 8086c818b6
+18 -8
View File
@@ -12,9 +12,19 @@
* budget, and report how much was dropped so nobody assumes they read it all. * budget, and report how much was dropped so nobody assumes they read it all.
*/ */
/** Characters of an error worth putting in a toast. Roughly the first /** Longest head of an oversized error kept for the toast, before the ellipsis.
* paragraph — enough for a sentence or two of real message. */ *
const TOAST_ERROR_BUDGET = 400; * Roughly the first paragraph — enough for a sentence or two of real message.
* This bounds the *message* rather than the rendered string: `display` is this
* plus `TRUNCATION_SUFFIX` when something was cut. Spending two of these
* characters on the ellipsis instead would shorten the message to no purpose,
* since nothing downstream has a hard character limit — the description box
* scrolls.
*/
const HEAD_BUDGET = 400;
/** Marks a `display` value as incomplete. Appended after the head. */
const TRUNCATION_SUFFIX = ' …';
/** Below this, condensing is not worth it and the whole message is shown. /** Below this, condensing is not worth it and the whole message is shown.
* *
@@ -25,7 +35,7 @@ const TOAST_ERROR_BUDGET = 400;
* around a single character. No Copy action is offered in this range because * around a single character. No Copy action is offered in this range because
* nothing is being withheld: `display` already holds the entire message. * nothing is being withheld: `display` already holds the entire message.
*/ */
const MIN_TO_CONDENSE = TOAST_ERROR_BUDGET + 120; const MIN_TO_CONDENSE = HEAD_BUDGET + 120;
export interface CondensedError { export interface CondensedError {
/** What to show in the toast. /** What to show in the toast.
@@ -60,16 +70,16 @@ export function condenseError(raw: string | null | undefined): CondensedError {
// it fits, since a newline is a stronger boundary than any punctuation. // it fits, since a newline is a stronger boundary than any punctuation.
const firstLine = text.split('\n', 1)[0].trim(); const firstLine = text.split('\n', 1)[0].trim();
let head = let head =
firstLine.length > 0 && firstLine.length <= TOAST_ERROR_BUDGET firstLine.length > 0 && firstLine.length <= HEAD_BUDGET
? firstLine ? firstLine
: text.slice(0, TOAST_ERROR_BUDGET); : text.slice(0, HEAD_BUDGET);
if (head.length < text.length && head === text.slice(0, head.length)) { if (head.length < text.length && head === text.slice(0, head.length)) {
// Back off to the last sentence end inside the budget so the text does not // Back off to the last sentence end inside the budget so the text does not
// stop mid-word. Only accept it if it keeps most of the budget — otherwise // stop mid-word. Only accept it if it keeps most of the budget — otherwise
// a stray early period would throw away usable context. // a stray early period would throw away usable context.
const lastStop = Math.max(head.lastIndexOf('. '), head.lastIndexOf('? ')); const lastStop = Math.max(head.lastIndexOf('. '), head.lastIndexOf('? '));
if (lastStop > TOAST_ERROR_BUDGET * 0.4) { if (lastStop > HEAD_BUDGET * 0.4) {
head = head.slice(0, lastStop + 1); head = head.slice(0, lastStop + 1);
} }
} }
@@ -83,5 +93,5 @@ export function condenseError(raw: string | null | undefined): CondensedError {
return { display: full, full, truncated: false, omitted: 0 }; return { display: full, full, truncated: false, omitted: 0 };
} }
return { display: `${head} …`, full, truncated: true, omitted }; return { display: `${head}${TRUNCATION_SUFFIX}`, full, truncated: true, omitted };
} }