Skip to content

diagnostics: the stack summary still cuts entry.text on a UTF-16 unit, so the report can show U+FFFD #1919

Closed

Description

@HugoXK

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, '&lt;')}</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 via text.js like 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 own diagnostics.js (regenerate; check + canonical must 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions