Skip to content

fix(line): truncate action label/data on code-point boundaries#96413

Closed
ly-wang19 wants to merge 1 commit into
openclaw:mainfrom
ly-wang19:fix/line-action-label-surrogate-safe
Closed

fix(line): truncate action label/data on code-point boundaries#96413
ly-wang19 wants to merge 1 commit into
openclaw:mainfrom
ly-wang19:fix/line-action-label-surrogate-safe

Conversation

@ly-wang19

Copy link
Copy Markdown
Contributor

What Problem This Solves

The LINE action builders in extensions/line/src/actions.ts enforce LINE's
field limits by calling String.prototype.slice directly:

  • messageAction / uriAction / postbackAction / datetimePickerAction
    label.slice(0, 20)
  • postbackAction / datetimePickerActiondata.slice(0, 300) and
    displayText?.slice(0, 300)

A raw UTF-16 slice cuts an emoji in half whenever the limit lands inside a
surrogate pair. The returned label then ends in an unpaired high
surrogate
, which LINE renders as a replacement character (or rejects the
payload).

Repro: messageAction("1234567890123456789😀") — 19 ASCII chars + 😀
(U+1F600, 2 UTF-16 code units) = 21 code units.

label returned
Before (buggy) "1234567890123456789\uD83D" — trailing lone high surrogate
After (fixed) "1234567890123456789" — half-emoji dropped, no lone surrogate

The fix swaps the raw slices for truncateUtf16Safe(value, limit) from
openclaw/plugin-sdk/text-utility-runtime, which drops a dangling surrogate
instead of emitting it. This matches how this plugin already truncates LINE
template titles (truncateTemplateText in template-messages.ts). Normal
inputs (short labels, exactly-at-limit ASCII, fully-fitting emoji) are
unchanged, and displayText stays undefined when omitted.

Evidence

Standalone red→green proof (replicates truncateUtf16Safe + the old vs. new
action logic):

RED  ok: old messageAction label = "1234567890123456789\ud83d" (lone surrogate present)
GREEN ok: messageAction label = "1234567890123456789"
GREEN ok: postbackAction label/data/displayText all surrogate-safe
GREEN ok: postbackAction displayText undefined is preserved
GREEN ok: normal/unaffected inputs unchanged

ALL ASSERTIONS PASSED — fix produces expectedOutput and preserves normal behavior.

The script asserts:

  • RED: the old label.slice(0, 20) keeps a lone high surrogate (\uD83D).
  • GREEN: the fixed builders return "1234567890123456789" (no lone
    surrogate) for label, and surrogate-safe data / displayText.
  • Preservation: "Yes""Yes", 20-char ASCII unchanged, 25-char ASCII
    truncated to 20, fully-fitting "hi😀" preserved, and displayText stays
    undefined when not passed.

New unit tests in extensions/line/src/message-cards.test.ts cover
messageAction, uriAction, postbackAction (label, data, displayText), and
datetimePickerAction, asserting no /[\uD800-\uDBFF](?![\uDC00-\uDFFF])/
lone surrogate survives truncation.

🤖 Generated with Claude Code

The LINE action builders truncated label (20) and data/displayText (300)
with a raw `String.prototype.slice`, which cuts an emoji apart when the
limit falls inside a surrogate pair. A 19-char label followed by 😀
(U+1F600) yielded a label ending in a lone high surrogate (\uD83D), which
LINE renders as a replacement glyph or rejects.

Truncate with `truncateUtf16Safe` instead so a dangling half-emoji is
dropped, matching how this plugin already truncates LINE template titles.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@openclaw-barnacle openclaw-barnacle Bot added channel: line Channel integration: line size: S r: too-many-prs Auto-close: author has more than twenty active PRs. labels Jun 24, 2026
@openclaw-barnacle

Copy link
Copy Markdown

Closing this PR because the author has more than 20 active PRs in this repo. Please reduce the active PR queue and reopen or resubmit once it is back under the limit. You can close your own PRs to get back under the limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: line Channel integration: line r: too-many-prs Auto-close: author has more than twenty active PRs. size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant