fix(ui): keep truncateWithEndVisible code-point-safe in short-width fallback#9029
fix(ui): keep truncateWithEndVisible code-point-safe in short-width fallback#9029greymoth-jp wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: 017122d The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@greymoth-jp is attempting to deploy a commit to the Clerk Production Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthrough
Surrogate-safe truncation fix
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui/src/utils/truncateTextWithEndVisible.ts`:
- Line 23: The fallback in truncateTextWithEndVisible is running before the
code-point-aware fit check, so astral-only strings that already fit still get
truncated. Move the Array.from(str).slice(-endChars) / ELLIPSIS fallback to
execute only after the totalChars <= maxLength guard (or equivalent
code-point-length check) in truncateWithEndVisible, and update the related tests
so they either expect the original string when it fits or use a smaller
maxLength to cover the truncation path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 7d33ce58-9c73-4bca-ad18-6e721833bb5f
📒 Files selected for processing (3)
.changeset/truncate-surrogate-safe.mdpackages/ui/src/utils/__tests__/truncateTextWithEndVisible.test.tspackages/ui/src/utils/truncateTextWithEndVisible.ts
|
|
||
| if (maxLength <= endChars + ELLIPSIS_LENGTH) { | ||
| return ELLIPSIS + str.slice(-endChars); | ||
| return ELLIPSIS + Array.from(str).slice(-endChars).join(''); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Hoist the code-point length check before this fallback.
Line 23 still runs before the code-point-aware totalChars <= maxLength guard, so astral-only strings that already fit are still truncated. For example, truncateWithEndVisible('🍣🍣🍣🍣🍣', 8, 5) now returns '...🍣🍣🍣🍣🍣' because Line 18 compares UTF-16 length. That also makes the new assertions at Lines 35-36 validate the wrong behavior; once this is fixed, they should either expect the original string or use a smaller maxLength to exercise the fallback.
Suggested fix
export function truncateWithEndVisible(str: string, maxLength = 20, endChars = 5): string {
const ELLIPSIS = '...';
const ELLIPSIS_LENGTH = ELLIPSIS.length;
- if (!str || str.length <= maxLength) {
+ if (!str) {
return str;
}
+
+ const chars = Array.from(str);
+ const totalChars = chars.length;
+
+ if (totalChars <= maxLength) {
+ return str;
+ }
if (maxLength <= endChars + ELLIPSIS_LENGTH) {
- return ELLIPSIS + Array.from(str).slice(-endChars).join('');
+ return ELLIPSIS + chars.slice(-endChars).join('');
}
-
- const chars = Array.from(str);
- const totalChars = chars.length;
-
- if (totalChars <= maxLength) {
- return str;
- }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return ELLIPSIS + Array.from(str).slice(-endChars).join(''); | |
| export function truncateWithEndVisible(str: string, maxLength = 20, endChars = 5): string { | |
| const ELLIPSIS = '...'; | |
| const ELLIPSIS_LENGTH = ELLIPSIS.length; | |
| if (!str) { | |
| return str; | |
| } | |
| const chars = Array.from(str); | |
| const totalChars = chars.length; | |
| if (totalChars <= maxLength) { | |
| return str; | |
| } | |
| if (maxLength <= endChars + ELLIPSIS_LENGTH) { | |
| return ELLIPSIS + chars.slice(-endChars).join(''); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/utils/truncateTextWithEndVisible.ts` at line 23, The fallback
in truncateTextWithEndVisible is running before the code-point-aware fit check,
so astral-only strings that already fit still get truncated. Move the
Array.from(str).slice(-endChars) / ELLIPSIS fallback to execute only after the
totalChars <= maxLength guard (or equivalent code-point-length check) in
truncateWithEndVisible, and update the related tests so they either expect the
original string when it fits or use a smaller maxLength to cover the truncation
path.
truncateWithEndVisiblekeeps the start and end of a string and puts an ellipsis in the middle. The main path builds the result fromArray.from(str), so it slices on code points and never breaks a character. The short-width fallback (taken whenmaxLength <= endChars + 3) instead returnsELLIPSIS + str.slice(-endChars), which slices on UTF-16 code units.For characters outside the BMP (CJK Extension B kanji, emoji) each character is a surrogate pair of two code units, so
str.slice(-endChars)can cut one in half and leave a lone surrogate:The fallback now slices by code point, the same way the main path already does. ASCII inputs are unaffected and the existing tests still pass. Added a test covering the fallback with astral characters.
Summary by CodeRabbit