mirror of
https://github.com/Hmbown/DeepSeek-TUI.git
synced 2026-09-03 06:50:13 +08:00
Defect: the homepage wire strip shortened long titles with `title.slice(0, 70)`, and the roadmap summaries with `stripped.slice(0, 137)`. `slice` counts UTF-16 code units, so when the cut lands between the two halves of a surrogate pair the output ends in a lone surrogate. GitHub issue, pull request and release titles carry emoji routinely; the reader sees U+FFFD — the black-diamond question mark — immediately before the ellipsis. Verified: a title of 69 ASCII characters followed by U+1F40B yields a string whose last code unit is 0xD83D. The same `.length` test also miscounted: an astral character was two characters toward the budget, so a 40-emoji title was treated as 80 characters and truncated at 35. Fix: one code-point-safe `truncateChars` in lib/truncate.ts, used by both surfaces. Character budgets and thresholds are unchanged (70 for the ticker; cut at 140, keep 137 for roadmap notes), so nothing but the broken cut moves. Also corrected a false comment in summarizeReleaseBody: it claimed to strip trailing emoji, which it has never done. Evidence: lib/truncate.test.ts. Restoring `value.slice(...)` in the helper fails "never splits an astral character in half" (a lone surrogate survives the well-formed-pair filter) and "counts code points, not UTF-16 code units". The last case pins both call sites onto the shared rule. npm test 322 passed, npm run lint clean, npx tsc --noEmit clean. Implemented with agent assistance. Signed-off-by: Hunter Bown <hmbown@gmail.com>
30 lines
1.0 KiB
TypeScript
30 lines
1.0 KiB
TypeScript
/**
|
|
* truncate.ts — code-point-safe truncation for text the repository owns
|
|
* rather than the site.
|
|
*
|
|
* `String.prototype.slice` counts UTF-16 code units, so a cut can land
|
|
* between the two halves of a surrogate pair. GitHub issue, pull request,
|
|
* and release titles routinely carry emoji, and the resulting lone surrogate
|
|
* is not a character: it renders as U+FFFD (the black-diamond question
|
|
* mark) in every browser, immediately before the ellipsis that says the text
|
|
* was shortened.
|
|
*/
|
|
|
|
/**
|
|
* `value` unchanged when it is at most `limit` characters long; otherwise its
|
|
* first `keep` characters (default: `limit`) followed by `ellipsis`.
|
|
*
|
|
* Characters are Unicode code points, so an astral character is either kept
|
|
* whole or dropped whole.
|
|
*/
|
|
export function truncateChars(
|
|
value: string,
|
|
limit: number,
|
|
keep: number = limit,
|
|
ellipsis = "…",
|
|
): string {
|
|
const chars = Array.from(value);
|
|
if (chars.length <= limit) return value;
|
|
return chars.slice(0, Math.max(0, keep)).join("") + ellipsis;
|
|
}
|