Repository navigation
diagnostics: the stack summary still cuts entry.text on a UTF-16 unit, so the report can show U+FFFD #1919
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 876dfee430e04177bc605e408d386a87
Branch: dev/2-diagnostics-stack-summary-cut
Base: f87fceb
At: 2026-10-31T10:17:11Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
Dedupe (checked on live main f87fceb0 before opening): #1916 / #1917 fixed nine bounds and deliberately left this line out (its issue lists only meetings.js:30 as "next round"); issue search for diagnostics stack summary returns nothing that covers it — the only hit is #1918, which is clip's third argument.
Problem
The last code-unit bound in core/src/host/diagnostics.js is the stack summary inside the browser-log report:
// core/src/host/diagnostics.js:87
for (const entry of stacks) out.push('', `<details><summary>Stack: ${entry.text.slice(0, 80).replace(/</g, '<')}</summary>`, '', '```', entry.stack, '```', '</details>')entry.text.slice(0, 80) cuts on a UTF-16 unit, so a bound that lands between the halves of a surrogate pair makes the <summary> line ill-formed. The line goes into the diagnostics report the owner copies out of the UI, so a following UTF-8 encode turns the half pair into U+FFFD — the same failure #1908/#1912/#1916 removed everywhere else.
#1916 fixed :211 (pick) and :220 (the browser-log row) in the same file and skipped this one only because its nine-point scope was already frozen.
Reproduction
Feed a browser-log entry whose text spends the 80-unit bound inside an emoji (e.g. 79 ASCII characters followed by an emoji), take the report from diagnostics, and read the <details><summary>Stack: … line: isWellFormed() is false today. Base f87fceb0 reproduces it; the rest of the report is unaffected.
Expected
- The summary line goes through
cut(entry.text, 80).text(imported from../shared/text-boundary.js, or viatext.jslike the other call sites in this file), so it never ends mid-pair; the escape of<stays. - Nothing else changes: same 80-unit display bound, same
<details>shape, and the full stack body below the summary is still printed verbatim.
Scope
core/src/host/diagnostics.js— one expression.core/test/diagnostics.test.js— red-first case.- Mirror
patches/ds-bot/files that owndiagnostics.js(regenerate;check+canonicalmust stay green).
Red first
With the source at f87fceb0 and only the new test in place: an entry whose text spends the 80-unit bound inside a surrogate pair produces a <summary> line with isWellFormed() === true, length <= 80 for the text part, and the kept prefix a byte-for-byte UTF-8 prefix of the input (not "exactly 80" — a cut that lands on a whole character keeps it); the escape of < and the full stack body are asserted in the same case.
Not in scope
- Grapheme-aware cutting (separate follow-up).
#1918(clip's optional third argument) — separate issue, separate branch.
Gates
Run verify with TZ=UTC against live main f87fceb0 (or the live main at PR time), keep patches check + canonical green and generate a no-op after the rewrite, and put Fixes #1919 plus a checkpoint with claim: "Dev" / Owner: Dev in the PR body.
Activity
diagnostics: the stack-summary test pins only the upper bound, so a w…