Skip to content

fix(ui): keep event payload previews UTF-16 safe#102625

Merged
steipete merged 4 commits into
openclaw:mainfrom
zhangguiping-xydt:fix/problem-utf16-event-payload-preview
Jul 9, 2026
Merged

fix(ui): keep event payload previews UTF-16 safe#102625
steipete merged 4 commits into
openclaw:mainfrom
zhangguiping-xydt:fix/problem-utf16-event-payload-preview

Conversation

@zhangguiping-xydt

@zhangguiping-xydt zhangguiping-xydt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

The Control UI Overview event log created its 120-code-unit payload preview with raw UTF-16 slicing. If the boundary landed between the halves of an emoji or another supplementary character, the rendered preview could contain a lone surrogate.

Why This Change Was Made

  • The Overview event log now renders formatted payload previews through the Control UI's existing truncateText helper.
  • truncateText delegates to the canonical @openclaw/normalization-core/utf16-slice truncator, so the UI keeps one UTF-16-safe truncation path instead of a presenter-specific adapter.
  • Payload storage, API/schema behavior, full debug payload rendering, and the existing 120-code-unit preview cap are unchanged.

User Impact

Long Overview event payload previews remain bounded and no longer display malformed emoji or replacement characters when payload text contains astral Unicode characters near the preview boundary.

Evidence

  • Freshness gate: latest origin/main at 8d6c54f513136ff204c5e254c2b33872cd4ea0d9 still renders formatEventPayload(entry.payload).slice(0, 120) in ui/src/pages/overview/event-log.ts.
  • Branch head verified: 5accd09a8e5d7c09a45a893954c7b325bcdca77d.
  • GitHub status check rollup for the head SHA: 77 items, 0 non-ok.
  • REST label/timeline verification shows proof: sufficient, rating: 🦞 diamond lobster, and status: 👀 ready for maintainer look were applied by clawsweeper[bot].

Real behavior proof

  • Behavior or issue addressed: Overview event payload previews could split an emoji surrogate pair at the 120-code-unit boundary.
  • Environment: Node v24.15.0, OpenClaw package v2026.6.11, PR head 5accd09a8e5d7c09a45a893954c7b325bcdca77d.
  • Steps exercised: started the Control UI Vite runtime, installed the mock Gateway WebSocket, opened /overview in Chromium, emitted an agent.message Gateway event whose legacy 120-code-unit preview ends on a high surrogate, then read .ov-event-log-payload from the DOM.
  • Observed result after fix:
{
  "browserPath": "/overview",
  "legacySliceLastCodeUnit": 55357,
  "legacyEndsWithHighSurrogate": true,
  "previewLength": 119,
  "previewLastCodeUnit": 97,
  "previewEndsWithHighSurrogate": false,
  "previewTail": "aaaaaaaaaaaaaaaaaaaaaaaa"
}

The legacy raw slice still demonstrates the bad boundary, while the rendered Overview DOM preview backs off to 119 code units and ends on ordinary a (97), not a dangling surrogate.

Focused checks

$ corepack [email protected] test ui/src/pages/overview/event-log.test.ts
Test Files  1 passed (1)
     Tests  1 passed (1)

$ node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.test.ui.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/test-ui.tsbuildinfo
exit 0

$ corepack [email protected] exec oxfmt --check ui/src/pages/overview/event-log.ts ui/src/pages/overview/event-log.test.ts
Checking formatting...
All matched files use the correct format.

$ corepack [email protected] exec oxlint --deny=warn --ignore-path=.oxlintignore ui/src/pages/overview/event-log.ts ui/src/pages/overview/event-log.test.ts
exit 0

$ git diff --check
exit 0

Regression Test Plan

  • Coverage level: focused UI renderer regression test plus browser runtime proof.
  • Target test file: ui/src/pages/overview/event-log.test.ts.
  • Scenario locked in: rendering an Overview event payload whose legacy 120-code-unit preview ends on the high surrogate of an emoji.
  • Why this is the smallest reliable guardrail: it exercises the same Overview renderer and payload formatting boundary without changing event storage or debug payload rendering.

Root Cause

  • Root cause: the Overview event log rendered formatEventPayload(entry.payload).slice(0, 120), which truncates by raw UTF-16 code units and can leave a dangling surrogate when the boundary falls inside an emoji.
  • Missing guardrail: there was no Overview regression test covering surrogate-pair truncation at the preview boundary.

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 9, 2026, 9:36 AM ET / 13:36 UTC.

Summary
The PR changes the Control UI Overview event-log payload preview to use truncateText(formatEventPayload(...), 120).text and adds a jsdom regression test for an emoji surrogate boundary.

PR surface: Source 0, Tests +39. Total +39 across 2 files.

Reproducibility: yes. at source level. Current main uses formatEventPayload(...).slice(0, 120), and the boundary payload pattern makes that raw slice end on high surrogate 0xd83d; I did not run the full browser UI in this read-only review.

Review metrics: none identified.

Root-cause cluster
Relationship: canonical
Canonical: #102625
Summary: This PR is the strongest open landing path for the Overview event-log payload preview surrogate-boundary bug.

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
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

  • No ClawSweeper repair lane is needed; the remaining path is maintainer review plus exact-head or merge-commit landing checks.

Security
Cleared: The diff only changes Control UI text rendering and a focused jsdom test; it does not touch dependencies, workflows, secrets, package metadata, or script execution.

Review details

Best possible solution:

Land this focused PR after maintainer review and exact-head or merge-commit checks, keeping full Debug payload rendering unchanged and using the shared safe truncation helper for the Overview preview.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level. Current main uses formatEventPayload(...).slice(0, 120), and the boundary payload pattern makes that raw slice end on high surrogate 0xd83d; I did not run the full browser UI in this read-only review.

Is this the best way to solve the issue?

Yes. Reusing truncateText at the narrow Overview preview call site is the best layer because the shared helper already delegates to the canonical UTF-16-safe slicer while Debug keeps full payload rendering.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 474d660137d0.

Label changes

Label justifications:

  • P2: This is a focused Control UI text-rendering bug with clear source proof, regression coverage, sufficient real behavior proof, and limited blast radius.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes after-fix /overview browser DOM output showing the legacy slice ending on a high surrogate while the rendered preview backs off safely, plus focused check output.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix /overview browser DOM output showing the legacy slice ending on a high surrogate while the rendered preview backs off safely, plus focused check output.
Evidence reviewed

PR surface:

Source 0, Tests +39. Total +39 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 2 2 0
Tests 1 39 0 +39
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 41 2 +39

What I checked:

  • AGENTS policy read: Root AGENTS.md and scoped ui/AGENTS.md were read fully; their high-confidence review, scoped UI, proof, and security guidance applied to this review. (AGENTS.md:1, 474d660137d0)
  • Current main still has the bug: The current Overview event-log renderer formats the payload and then uses raw .slice(0, 120), which can leave a dangling surrogate at the preview boundary. (ui/src/pages/overview/event-log.ts:35, 474d660137d0)
  • PR implementation uses the shared safe helper: At PR head, the same renderer imports truncateText and renders truncateText(formatEventPayload(entry.payload), 120).text only for the Overview preview. (ui/src/pages/overview/event-log.ts:35, 042de6a45e54)
  • Helper contract is already UTF-16 safe: truncateText delegates its cut to truncateUtf16Safe, and the low-level slicer backs off when the end boundary lands between surrogate halves. (ui/src/lib/format.ts:117, 474d660137d0)
  • Regression test covers the exact boundary: The added test constructs a formatted payload whose old 120-code-unit slice ends on high surrogate 0xd83d, renders the Overview event log, and asserts the DOM preview backs off safely. (ui/src/pages/overview/event-log.test.ts:15, 042de6a45e54)
  • Sibling surface remains unchanged: The Debug event log still renders the full formatted payload, so the truncation change is scoped to the bounded Overview preview. (ui/src/pages/debug/view.ts:145, 474d660137d0)

Likely related people:

  • steipete: Current blame for the Overview event-log preview and shared format helper lines points to 2806195e11277b8cb270212f2b0c293ae18f2234, and the final PR-head commit refactors the patch to reuse truncateText. (role: recent area contributor and branch fixup author; confidence: high; commits: 2806195e1127, 042de6a45e54; files: ui/src/pages/overview/event-log.ts, ui/src/lib/format.ts)
  • zw-xysk: Authored the merged helper PR that made clampText and truncateText surrogate-safe, which is the helper path reused by this PR. (role: adjacent helper contributor; confidence: high; commits: 57db713ff73f; files: ui/src/lib/format.ts, ui/src/lib/format.test.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.
Review history (7 earlier review cycles)
  • reviewed 2026-07-09T09:50:45.118Z sha 2bf3d0a :: needs changes before merge. :: [P1] Use the canonical UTF-16 truncator
  • reviewed 2026-07-09T10:16:58.857Z sha 31e0767 :: needs changes before merge. :: [P1] Import the truncator from an exported module
  • reviewed 2026-07-09T10:46:52.264Z sha 5b5ebe9 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T10:53:28.613Z sha 5b5ebe9 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T11:54:25.900Z sha 5accd09 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T12:01:00.230Z sha 5accd09 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-09T12:27:03.025Z sha 5accd09 :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. labels Jul 9, 2026
@zhangguiping-xydt

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 9, 2026
@zhangguiping-xydt

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 9, 2026
@zhangguiping-xydt
zhangguiping-xydt force-pushed the fix/problem-utf16-event-payload-preview branch from 5b5ebe9 to 25a9f11 Compare July 9, 2026 11:32
@steipete steipete self-assigned this Jul 9, 2026
@steipete
steipete force-pushed the fix/problem-utf16-event-payload-preview branch from 5accd09 to 042de6a Compare July 9, 2026 13:05
@openclaw-barnacle openclaw-barnacle Bot added channel: googlechat Channel integration: googlechat channel: msteams Channel integration: msteams commands Command implementations labels Jul 9, 2026
@steipete
steipete force-pushed the fix/problem-utf16-event-payload-preview branch from ce845c1 to 0aa1634 Compare July 9, 2026 13:51
@openclaw-barnacle openclaw-barnacle Bot added size: XS and removed channel: googlechat Channel integration: googlechat channel: msteams Channel integration: msteams commands Command implementations agents Agent runtime and tooling channel: qqbot extensions: codex size: XL labels Jul 9, 2026
@steipete
steipete force-pushed the fix/problem-utf16-event-payload-preview branch from 0aa1634 to 9bcdd65 Compare July 9, 2026 13:52
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Land-ready proof for 9bcdd658796847048e5e0d33da1e949ee9e9610b (tree 65916c08bb31740e782824578862a75b6e68abe4):

No provider/OAI-key lane is relevant to this deterministic presentation-only invariant.

@steipete
steipete merged commit 763ef21 into openclaw:main Jul 9, 2026
71 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 10, 2026
* fix(ui): keep event payload previews UTF-16 safe

* chore: use shared utf16 truncator in presenter

* fix(ui): use canonical utf16 truncator import

* refactor(ui): reuse canonical text truncation

---------

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 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 status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants