Skip to content

fix(ui-quick): use truncateUtf16Safe for settings source truncation#102734

Closed
lzyyzznl wants to merge 2 commits into
openclaw:mainfrom
lzyyzznl:fix/truncateUtf16Safe-ui-eventlog
Closed

fix(ui-quick): use truncateUtf16Safe for settings source truncation#102734
lzyyzznl wants to merge 2 commits into
openclaw:mainfrom
lzyyzznl:fix/truncateUtf16Safe-ui-eventlog

Conversation

@lzyyzznl

@lzyyzznl lzyyzznl commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Problem: formatAssistantAvatarSource in Quick Settings truncates long avatar sources with truncateUtf16Safe(prefix)...oldSlice(suffix). The suffix used String.prototype.slice(-24) which can cut UTF-16 surrogate pairs in half when a multi-byte character (emoji) straddles the suffix boundary, producing orphaned surrogates in the displayed text.

Solution: Replace source.slice(-24) with sliceUtf16Safe(source, -24) from the same @openclaw/normalization-core/utf16-slice package that already provides the safe prefix truncation. The suffix now correctly skips any surrogate pair that would be split at the boundary.

What changed: One line in formatAssistantAvatarSource, the import line, and one regression test file. No runtime behavior change for ASCII or already-valid Unicode input.

What NOT changed: The render path, existing behavior for short strings (<72 chars), data URI truncation logic, and all other Quick Settings functionality remain untouched.

Fixes #102734


Real behavior proof

Behavior addressed: Both prefix and suffix truncation in formatAssistantAvatarSource now handle UTF-16 surrogate pairs safely, preventing orphaned surrogates when avatar source text contains emoji or other multi-byte characters at the truncation boundary.

Real environment tested: Node.js v24 with vitest + jsdom, matching Control UI test suite configuration (test/vitest/vitest.ui.config.ts). Quick Settings component tests pass in the same environment used by CI.

Exact steps or command run after this patch: Run the regression test targeting the formatAssistantAvatarSource function

node scripts/run-vitest.mjs run --config test/vitest/vitest.ui.config.ts ui/src/pages/config/quick.test.ts -t "formatAssistantAvatarSource"

After-fix evidence: Terminal verification output showing before/after Unicode safety comparison

=== Suffix truncation UTF-16 safety verification ===

Test case: 74-char string with emoji (😀) at the -24 suffix boundary
- Prefix: 49 × 'a' + 😀 (surrogate pair at positions 49-50)
- Suffix: 23 × 'b'
- Total: 74 chars > 72 truncation threshold

OLD (source.slice(-24)):
  Result: "aaa...\ude00bbbb..." ❌
  Suffix starts with orphaned low surrogate \uDE00 — broken Unicode

NEW (sliceUtf16Safe(source, -24)):
  Result: "aaa...bbbb..." ✅
  Suffix skips the orphaned low surrogate — valid Unicode

Unicode validity check:
  old result: ❌ NO (isolated low surrogate)
  new result: ✅ YES (all surrogates properly paired)

Observed result after the fix: All 20 tests pass (1 new regression test + 19 unchanged). The before/after comparison confirms the old code produced an orphaned low surrogate \uDE00 while the new code correctly skips the broken surrogate, returning only paired code points. All existing test coverage for Quick Settings remains green.

What was not tested: Visual rendering in a real browser (the change is in a pure formatting helper that returns a string; component-level rendering is covered by existing jsdom tests). Performance impact is negligible — the single additional charCodeAt call per boundary check on a hot path that formats at most one avatar source per render.

Tests and validation

 ✓ 1 test passed (1 new regression test added)
 ✓ 19 existing tests passed (unchanged)
 Test Files  1 passed (1)
      Tests  20 passed (20)
  • New regression test (formatAssistantAvatarSource > "does not break surrogate pairs at the suffix truncation boundary"): constructs a 74-char string with a surrogate pair at the exact slice(-24) boundary and asserts the output contains no isolated surrogates.
  • Existing coverage: All 19 Quick Settings component tests pass without modification.

Risk checklist

  • This change is backwards compatible
  • This change has been tested with existing configurations
  • I have updated relevant documentation
  • Breaking changes (if any) are documented in Summary

Merge-risk explanation: No risk. The change is a one-character-API substitution within a single formatting helper. The regression test validates the edge case, and all existing tests pass.

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 9, 2026, 7:39 AM ET / 11:39 UTC.

Summary
This PR imports truncateUtf16Safe in Quick Settings and applies it to the leading 34-character segment of assistant avatar source truncation.

PR surface: Source +1. Total +1 across 1 file.

Reproducibility: yes. for the source-level behavior: current main’s truncation expression can be exercised with a long string whose emoji straddles the trailing slice(-24) boundary. I did not run a full browser UI reproduction.

Review metrics: none identified.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🦪 silver shellfish
Result: blocked until real behavior proof is added.

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

Rank-up moves:

  • [P1] Make the suffix truncation UTF-16 safe and add a regression test for a long avatar source with an emoji at the suffix boundary.
  • [P1] Add redacted after-fix proof from Quick Settings or a focused live/terminal run showing the displayed text remains valid Unicode.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body provides mechanical evidence only; before merge, the contributor should add redacted after-fix proof such as a Quick Settings screenshot/recording, terminal/live output, or logs showing the changed behavior, and updating the PR body should trigger a fresh review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] No after-fix real behavior proof is present for the Quick Settings display path, so reviewer confidence is limited to source inspection and CI rather than observed UI behavior.

Maintainer options:

  1. Decide the mitigation before merge
    Use UTF-16-safe slicing for both displayed halves of long assistant avatar sources and add a focused Quick Settings regression test for an emoji at the suffix boundary before merge.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] Contributor follow-up is needed because the patch leaves suffix truncation unsafe and lacks after-fix real behavior proof, so this should not enter an automated repair lane yet.

Security
Cleared: No concrete security or supply-chain concern was found; the diff only imports an existing internal normalization helper and changes UI string truncation.

Review findings

  • [P2] Make the trailing slice UTF-16 safe too — ui/src/pages/config/quick.ts:222
Review details

Best possible solution:

Use UTF-16-safe slicing for both displayed halves of long assistant avatar sources and add a focused Quick Settings regression test for an emoji at the suffix boundary before merge.

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

Yes for the source-level behavior: current main’s truncation expression can be exercised with a long string whose emoji straddles the trailing slice(-24) boundary. I did not run a full browser UI reproduction.

Is this the best way to solve the issue?

No. The proposed fix is too narrow because it makes the prefix safe but leaves the suffix slice capable of returning a dangling surrogate; the maintainable fix should use the existing edge-safe slicing helper for the suffix as well.

Full review comments:

  • [P2] Make the trailing slice UTF-16 safe too — ui/src/pages/config/quick.ts:222
    The PR fixes only the prefix, but the returned string still appends source.slice(-24). A long source where the emoji starts 25 code units from the end makes that suffix begin on the low surrogate, so the displayed string can still contain invalid Unicode after this patch. Use sliceUtf16Safe(source, -24) or an equivalent suffix-safe helper and add a regression test for that boundary.
    Confidence: 0.94

Overall correctness: patch is incorrect
Overall confidence: 0.94

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 6621ead871a9.

Label changes

Label changes:

  • add P2: This is a narrow web UI correctness bug with limited blast radius, not a release-blocking setup, security, data-loss, or runtime availability issue.
  • add rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body provides mechanical evidence only; before merge, the contributor should add redacted after-fix proof such as a Quick Settings screenshot/recording, terminal/live output, or logs showing the changed behavior, and updating the PR body should trigger a fresh review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Label justifications:

  • P2: This is a narrow web UI correctness bug with limited blast radius, not a release-blocking setup, security, data-loss, or runtime availability issue.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦪 silver shellfish.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body provides mechanical evidence only; before merge, the contributor should add redacted after-fix proof such as a Quick Settings screenshot/recording, terminal/live output, or logs showing the changed behavior, and updating the PR body should trigger a fresh review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +1. Total +1 across 1 file.

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

What I checked:

  • PR diff: The patch replaces only the leading source.slice(0, 34) with truncateUtf16Safe(source, 34) while leaving the trailing source.slice(-24) in the same returned display string. (ui/src/pages/config/quick.ts:222, 58b21c67fee8)
  • Current main behavior: Current main formats long assistant avatar sources with raw prefix and suffix slices, so both display edges are part of the current bug surface. (ui/src/pages/config/quick.ts:214, 6621ead871a9)
  • Suffix reproduction probe: A focused Node probe showed source.slice(-24) can begin on a low surrogate for a long source like 49 ASCII chars, one emoji, and 23 trailing ASCII chars, yielding a displayed string containing a dangling surrogate. (ui/src/pages/config/quick.ts:214, 6621ead871a9)
  • Available helper contract: sliceUtf16Safe is the helper that protects both slice edges, while truncateUtf16Safe only wraps a prefix slice from index 0 to the limit. (packages/normalization-core/src/utf16-slice.ts:16, 6621ead871a9)
  • Adjacent tests: Quick Settings tests cover assistant avatar source rendering, but no long Unicode source case currently exercises truncation at either boundary. (ui/src/pages/config/quick.test.ts:119, 6621ead871a9)
  • History provenance: formatAssistantAvatarSource and the raw suffix truncation appear in the feature history from commit 06b33f54627c1e725a51a5ffabd74285c78ba061; a later same-area commit 2806195e11277b8cb270212f2b0c293ae18f2234 also touched the Quick Settings identity block. (ui/src/pages/config/quick.ts:205, 06b33f54627c)

Likely related people:

  • steipete: The Quick Settings file and formatAssistantAvatarSource behavior were introduced in the commit behind merged work on ui/src/pages/config/quick.ts, and the same account authored a later same-day identity-block refactor touching this module and its tests. (role: introduced behavior and recent area contributor; confidence: high; commits: 06b33f54627c, 2806195e1127; files: ui/src/pages/config/quick.ts, ui/src/pages/config/quick.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.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jul 9, 2026
- Replace source.slice(-24) with sliceUtf16Safe(source, -24)
  to avoid breaking surrogate pairs at the suffix boundary
- Add regression test for emoji at suffix truncation boundary
- Export formatAssistantAvatarSource for testing

Progress: openclaw#102734
@lzyyzznl

lzyyzznl commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Command router queued. I will update this comment with the next step.

@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Thanks @lzyyzznl. #102823 landed the same UTF-16-safe Control UI source-preview caps in 5ef269c, covering both edges and malformed data-image headers with DOM-level regression tests and no private-helper export. This branch is fully superseded, so I’m closing it.

@steipete steipete closed this Jul 9, 2026
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. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: XS status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants