Repository navigation
fix(shared): truncate no longer splits surrogate pairs - #24611
juliusmarminge wants to merge 3 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This narrowly scoped shared-helper fix prevents truncation from producing lone surrogate characters while preserving existing trimming, length, and suffix behavior. It includes focused regression tests and introduces no schema, configuration, or deployment changes. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Truncation boundary and tests packages/shared/src/String.ts, packages/shared/src/String.test.ts |
truncate removes the final high surrogate when the slice would split a surrogate pair before appending .... Tests cover surrogate-pair boundaries, ASCII and BMP strings, and input that fits the limit. |
Priority:
Estimated code review effort: 2 (Simple) | ~8 minutes
Change: Bug fix
Possibly related PRs
- pingdotgg/t3code#24333: Uses shared truncation logic for generated prompt context.
- pingdotgg/t3code#24077: Truncates preview text and removes a trailing high surrogate.
Suggested reviewers: macroscopeapp
Merge Risk: 
Minimal · up to 43cb2
-
The truncation change is mergeable after normal checks; no actionable risk remains in the supplied evidence.
Pre-merge checks: 
4
-
Full details: Description check
-
Explanation
The description explains the problem, implementation, scope, verification results, limitations, and takeover context. It does not use the exact template headings, but it provides the required information and links the related issue or prior pull request.
-
Finishing Touches
-
📝 Generate docstrings
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
|
Taking over this one |
Thread titles could end in a replacement glyph when their UTF-16 length limit fell between the two halves of an emoji. The shared
truncatehelper now backs off one code unit at that boundary. ASCII/BMP lengths, trimming and the three-dot suffix are preserved.Takeover
Original fix by @ValeraZSD in #13413.
Rebuilt on current main as one focused replacement because the submission lives on a contributor fork. The forward grapheme scanner and cluster tests were replaced with a constant-time surrogate-boundary check and three focused tests, matching the requested surrogate-pair scope without changing combining-mark truncation.
Evidence
Fixture matrix · Evidence index · Proof audit pass 2: web rows SUFFICIENT. Native Android rows (M-Android-L/D: native T50 title and the 200-unit work-log labels) are not captured; see Verification log.
Fixture titles pad with U+200B so the cut lands on the emoji while the visible title stays short:
T50 = "Text " + "\u200b".repeat(44) + "😀 tail",P50plan heading"Plan " + "\u200b".repeat(34) + "😀 tail", controlsE50(pair fits exactly) andSHORT.Environment for all W scenes: real base/head apps from separate checkouts and fresh isolated state; Linux fs-38, Chromium headless-shell 156.0.8078.70 via agent-browser, no device emulation, DPR 1, font scale 1, CSS viewport = PNG pixels, reduced motion, Date frozen at 2026-10-27T15:00:00Z. Fonts/network settled; two pixel-identical frames ≥500 ms apart; all originals/diffs visually reviewed.
Seed: actual composer T50/E50/SHORT sends; actual proposed_plan → Implement in a new thread for P50. Deliberately unavailable custom text-generation model preserves fallback seeds after regeneration fails. Isolated projection metadata equalizes times/branch/settled flags only. seed-recipe.md · provenance.json.
W-1440-L — 1440×1000, light; T50, E50, SHORT, P50
843f4e3
43cb239
843f4e3 → 43cb239
843f4e3 → 43cb239
W-1440-D — 1440×1000, dark; T50, E50, SHORT, P50
843f4e3
43cb239
843f4e3 → 43cb239
843f4e3 → 43cb239
W-390-L — 390×844, light; T50, E50, SHORT, P50
843f4e3
43cb239
843f4e3 → 43cb239
843f4e3 → 43cb239
W-390-D — 390×844, dark; T50, E50, SHORT, P50
843f4e3
43cb239
843f4e3 → 43cb239
843f4e3 → 43cb239
What else changed:
Drivers and seeds: capture-real.py · seed-web.py (reproduction recipe; UI seed was executed manually) · normalize.py · diff.py · audit-bundle.tar.gz (scripts, SQL, settling transcripts and redacted logs).
Verification log
packages/shared/src/String.test.ts. Merge-base helper: 1 failure, 2 passes; head: all 3 pass. Exact-pair and ASCII/BMP cases guard compatibility.truncatecalls (matrix, probe). Four web pairs were captured from real base/head apps. Pass 2 inspected all 8 originals and 8 diffs and rebuilt every diff byte-identically: W-1440-L/D and W-390-L/D are SUFFICIENT; M-Android-L/D are missing.mobile:installclient, and a local Android build took the VM below its disk floor. The native rows reuse the same helper throughapps/mobile/src/lib/threadActivity.ts(200 units) andderiveThreadTitleSeed(50 units). Unexecuted native seed.Original description by @ValeraZSD
Thread titles could end in
�when the truncation boundary split an emoji's UTF-16 surrogate pair. The shared helper now selects a safe boundary before adding the ellipsis, preserving joined emoji, modifiers, combining marks, and paired flag indicators.The implementation uses a small forward code-point scan and avoids
Intl.Segmenter, which shared mobile code cannot use under the repository's Hermes constraint. Ordinary short-text trimming remains unchanged.Two focused tests replace six overlapping cases. They fail with the original helper, and their cluster assertions also fail with a surrogate-only fix. The helper and thread-title suites pass all 7 tests; targeted lint, formatting, and shared-package typecheck pass. After merging current main, the same 7 tests and shared-package typecheck pass at ca8b37b. The merge leaves both PR files unchanged; fresh independent Opus and Sol reviews found no actionable defects. No live client or Hermes verification was performed during this takeover.
Original fix by @ValeraZSD in #13413. Maintainer takeover by Claude Opus 5.5 and Claude Fable 5.1 through T3 Code, independently reviewed by Claude Opus 5.5 and GPT-6.1-Sol through the Codex harness.