refactor(ai-vault): move the surrogate-safe slice to shared, one implementation

`sliceAtCodeUnitLimit` lived in src/main/ai-vault, and src/shared never imports
src/main, so a shared consumer could not reach it. Rather than add a second
copy, it moves to src/shared and ai-vault imports and re-exports it, leaving
every existing importer of that module untouched.

Separated from the feature that needs it: this is the only change here to a
subsystem the rest of the branch does not touch.
This commit is contained in:
Merge Sim
2026-09-10 16:09:07 -07:00
parent 721a269289
commit 63c2da2d38
2 changed files with 23 additions and 9 deletions
@@ -1,3 +1,7 @@
import { sliceAtCodeUnitLimit } from '../../shared/surrogate-safe-text-slice'
export { sliceAtCodeUnitLimit }
const SESSION_TITLE_TEXT_LIMIT = 96
const SESSION_PREVIEW_TEXT_LIMIT = 220
const ELLIPSIS = '...'
@@ -42,15 +46,6 @@ export function normalizePreviewText(value: string): string | null {
return finalizeNormalizedText(normalizeStringText(value, SESSION_PREVIEW_TEXT_LIMIT))
}
/** Cut to `limit` UTF-16 code units without splitting a trailing surrogate pair. */
export function sliceAtCodeUnitLimit(value: string, limit: number): string {
if (value.length <= limit) {
return value
}
const end = limit > 0 && isHighSurrogate(value.charCodeAt(limit - 1)) ? limit - 1 : limit
return value.slice(0, end)
}
function normalizeContentText(value: unknown, limit: number): string | null {
if (typeof value === 'string') {
return finalizeNormalizedText(normalizeStringText(value, limit))
+19
View File
@@ -0,0 +1,19 @@
// Cutting text to a length bound without splitting a character in half.
//
// JavaScript string length counts UTF-16 code units, so a raw `slice` at a
// bound can land between the two halves of an astral character — an emoji, or
// most CJK extension characters — and leave a lone surrogate that every surface
// renders as U+FFFD.
/** Cut to `limit` UTF-16 code units without splitting a trailing surrogate pair. */
export function sliceAtCodeUnitLimit(value: string, limit: number): string {
if (value.length <= limit) {
return value
}
const end = limit > 0 && isHighSurrogate(value.charCodeAt(limit - 1)) ? limit - 1 : limit
return value.slice(0, end)
}
function isHighSurrogate(code: number): boolean {
return code >= 0xd800 && code <= 0xdbff
}