Skip to content

Commit 009c0da

Browse files
committed
fix(tui): per-file polish round 12 — locale slice(-0) truncation
truncateLeft (budget 1) and truncateMiddle (maxLength 1-2) computed a negative slice bound of -0; str.slice(-0) === str.slice(0) returns the whole string, so callers that clamp the width to 1 via Math.max(1, …) rendered an ellipsis followed by the full untruncated string, blowing out the row layout on very narrow terminals. Guard the zero case and add regression tests.
1 parent efa7be3 commit 009c0da

3 files changed

Lines changed: 28 additions & 2 deletions

File tree

MERGE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ fork 与上游改了同一处(常见于 TUI 视觉/UX、core 加固逻辑)
205205
| 逐文件打磨轮7:`component/dialog-model.tsx``dialog-{agent,move-session,theme-list,variant,session-delete-failed,session-list,skill,stash,workspace-file-changes,workspace-list}.tsx`+`command-palette.tsx` 已审计) || 保留 bug 修复:收藏/最近模型选项由可选的 `model.id` 改用可靠的映射键 `item.modelID``id?: string` 缺省时 `model.id.includes("-nano")` 抛错、选中项 modelID 变 undefined;并与 providerOptions 分支用键一致);`dialog-theme-list` `theme.all()` setup 期快照(需订阅 `subscribeThemes` 才能修,`all()` 非响应式且启动后基本不变)择要跳过(上游若已做可取上游版本) |
206206
| 逐文件打磨轮8:`prompt/{part.ts,frecency.tsx}` + `test/prompt/part.test.ts``prompt/{display,history,stash,traits}` + `component/prompt/{history,frecency,stash}` 再导出壳已审计) || 保留 bug 修复:`expandPastedTextPlaceholders``String.replace(needle, part.text)` 改函数替换 `() => part.text`(粘贴含 `$&`/`$'`/`$$`/`$n` 的文本经 open-editor/copy 路径被当替换模式解释而损坏,补回归测试);`frecency` 剪枝 `setStore("data", obj)``reconcile(obj)`(Solid store 合并语义使被剪路径永不删除,会话内 map 无限增长、每次 update 全量重写 jsonl)(上游若已做可取上游版本) |
207207
| 逐文件打磨轮9:`context/{kv,data}.tsx``context/{permission,prompt,clipboard,editor,path-format,thinking,directory,location}` 已审计) || 保留 bug 修复:`kv` 读取前先 `Bun.file(file).exists()`,缺文件返回 `{}`(首次启动 `kv.json` 不存在时不再打印误导性的 "Failed to read KV state",保留真实读/解析错误日志);`data` 事件驱动的 `catalog/reference/integration.updated` 三处 `void refresh()``.catch(console.error)``throwOnError` 下瞬时失败原为未处理 rejection,与初始加载 allSettled+console.error 一致);`editor.ts` Zed 轮询代际/`enabled()` 非响应式属自我纠正边角,按审计建议暂缓(上游若已做可取上游版本) |
208+
| 逐文件打磨轮12:`util/locale.ts` + `test/util/locale.test.ts``ui/{dialog,dialog-alert,dialog-confirm,dialog-help,border,activity-verbs}` + `util/{renderer,record,system,signal}` 及轮10 `plugin/*`+`builtins`、轮11 `util/{revert-diff,scroll,selection,transcript,tool-display,model,session,format,path,provider-origin}` 均审计为净) || 保留 bug 修复:`truncateLeft`/`truncateMiddle``slice(-0)` 边界(预算=1 或 keepEnd=0 时 `str.slice(-0)===str.slice(0)` 返回整串,`Math.max(1,…)` 钳制的窄终端下 dialog-select/dialog-move-session/workspace-file-changes 会渲染出比原串更长的 `…+整串` 撑爆行),补回归测试(上游若已做可取上游版本) |
208209

209210
### 其他
210211

packages/tui/src/util/locale.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function truncate(str: string, len: number): string {
6868

6969
export function truncateLeft(str: string, len: number): string {
7070
if (str.length <= len) return str
71-
return "…" + str.slice(-(len - 1))
71+
return "…" + (len <= 1 ? "" : str.slice(-(len - 1)))
7272
}
7373

7474
export function truncateMiddle(str: string, maxLength: number = 35): string {
@@ -78,7 +78,7 @@ export function truncateMiddle(str: string, maxLength: number = 35): string {
7878
const keepStart = Math.ceil((maxLength - ellipsis.length) / 2)
7979
const keepEnd = Math.floor((maxLength - ellipsis.length) / 2)
8080

81-
return str.slice(0, keepStart) + ellipsis + str.slice(-keepEnd)
81+
return str.slice(0, keepStart) + ellipsis + (keepEnd === 0 ? "" : str.slice(-keepEnd))
8282
}
8383

8484
export function pluralize(count: number, singular: string, plural: string): string {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { Locale } from "../../src/util/locale"
3+
4+
describe("Locale truncation", () => {
5+
test("truncateLeft respects a width budget of 1", () => {
6+
expect(Locale.truncateLeft("abcdef", 1)).toBe("…")
7+
})
8+
9+
test("truncateLeft keeps the tail for larger budgets", () => {
10+
expect(Locale.truncateLeft("abcdef", 3)).toBe("…ef")
11+
})
12+
13+
test("truncateLeft returns the string when it already fits", () => {
14+
expect(Locale.truncateLeft("ab", 3)).toBe("ab")
15+
})
16+
17+
test("truncateMiddle respects a maxLength of 1 or 2", () => {
18+
expect(Locale.truncateMiddle("abcdef", 1)).toBe("…")
19+
expect(Locale.truncateMiddle("abcdef", 2)).toBe("a…")
20+
})
21+
22+
test("truncateMiddle keeps both ends for larger budgets", () => {
23+
expect(Locale.truncateMiddle("abcdefgh", 5)).toBe("ab…gh")
24+
})
25+
})

0 commit comments

Comments
 (0)