fix(ui): keep clampText/truncateText surrogate-safe at emoji boundaries#102225
Conversation
clampText and truncateText in format.ts used String.prototype.slice(0, N) to truncate user-visible text. When the truncation boundary falls inside a UTF-16 surrogate pair (emoji, CJK extended, etc.), the resulting string contains a dangling surrogate that browsers render as U+FFFD (�). Replace .slice(0, ...) with truncateUtf16Safe(...) which skips past incomplete surrogate pairs so the truncation point always falls on a code-point boundary. Affected UI surfaces: markdown rendering, command descriptions, skill summaries, exec-approval rules, tool-stream output previews. Add surrogate-safe truncation test cases for both functions using a real emoji at the exact truncation boundary.
|
Codex review: needs maintainer review before merge. Reviewed July 8, 2026, 11:40 PM ET / 03:40 UTC. Summary PR surface: Source +1, Tests +30. Total +31 across 2 files. Reproducibility: yes. Source inspection of current main shows both shared UI formatters slice by UTF-16 code unit, and the PR adds exact emoji-boundary inputs that would catch the malformed-output case. Review metrics: none identified. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Next step before merge
Security Review detailsBest possible solution: Land the focused formatter change after maintainer review, keeping Do we have a high-confidence way to reproduce the issue? Yes. Source inspection of current main shows both shared UI formatters slice by UTF-16 code unit, and the PR adds exact emoji-boundary inputs that would catch the malformed-output case. Is this the best way to solve the issue? Yes. Reusing the existing dependency-free AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 003bb8284bea. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +1, Tests +30. Total +31 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
🦞✅ Reason: structured ClawSweeper close marker: close-required (sha=ed138d7b03edd2d4513cfad57a584a376284ea9c) Closed:
|
|
Reopening for landing because the automated close rationale is contradicted by current source and by its own review evidence.
The branch is not superseded by #101711 and is ready to land. |
|
@clawsweeper stop Manual maintainer landing is in progress. Pause all automated review, close, repair, and merge actions for this PR. |
|
🦞✅ I added |
|
Merged via squash.
|
…es (openclaw#102225) * fix(ui): keep clampText/truncateText surrogate-safe at emoji boundaries clampText and truncateText in format.ts used String.prototype.slice(0, N) to truncate user-visible text. When the truncation boundary falls inside a UTF-16 surrogate pair (emoji, CJK extended, etc.), the resulting string contains a dangling surrogate that browsers render as U+FFFD (�). Replace .slice(0, ...) with truncateUtf16Safe(...) which skips past incomplete surrogate pairs so the truncation point always falls on a code-point boundary. Affected UI surfaces: markdown rendering, command descriptions, skill summaries, exec-approval rules, tool-stream output previews. Add surrogate-safe truncation test cases for both functions using a real emoji at the exact truncation boundary. * test(ui): tighten UTF-16 truncation coverage --------- Co-authored-by: Peter Steinberger <[email protected]>
What Problem This Solves
The shared Control UI
clampTextandtruncateTexthelpers used raw UTF-16 code-unit slicing. If a limit fell between an astral character's surrogate halves, rendered markdown, tool/activity previews, skill descriptions, or execution-approval text could contain a lone surrogate.Why This Change Was Made
Both shared formatters now delegate their cut to the repository's canonical
truncateUtf16Safehelper. Short text, ordinary ASCII truncation, ellipsis placement, andtruncateTextmetadata remain unchanged.The maintainer fixup also corrected the original
clampTextfixture: its emoji began immediately after the old end-exclusive slice boundary, so that test passed before the production fix. The replacement places the high surrogate inside the cut and asserts exact output; 64 lines of manual surrogate scanning were removed.User Impact
Control UI truncation no longer produces malformed text when a boundary crosses an emoji or another astral character.
Evidence
clampTextregression: 118 ASCII units followed by an emoji and one trailing character at a 120-unit display cap produces 118 ASCII units plus the ellipsis.truncateTextregression: 120 ASCII units followed by an emoji at a 121-unit cap produces 120 ASCII units and retains the original total of 122.git diff --check: pass.28991500251oned138d7b03edd2d4513cfad57a584a376284ea9c: success (57 checks).AI-assisted: yes. Maintainer-reviewed and tightened.