From e23c0552aab27b0252831b1dc9dd3b1d8aea3f3d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 10 Jul 2026 22:58:48 -0400 Subject: [PATCH 1/6] feat(tui): add semantic file path truncation --- .../dialog-workspace-file-changes.tsx | 6 +- .../tui/src/feature-plugins/sidebar/files.tsx | 10 ++- packages/tui/src/routes/session/index.tsx | 5 +- packages/tui/src/ui/file-path.tsx | 86 +++++++++++++++++++ packages/tui/test/ui/file-path.test.ts | 47 ++++++++++ 5 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 packages/tui/src/ui/file-path.tsx create mode 100644 packages/tui/test/ui/file-path.test.ts diff --git a/packages/tui/src/component/dialog-workspace-file-changes.tsx b/packages/tui/src/component/dialog-workspace-file-changes.tsx index 2babeecf8fcc..24a46de4529f 100644 --- a/packages/tui/src/component/dialog-workspace-file-changes.tsx +++ b/packages/tui/src/component/dialog-workspace-file-changes.tsx @@ -3,7 +3,7 @@ import { useKeyboard } from "@opentui/solid" import type { VcsFileStatus } from "@opencode-ai/sdk/v2" import { createMemo, For } from "solid-js" import { createStore } from "solid-js/store" -import { Locale } from "../util/locale" +import { FilePath } from "../ui/file-path" import { useTheme } from "../context/theme" import { useTuiConfig } from "../config" import { useDialog, type DialogContext } from "../ui/dialog" @@ -93,9 +93,7 @@ export function DialogWorkspaceFileChanges(props: { {statusLabel(item.status)} - - {Locale.truncateLeft(item.file, fileNameWidth())} - + diff --git a/packages/tui/src/feature-plugins/sidebar/files.tsx b/packages/tui/src/feature-plugins/sidebar/files.tsx index 01f33f647d69..e76db914440d 100644 --- a/packages/tui/src/feature-plugins/sidebar/files.tsx +++ b/packages/tui/src/feature-plugins/sidebar/files.tsx @@ -1,7 +1,7 @@ import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui" import type { BuiltinTuiPlugin } from "../builtins" import { createMemo, For, Show, createSignal } from "solid-js" -import { Locale } from "../../util/locale" +import { FilePath } from "../../ui/file-path" const id = "internal:sidebar-files" @@ -31,9 +31,11 @@ function View(props: { api: TuiPluginApi; session_id: string }) { {(item) => ( - - {Locale.truncateLeft(item.file, Math.max(2, 36 - changeCountWidth(item)))} - + +{item.additions} diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 13c200101a2d..2742e183b173 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -38,6 +38,7 @@ import type { } from "@opencode-ai/sdk/v2" import { useLocal } from "../../context/local" import { Locale } from "../../util/locale" +import { FilePath } from "../../ui/file-path" import { webSearchProviderLabel } from "../../util/tool-display" import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid" import { useSDK } from "../../context/sdk" @@ -1435,9 +1436,7 @@ function RevertMessage(props: { {(file) => ( {statusLabel(file.status)} - - {Locale.truncateLeft(file.file, 60)} - + 0}> +{file.additions} diff --git a/packages/tui/src/ui/file-path.tsx b/packages/tui/src/ui/file-path.tsx new file mode 100644 index 000000000000..b5adf67b6a0d --- /dev/null +++ b/packages/tui/src/ui/file-path.tsx @@ -0,0 +1,86 @@ +import type { RGBA } from "@opentui/core" + +const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" }) + +export interface FilePathProps { + value: string + maxWidth: number + fg?: RGBA +} + +export function FilePath(props: FilePathProps) { + return ( + + {truncateFilePath(props.value, props.maxWidth)} + + ) +} + +export function truncateFilePath(value: string, maxWidth: number) { + if (maxWidth <= 0) return "" + if (Bun.stringWidth(value) <= maxWidth) return value + + const separator = value.includes("/") ? "/" : "\\" + const drive = separator === "\\" ? value.match(/^[A-Za-z]:\\/)?.[0] : undefined + const root = drive ?? (value.startsWith(separator) ? separator : "") + const segments = value.slice(root.length).split(separator).filter(Boolean) + const basename = segments.at(-1) ?? value + if (segments.length < 2) { + const rootWidth = Bun.stringWidth(root) + if (rootWidth >= maxWidth) return takeStart(root, maxWidth) + return root + truncateBasename(basename, maxWidth - rootWidth) + } + + const prefix = `…${separator}` + const basenameWidth = maxWidth - Bun.stringWidth(prefix) + if (basenameWidth <= 0) return takeStart("…", maxWidth) + const compact = truncateBasename(basename, basenameWidth) + if (compact !== basename) return prefix + compact + + const selected = [basename] + const separatorWidth = Bun.stringWidth(separator) + let width = Bun.stringWidth(prefix + basename) + for (let index = segments.length - 2; index >= 0; index--) { + const next = Bun.stringWidth(segments[index]!) + separatorWidth + if (width + next > maxWidth) break + selected.unshift(segments[index]!) + width += next + } + return prefix + selected.join(separator) +} + +function truncateBasename(value: string, maxWidth: number) { + if (Bun.stringWidth(value) <= maxWidth) return value + if (maxWidth <= 1) return takeStart("…", maxWidth) + + const dot = value.lastIndexOf(".") + const extension = dot > 0 ? value.slice(dot) : "" + const extensionWidth = Bun.stringWidth(extension) + if (extensionWidth >= maxWidth) return "…" + takeEnd(extension, maxWidth - 1) + + const stem = extension ? value.slice(0, dot) : value + return takeStart(stem, maxWidth - extensionWidth - 1) + "…" + extension +} + +function takeStart(value: string, maxWidth: number) { + return take(value, maxWidth, false) +} + +function takeEnd(value: string, maxWidth: number) { + return take(value, maxWidth, true) +} + +function take(value: string, maxWidth: number, reverse: boolean) { + const segments = Array.from(graphemeSegmenter.segment(value), (item) => item.segment) + if (reverse) segments.reverse() + const selected: string[] = [] + let width = 0 + for (const segment of segments) { + const next = Bun.stringWidth(segment) + if (width + next > maxWidth) break + selected.push(segment) + width += next + } + if (reverse) selected.reverse() + return selected.join("") +} diff --git a/packages/tui/test/ui/file-path.test.ts b/packages/tui/test/ui/file-path.test.ts new file mode 100644 index 000000000000..8b3b198fcee5 --- /dev/null +++ b/packages/tui/test/ui/file-path.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from "bun:test" +import { truncateFilePath } from "../../src/ui/file-path" + +describe("truncateFilePath", () => { + const path = "packages/tui/src/ui/dialog-select.tsx" + + test("keeps the full path when it fits", () => { + expect(truncateFilePath(path, 37)).toBe(path) + }) + + test("adds nearest parent segments from right to left", () => { + expect(truncateFilePath(path, 26)).toBe("…/src/ui/dialog-select.tsx") + expect(truncateFilePath(path, 22)).toBe("…/ui/dialog-select.tsx") + expect(truncateFilePath(path, 19)).toBe("…/dialog-select.tsx") + }) + + test("preserves the extension when the basename must shrink", () => { + expect(truncateFilePath(path, 16)).toBe("…/dialog-se….tsx") + expect(truncateFilePath("dialog-select.tsx", 12)).toBe("dialog-….tsx") + }) + + test("preserves the input separator", () => { + expect(truncateFilePath("packages\\tui\\src\\ui\\dialog-select.tsx", 22)).toBe("…\\ui\\dialog-select.tsx") + }) + + test("does not treat a backslash in a POSIX filename as a separator", () => { + expect(truncateFilePath("dir/file\\name.ts", 14)).toBe("…/file\\name.ts") + }) + + test("preserves root-level absolute paths", () => { + expect(truncateFilePath("/file.ts", 7)).toBe("/fi….ts") + expect(truncateFilePath("C:\\file.ts", 9)).toBe("C:\\fi….ts") + }) + + test("measures terminal columns without splitting graphemes", () => { + expect(truncateFilePath("packages/组件/对话框.tsx", 12)).toBe("…/对话框.tsx") + expect(truncateFilePath("src/👩‍💻-notes.tsx", 12)).toContain("👩‍💻") + expect(truncateFilePath("中a.txt", 6)).toBe("….txt") + expect(truncateFilePath("file.中a", 3)).toBe("…a") + }) + + test("never exceeds the requested width", () => { + for (let width = 0; width <= Bun.stringWidth(path); width++) { + expect(Bun.stringWidth(truncateFilePath(path, width))).toBeLessThanOrEqual(width) + } + }) +}) From 87c4939d20b3df6d387ac380d0e02253738ff343 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 11 Jul 2026 12:35:00 -0400 Subject: [PATCH 2/6] fix(tui): use available width for file paths --- .../tui/src/feature-plugins/home/footer.tsx | 22 ++++- packages/tui/src/routes/session/index.tsx | 97 +++++++++++++++---- 2 files changed, 96 insertions(+), 23 deletions(-) diff --git a/packages/tui/src/feature-plugins/home/footer.tsx b/packages/tui/src/feature-plugins/home/footer.tsx index 41bee5da5a49..5ed139b0eefb 100644 --- a/packages/tui/src/feature-plugins/home/footer.tsx +++ b/packages/tui/src/feature-plugins/home/footer.tsx @@ -4,10 +4,12 @@ import { createMemo, Match, Show, Switch } from "solid-js" import { abbreviateHome } from "../../runtime" import { useTuiPaths } from "../../context/runtime" import { useHomeSessionDestination } from "../../routes/home/session-destination" +import { FilePath } from "../../ui/file-path" +import { useTerminalDimensions } from "@opentui/solid" const id = "internal:home-footer" -function Directory(props: { api: TuiPluginApi }) { +function Directory(props: { api: TuiPluginApi; maxWidth: number }) { const theme = () => props.api.theme.current const destination = useHomeSessionDestination() const paths = useTuiPaths() @@ -21,7 +23,11 @@ function Directory(props: { api: TuiPluginApi }) { return out }) - return {(value) => {value()}} + return ( + + {(value) => } + + ) } function Mcp(props: { api: TuiPluginApi }) { @@ -62,6 +68,16 @@ function Version(props: { api: TuiPluginApi }) { } function View(props: { api: TuiPluginApi }) { + const dimensions = useTerminalDimensions() + const mcpWidth = createMemo(() => { + const list = props.api.state.mcp() + if (list.length === 0) return 0 + const count = list.filter((item) => item.status === "connected").length + return Bun.stringWidth(`⊙ ${count} MCP /status`) + 2 + }) + const directoryWidth = createMemo(() => + Math.max(2, dimensions().width - 8 - Bun.stringWidth(props.api.app.version) - mcpWidth()), + ) return ( - + diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 2742e183b173..8359749eece3 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1394,6 +1394,7 @@ function RevertMessage(props: { readonly deletions: number }> }) { + const ctx = use() const { theme } = useTheme() const route = useRouteData("session") const sdk = useSDK() @@ -1436,7 +1437,17 @@ function RevertMessage(props: { {(file) => ( {statusLabel(file.status)} - + 0 ? Bun.stringWidth(`+${file.additions}`) + 1 : 0) - + (file.deletions > 0 ? Bun.stringWidth(`-${file.deletions}`) + 1 : 0), + )} + fg={theme.text} + /> 0}> +{file.additions} @@ -2128,6 +2139,7 @@ export function InlineToolRow(props: { function BlockTool(props: { title?: string + path?: { label: string; value: string } children?: JSX.Element onClick?: () => void part?: SessionMessageAssistantTool @@ -2161,18 +2173,45 @@ function BlockTool(props: { props.onClick?.() }} > - - {(title) => ( - - {title()} - - } - > - {title().replace(/^# /, "")} + + {(title) => ( + + {title()} + + } + > + {title().replace(/^# /, "")} + + )} + } + > + {(path) => ( + + + {path().label} + + } + > + + {path().label.replace(/^# /, "")} + + + + )} {props.children} @@ -2267,7 +2306,10 @@ function Write(props: ToolProps) { return ( - + {(item) => ( - + @@ -2566,7 +2609,10 @@ function ApplyPatch(props: ToolProps) { {(file) => ( @@ -2600,7 +2646,10 @@ function ApplyPatch(props: ToolProps) { {(file) => ( {file.resource} @@ -2611,9 +2660,17 @@ function ApplyPatch(props: ToolProps) { Date: Sat, 11 Jul 2026 12:59:43 -0400 Subject: [PATCH 3/6] fix(tui): align file tool headers --- packages/tui/src/routes/session/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 8359749eece3..1d8fc0bf786f 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -2181,7 +2181,7 @@ function BlockTool(props: { + {title()} } @@ -2193,7 +2193,7 @@ function BlockTool(props: { } > {(path) => ( - + @@ -2663,7 +2663,7 @@ function ApplyPatch(props: ToolProps) { path={ targets().length === 1 ? { - label: props.part.state.status === "error" ? "# Patch failed" : "# Preparing patch", + label: props.part.state.status === "error" ? "# Patch failed" : "Patching", value: pathFormatter.format(targets()[0]), } : undefined @@ -2673,7 +2673,7 @@ function ApplyPatch(props: ToolProps) { ? undefined : props.part.state.status === "error" ? "# Patch failed" - : "# Preparing patch..." + : "Patching" } part={props.part} spinner={props.part.state.status === "streaming"} From 2db90cb79285d741510d0a8a832c10b5fb96427c Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 11 Jul 2026 13:09:38 -0400 Subject: [PATCH 4/6] fix(tui): compact sidebar project paths --- packages/tui/src/feature-plugins/sidebar/footer.tsx | 13 +++---------- packages/tui/src/ui/file-path.tsx | 13 ++++++++++++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/tui/src/feature-plugins/sidebar/footer.tsx b/packages/tui/src/feature-plugins/sidebar/footer.tsx index 6fb51ffafc91..406b1e2e9684 100644 --- a/packages/tui/src/feature-plugins/sidebar/footer.tsx +++ b/packages/tui/src/feature-plugins/sidebar/footer.tsx @@ -3,6 +3,7 @@ import type { BuiltinTuiPlugin } from "../builtins" import { createMemo, Show } from "solid-js" import { abbreviateHome } from "../../runtime" import { useTuiPaths } from "../../context/runtime" +import { FilePath } from "../../ui/file-path" const id = "internal:sidebar-footer" @@ -19,12 +20,7 @@ function View(props: { api: TuiPluginApi; directory: string }) { const path = createMemo(() => { const out = abbreviateHome(props.directory, paths.home) const branch = props.directory === props.api.state.path.directory ? props.api.state.vcs?.branch : undefined - const text = branch ? out + ":" + branch : out - const list = text.split("/") - return { - parent: list.slice(0, -1).join("/"), - name: list.at(-1) ?? "", - } + return branch ? out + ":" + branch : out }) return ( @@ -62,10 +58,7 @@ function View(props: { api: TuiPluginApi; directory: string }) { - - {path().parent}/ - {path().name} - + Open diff --git a/packages/tui/src/ui/file-path.tsx b/packages/tui/src/ui/file-path.tsx index b5adf67b6a0d..7a3304a7c8c5 100644 --- a/packages/tui/src/ui/file-path.tsx +++ b/packages/tui/src/ui/file-path.tsx @@ -1,4 +1,5 @@ import type { RGBA } from "@opentui/core" +import { createMemo } from "solid-js" const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" }) @@ -6,12 +7,22 @@ export interface FilePathProps { value: string maxWidth: number fg?: RGBA + basenameFg?: RGBA } export function FilePath(props: FilePathProps) { + const display = createMemo(() => { + const value = truncateFilePath(props.value, props.maxWidth) + const index = Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\")) + return { + parent: value.slice(0, index + 1), + basename: value.slice(index + 1), + } + }) return ( - {truncateFilePath(props.value, props.maxWidth)} + {display().parent} + {display().basename} ) } From de1196fa8b625347c8f333c7bb9b5519b208ecb5 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 11 Jul 2026 13:38:13 -0400 Subject: [PATCH 5/6] fix(tui): harden semantic path rendering --- .../dialog-workspace-file-changes.tsx | 7 ++++-- .../tui/src/feature-plugins/home/footer.tsx | 23 +++++++++++++++---- .../src/feature-plugins/sidebar/footer.tsx | 21 +++++++++++++---- packages/tui/src/routes/session/index.tsx | 8 +++++-- packages/tui/src/ui/file-path.tsx | 21 ++++++++++++----- packages/tui/test/ui/file-path.test.ts | 7 +++++- 6 files changed, 68 insertions(+), 19 deletions(-) diff --git a/packages/tui/src/component/dialog-workspace-file-changes.tsx b/packages/tui/src/component/dialog-workspace-file-changes.tsx index 24a46de4529f..8918a2e7e8a0 100644 --- a/packages/tui/src/component/dialog-workspace-file-changes.tsx +++ b/packages/tui/src/component/dialog-workspace-file-changes.tsx @@ -1,5 +1,5 @@ import { TextAttributes } from "@opentui/core" -import { useKeyboard } from "@opentui/solid" +import { useKeyboard, useTerminalDimensions } from "@opentui/solid" import type { VcsFileStatus } from "@opencode-ai/sdk/v2" import { createMemo, For } from "solid-js" import { createStore } from "solid-js/store" @@ -33,10 +33,13 @@ export function DialogWorkspaceFileChanges(props: { const dialog = useDialog() const { theme } = useTheme() const tuiConfig = useTuiConfig() + const dimensions = useTerminalDimensions() const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig)) const [store, setStore] = createStore({ active: "yes" as WorkspaceFileChangesChoice }) const height = createMemo(() => Math.min(props.files.length, 8)) - const fileNameWidth = createMemo(() => 48 - Math.max(Math.max(7, ...props.files.map(changeCountWidth)) - 7, 0)) + const fileNameWidth = createMemo( + () => Math.max(2, Math.min(60, dimensions().width - 2) - 6 - Math.max(7, ...props.files.map(changeCountWidth))), + ) function confirm() { props.onSelect(store.active) diff --git a/packages/tui/src/feature-plugins/home/footer.tsx b/packages/tui/src/feature-plugins/home/footer.tsx index 5ed139b0eefb..af1277b5c217 100644 --- a/packages/tui/src/feature-plugins/home/footer.tsx +++ b/packages/tui/src/feature-plugins/home/footer.tsx @@ -16,16 +16,31 @@ function Directory(props: { api: TuiPluginApi; maxWidth: number }) { const dir = createMemo(() => { const selected = destination?.destination() if (!selected || selected.type === "new") return - const out = abbreviateHome(selected.directory, paths.home) const branch = selected.directory === (props.api.state.path.directory || paths.cwd) ? props.api.state.vcs?.branch : undefined - if (branch) return out + ":" + branch - return out + return { path: abbreviateHome(selected.directory, paths.home), branch } }) return ( - {(value) => } + {(value) => { + const suffix = () => (value().branch ? `:${value().branch}` : "") + const suffixWidth = () => Math.min(Bun.stringWidth(suffix()), Math.max(0, props.maxWidth - 2)) + return ( + + + + + {suffix()} + + + + ) + }} ) } diff --git a/packages/tui/src/feature-plugins/sidebar/footer.tsx b/packages/tui/src/feature-plugins/sidebar/footer.tsx index 406b1e2e9684..b0fb68115354 100644 --- a/packages/tui/src/feature-plugins/sidebar/footer.tsx +++ b/packages/tui/src/feature-plugins/sidebar/footer.tsx @@ -17,11 +17,12 @@ function View(props: { api: TuiPluginApi; directory: string }) { ) const done = createMemo(() => props.api.kv.get("dismissed_getting_started", false)) const show = createMemo(() => !has() && !done()) - const path = createMemo(() => { - const out = abbreviateHome(props.directory, paths.home) + const location = createMemo(() => { const branch = props.directory === props.api.state.path.directory ? props.api.state.vcs?.branch : undefined - return branch ? out + ":" + branch : out + return { path: abbreviateHome(props.directory, paths.home), branch } }) + const suffix = createMemo(() => (location().branch ? `:${location().branch}` : "")) + const suffixWidth = createMemo(() => Math.min(Bun.stringWidth(suffix()), 36)) return ( @@ -58,7 +59,19 @@ function View(props: { api: TuiPluginApi; directory: string }) { - + + + + + {suffix()} + + + Open diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index 1d8fc0bf786f..b008c48cdc31 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -2652,7 +2652,11 @@ function ApplyPatch(props: ToolProps) { }} part={props.part} > - {file.resource} + )} @@ -2676,7 +2680,7 @@ function ApplyPatch(props: ToolProps) { : "Patching" } part={props.part} - spinner={props.part.state.status === "streaming"} + spinner={props.part.state.status === "streaming" || props.part.state.status === "running"} /> diff --git a/packages/tui/src/ui/file-path.tsx b/packages/tui/src/ui/file-path.tsx index 7a3304a7c8c5..0633e2aef4c1 100644 --- a/packages/tui/src/ui/file-path.tsx +++ b/packages/tui/src/ui/file-path.tsx @@ -31,10 +31,19 @@ export function truncateFilePath(value: string, maxWidth: number) { if (maxWidth <= 0) return "" if (Bun.stringWidth(value) <= maxWidth) return value - const separator = value.includes("/") ? "/" : "\\" - const drive = separator === "\\" ? value.match(/^[A-Za-z]:\\/)?.[0] : undefined - const root = drive ?? (value.startsWith(separator) ? separator : "") - const segments = value.slice(root.length).split(separator).filter(Boolean) + const drive = value.match(/^([A-Za-z]:)([\\/])/) + const unc = value.match(/^(\\\\|\/\/)([^\\/]+)[\\/]([^\\/]+)(?:[\\/]|$)/) + const windows = drive !== null || unc !== null || (!value.includes("/") && value.includes("\\")) + const separator = drive?.[2] ?? (unc?.[1] === "//" ? "/" : windows ? "\\" : "/") + const root = drive + ? drive[1] + separator + : unc + ? unc[1] + unc[2] + separator + unc[3] + separator + : value.startsWith("/") + ? "/" + : "" + const source = value.slice(drive?.[0].length ?? unc?.[0].length ?? root.length) + const segments = source.split(windows ? /[\\/]/ : separator).filter(Boolean) const basename = segments.at(-1) ?? value if (segments.length < 2) { const rootWidth = Bun.stringWidth(root) @@ -42,9 +51,9 @@ export function truncateFilePath(value: string, maxWidth: number) { return root + truncateBasename(basename, maxWidth - rootWidth) } - const prefix = `…${separator}` + const prefix = `${root}…${separator}` const basenameWidth = maxWidth - Bun.stringWidth(prefix) - if (basenameWidth <= 0) return takeStart("…", maxWidth) + if (basenameWidth <= 0) return takeStart(prefix, maxWidth) const compact = truncateBasename(basename, basenameWidth) if (compact !== basename) return prefix + compact diff --git a/packages/tui/test/ui/file-path.test.ts b/packages/tui/test/ui/file-path.test.ts index 8b3b198fcee5..05d2dbe3e264 100644 --- a/packages/tui/test/ui/file-path.test.ts +++ b/packages/tui/test/ui/file-path.test.ts @@ -27,9 +27,14 @@ describe("truncateFilePath", () => { expect(truncateFilePath("dir/file\\name.ts", 14)).toBe("…/file\\name.ts") }) - test("preserves root-level absolute paths", () => { + test("preserves absolute roots", () => { expect(truncateFilePath("/file.ts", 7)).toBe("/fi….ts") expect(truncateFilePath("C:\\file.ts", 9)).toBe("C:\\fi….ts") + expect(truncateFilePath("/usr/local/bin/file.ts", 14)).toBe("/…/bin/file.ts") + expect(truncateFilePath("C:\\Users\\kit\\src\\file.ts", 16)).toBe("C:\\…\\src\\file.ts") + expect(truncateFilePath("C:/Users/kit/src/file.ts", 16)).toBe("C:/…/src/file.ts") + expect(truncateFilePath("C:\\Users\\kit/src/file.ts", 16)).toBe("C:\\…\\src\\file.ts") + expect(truncateFilePath("\\\\server\\share\\src\\file.ts", 25)).toBe("\\\\server\\share\\…\\file.ts") }) test("measures terminal columns without splitting graphemes", () => { From ea16d918687c18e9137f40812958b93acd31981d Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 11 Jul 2026 13:45:53 -0400 Subject: [PATCH 6/6] refactor(tui): hide sidebar session id --- packages/tui/src/routes/session/sidebar.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/tui/src/routes/session/sidebar.tsx b/packages/tui/src/routes/session/sidebar.tsx index bd415768a25e..a0282007006b 100644 --- a/packages/tui/src/routes/session/sidebar.tsx +++ b/packages/tui/src/routes/session/sidebar.tsx @@ -3,7 +3,7 @@ import { useData } from "../../context/data" import { createMemo, Show } from "solid-js" import { useTheme } from "../../context/theme" import { useTuiConfig } from "../../config" -import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version" +import { InstallationVersion } from "@opencode-ai/core/installation/version" import { usePluginRuntime } from "../../plugin/runtime" import { getScrollAcceleration } from "../../util/scroll" @@ -56,9 +56,6 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { {session()!.title} - - {props.sessionID} -