fix(line): truncate template title/altText on grapheme boundaries, not raw UTF-16#97428
Conversation
…t raw UTF-16 createConfirmTemplate/createButtonTemplate/createTemplateCarousel/createCarouselColumn/ createImageCarousel truncated title and altText with a raw `.slice(0, N)`, so an emoji straddling a LINE field limit (e.g. a 40-char button title) was cut in half, leaving a lone high surrogate that LINE renders as the replacement char or rejects. Route those fields through the file's existing grapheme-safe truncateTemplateText (already used for the text body) via a small truncateOptionalTemplateText wrapper. Byte-identical for all-BMP input; only straddling-emoji truncation changes. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
Codex review: needs maintainer review before merge. Reviewed June 28, 2026, 1:49 PM ET / 17:49 UTC. Summary PR surface: Source +9, Tests +53. Total +62 across 2 files. Reproducibility: yes. source-level: current main uses raw 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 this focused LINE template-message truncation fix after normal maintainer merge gates; keep adjacent LINE action-field cleanup in #97470 separate. Do we have a high-confidence way to reproduce the issue? Yes, source-level: current main uses raw Is this the best way to solve the issue? Yes: reusing the existing local grapheme-aware helper inside the LINE template-message builder is the narrowest maintainable fix for this surface. The related action-field cleanup is adjacent and tracked separately in #97470. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against c0883a531de9. Label changesLabel justifications:
Evidence reviewedPR surface: Source +9, Tests +53. Total +62 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
|
Real behavior proof — grapheme-safe title/altText truncationThe new surrogate-safe path is exercised by a focused regression test that asserts no lone high surrogate remains after truncation. Local run (Node 22.22.1): Regression case added (
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review |
|
Real-behavior proof (post-fix) for the surrogate-boundary case, generated by invoking production Input title (41 UTF-16 code units, emoji at the 40-char boundary): Before-fix behavior on main: raw The last code unit is a lone high surrogate After-fix behavior: The grapheme segmenter drops the whole After-fix: Both Local: @clawsweeper re-review |
…t raw UTF-16 (openclaw#97428) * fix(line): truncate template title/altText on grapheme boundaries, not raw UTF-16 createConfirmTemplate/createButtonTemplate/createTemplateCarousel/createCarouselColumn/ createImageCarousel truncated title and altText with a raw `.slice(0, N)`, so an emoji straddling a LINE field limit (e.g. a 40-char button title) was cut in half, leaving a lone high surrogate that LINE renders as the replacement char or rejects. Route those fields through the file's existing grapheme-safe truncateTemplateText (already used for the text body) via a small truncateOptionalTemplateText wrapper. Byte-identical for all-BMP input; only straddling-emoji truncation changes. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * chore: retry OpenGrep scan (HTTP 502 infra flake) * test(line): cover grapheme-safe template fields --------- Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
…t raw UTF-16 (openclaw#97428) * fix(line): truncate template title/altText on grapheme boundaries, not raw UTF-16 createConfirmTemplate/createButtonTemplate/createTemplateCarousel/createCarouselColumn/ createImageCarousel truncated title and altText with a raw `.slice(0, N)`, so an emoji straddling a LINE field limit (e.g. a 40-char button title) was cut in half, leaving a lone high surrogate that LINE renders as the replacement char or rejects. Route those fields through the file's existing grapheme-safe truncateTemplateText (already used for the text body) via a small truncateOptionalTemplateText wrapper. Byte-identical for all-BMP input; only straddling-emoji truncation changes. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * chore: retry OpenGrep scan (HTTP 502 infra flake) * test(line): cover grapheme-safe template fields --------- Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
…t raw UTF-16 (openclaw#97428) * fix(line): truncate template title/altText on grapheme boundaries, not raw UTF-16 createConfirmTemplate/createButtonTemplate/createTemplateCarousel/createCarouselColumn/ createImageCarousel truncated title and altText with a raw `.slice(0, N)`, so an emoji straddling a LINE field limit (e.g. a 40-char button title) was cut in half, leaving a lone high surrogate that LINE renders as the replacement char or rejects. Route those fields through the file's existing grapheme-safe truncateTemplateText (already used for the text body) via a small truncateOptionalTemplateText wrapper. Byte-identical for all-BMP input; only straddling-emoji truncation changes. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * chore: retry OpenGrep scan (HTTP 502 infra flake) * test(line): cover grapheme-safe template fields --------- Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
…t raw UTF-16 (openclaw#97428) * fix(line): truncate template title/altText on grapheme boundaries, not raw UTF-16 createConfirmTemplate/createButtonTemplate/createTemplateCarousel/createCarouselColumn/ createImageCarousel truncated title and altText with a raw `.slice(0, N)`, so an emoji straddling a LINE field limit (e.g. a 40-char button title) was cut in half, leaving a lone high surrogate that LINE renders as the replacement char or rejects. Route those fields through the file's existing grapheme-safe truncateTemplateText (already used for the text body) via a small truncateOptionalTemplateText wrapper. Byte-identical for all-BMP input; only straddling-emoji truncation changes. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * chore: retry OpenGrep scan (HTTP 502 infra flake) * test(line): cover grapheme-safe template fields --------- Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
…t raw UTF-16 (openclaw#97428) * fix(line): truncate template title/altText on grapheme boundaries, not raw UTF-16 createConfirmTemplate/createButtonTemplate/createTemplateCarousel/createCarouselColumn/ createImageCarousel truncated title and altText with a raw `.slice(0, N)`, so an emoji straddling a LINE field limit (e.g. a 40-char button title) was cut in half, leaving a lone high surrogate that LINE renders as the replacement char or rejects. Route those fields through the file's existing grapheme-safe truncateTemplateText (already used for the text body) via a small truncateOptionalTemplateText wrapper. Byte-identical for all-BMP input; only straddling-emoji truncation changes. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * chore: retry OpenGrep scan (HTTP 502 infra flake) * test(line): cover grapheme-safe template fields --------- Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: Vincent Koc <[email protected]> (cherry picked from commit 4b36cf4)
…t raw UTF-16 (openclaw#97428) * fix(line): truncate template title/altText on grapheme boundaries, not raw UTF-16 createConfirmTemplate/createButtonTemplate/createTemplateCarousel/createCarouselColumn/ createImageCarousel truncated title and altText with a raw `.slice(0, N)`, so an emoji straddling a LINE field limit (e.g. a 40-char button title) was cut in half, leaving a lone high surrogate that LINE renders as the replacement char or rejects. Route those fields through the file's existing grapheme-safe truncateTemplateText (already used for the text body) via a small truncateOptionalTemplateText wrapper. Byte-identical for all-BMP input; only straddling-emoji truncation changes. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> * chore: retry OpenGrep scan (HTTP 502 infra flake) * test(line): cover grapheme-safe template fields --------- Co-authored-by: ly-wang19 <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: Vincent Koc <[email protected]> (cherry picked from commit 4b36cf4)
What Problem This Solves
Line plugin template
title/altTexttruncation operates on raw UTF-16 code units. When a template title contains an emoji or other astral character near the truncation boundary, the cut splits a surrogate pair and produces a malformed string with a lone surrogate.Why This Change Was Made
Truncation boundaries should respect grapheme/code-point boundaries for the same reason other user-facing text does — a lone surrogate breaks downstream rendering, JSON encoding, and copy-paste. Switch the truncation to grapheme boundaries so a multi-code-point character never gets cut mid-character.
User Impact
Line templates with long titles or alt text that include emoji or astral characters no longer produce broken strings when truncated.
Evidence
pnpm teston touched line-plugin test filefix/line-template-surrogate-safe