Skip to content

fix(ui): keep clampText/truncateText surrogate-safe at emoji boundaries#102225

Merged
steipete merged 2 commits into
openclaw:mainfrom
zw-xysk:fix/format-emoji-safe
Jul 9, 2026
Merged

fix(ui): keep clampText/truncateText surrogate-safe at emoji boundaries#102225
steipete merged 2 commits into
openclaw:mainfrom
zw-xysk:fix/format-emoji-safe

Conversation

@zw-xysk

@zw-xysk zw-xysk commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

The shared Control UI clampText and truncateText helpers 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 truncateUtf16Safe helper. Short text, ordinary ASCII truncation, ellipsis placement, and truncateText metadata remain unchanged.

The maintainer fixup also corrected the original clampText fixture: 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

  • Exact clampText regression: 118 ASCII units followed by an emoji and one trailing character at a 120-unit display cap produces 118 ASCII units plus the ellipsis.
  • Exact truncateText regression: 120 ASCII units followed by an emoji at a 121-unit cap produces 120 ASCII units and retains the original total of 122.
  • Short-text and ordinary ASCII behavior remain covered.
  • Targeted oxfmt and git diff --check: pass.
  • Fresh maintainer autoreview: clean, confidence 0.98.
  • Exact-head CI run 28991500251 on ed138d7b03edd2d4513cfad57a584a376284ea9c: success (57 checks).

AI-assisted: yes. Maintainer-reviewed and tightened.

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.
@clawsweeper

clawsweeper Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 8, 2026, 11:40 PM ET / 03:40 UTC.

Summary
This PR replaces raw slice truncation in Control UI clampText and truncateText with truncateUtf16Safe and adds emoji-boundary regression coverage.

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
Relationship: canonical
Canonical: #102225
Summary: This PR is the canonical remaining fix for the shared Control UI formatter surrogate-boundary gap; the merged related PR fixed adjacent truncation sites but not this file.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • A maintainer explicitly paused ClawSweeper and said manual landing is in progress, so no automated repair or cleanup action should run.

Security
Cleared: Security review cleared: the diff imports an existing workspace helper and adds tests, with no dependency, workflow, secret, package, or install-script changes.

Review details

Best possible solution:

Land the focused formatter change after maintainer review, keeping truncateUtf16Safe as the shared UI truncation boundary and retaining the regression tests.

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 truncateUtf16Safe helper in the shared formatter layer is narrower and less drift-prone than fixing each caller separately.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 003bb8284bea.

Label changes

Label changes:

  • remove status: 👀 ready for maintainer look: Current PR status no longer selects a status label.

Label justifications:

  • P2: The PR fixes a normal-priority Control UI text corruption edge with limited blast radius and no emergency availability, security, or data-loss signal.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • proof: sufficient: Contributor real behavior proof is sufficient. Sufficient for this deterministic formatter fix: the PR body and maintainer comment give exact after-fix boundary expectations, and exact-head Real behavior proof plus CI succeeded on the reviewed head.
Evidence reviewed

PR surface:

Source +1, Tests +30. Total +31 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 3 2 +1
Tests 1 30 0 +30
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 33 2 +31

What I checked:

  • Current main still raw-slices the shared UI formatters: clampText and truncateText still call value.slice(0, Math.max(...)) on current main, so the central bug remains present outside this PR. (ui/src/lib/format.ts:113, 003bb8284bea)
  • PR applies the existing safe helper at the shared boundary: The diff imports truncateUtf16Safe and uses it for both formatter cut points, preserving existing max and ellipsis behavior while avoiding dangling surrogate halves. (ui/src/lib/format.ts:111, ed138d7b03ed)
  • Helper contract supports the fix: truncateUtf16Safe floors the limit, keeps short strings unchanged, and delegates to sliceUtf16Safe so truncation cannot return a split surrogate pair. (packages/normalization-core/src/utf16-slice.ts:44, 003bb8284bea)
  • Affected callers are shared Control UI surfaces: Markdown rendering, tool previews, exec-approval text, and skill descriptions call the shared helpers, so fixing the helper layer is the right owner boundary. (ui/src/components/markdown.ts:696, 003bb8284bea)
  • Related merged PR is only partial overlap: fix: preserve emoji at remaining bounded-text edges #101711 merged adjacent UTF-16 boundary fixes, but its file list excludes ui/src/lib/format.ts, so it does not supersede this PR. (87fe266c5e4d)
  • Maintainer paused automation: Live PR comments show a maintainer reopened for landing, then commented that manual maintainer landing is in progress and asked ClawSweeper to stop; the PR now has clawsweeper:human-review. (ed138d7b03ed)

Likely related people:

  • joshavant: Current-main blame and file-add history show commit 4ae8d73 added ui/src/lib/format.ts with the raw-slice helpers and also added the UTF-16 helper file in this checkout history. (role: introduced current-main behavior; confidence: high; commits: 4ae8d735bf54; files: ui/src/lib/format.ts, packages/normalization-core/src/utf16-slice.ts)
  • steipete: steipete authored the merged adjacent UTF-16 boundary consolidation, authored the current PR's test-tightening commit, and reopened this PR with source-backed landing rationale. (role: recent adjacent owner and current fixup author; confidence: high; commits: 87fe266c5e4d, ed138d7b03ed; files: ui/src/lib/format.test.ts, ui/src/lib/chat/commands.ts, ui/src/pages/workboard/view.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jul 8, 2026
@clawsweeper clawsweeper Bot closed this Jul 9, 2026
@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🦞✅
ClawSweeper autoclose is complete.

Reason: structured ClawSweeper close marker: close-required (sha=ed138d7b03edd2d4513cfad57a584a376284ea9c)

Closed:

@steipete steipete reopened this Jul 9, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Reopening for landing because the automated close rationale is contradicted by current source and by its own review evidence.

  • fix: preserve emoji at remaining bounded-text edges #101711 did not change ui/src/lib/format.ts.
  • Current main still uses raw slicing in both shared Control UI formatters.
  • This exact head fixes those two remaining boundaries with the canonical helper.
  • The corrected clampText fixture now fails against current main; the original fixture did not.
  • Exact-head CI run 28991500251 passed all 57 checks; fresh autoreview is clean at confidence 0.98.

The branch is not superseded by #101711 and is ready to land.

@clawsweeper clawsweeper Bot closed this Jul 9, 2026
@steipete steipete added the clawsweeper:human-review Needs maintainer review before ClawSweeper can continue label Jul 9, 2026
@steipete steipete reopened this Jul 9, 2026
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@clawsweeper stop

Manual maintainer landing is in progress. Pause all automated review, close, repair, and merge actions for this PR.

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🦞✅
Got it. ClawSweeper will leave this item for human review.

I added clawsweeper:human-review and paused the automation trail until a maintainer asks again.

@clawsweeper clawsweeper Bot removed the status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. label Jul 9, 2026
@steipete
steipete merged commit 57db713 into openclaw:main Jul 9, 2026
239 of 248 checks passed
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 9, 2026
…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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui clawsweeper:human-review Needs maintainer review before ClawSweeper can continue P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants