From 33339fe741dd84f1ee45991a9f72f3ec69334212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Andr=C3=A9?= Date: Tue, 28 Jul 2026 13:34:04 +0200 Subject: [PATCH 1/2] fix(ui): keep relevant file path segments visible Render mention-picker file results with the filename on its own line and an abbreviated first-folder/parent context so deeply nested paths remain identifiable without horizontal scrolling. Keep selection values unchanged, expose the complete path to assistive technology and hover users, and preserve shared overflow behavior for command and agent results. Add focused path-splitting coverage to the PR UI test job. Validated with workspace typecheck, UI production build, the focused Node test, and repeated Gatekeeper review. --- .github/workflows/pr-build.yml | 1 + .../components/unified-picker-path.test.ts | 24 +++++++++++++++++++ .../ui/src/components/unified-picker-path.ts | 11 +++++++++ packages/ui/src/components/unified-picker.tsx | 22 +++++++++++++++-- 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/ui/src/components/unified-picker-path.test.ts create mode 100644 packages/ui/src/components/unified-picker-path.ts diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 9ab747b1..28e5f98d 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -105,6 +105,7 @@ jobs: run: >- node --import tsx --test packages/ui/src/components/session-list-visibility.test.ts + packages/ui/src/components/unified-picker-path.test.ts packages/ui/src/lib/hooks/use-app-session-capture.test.ts packages/ui/src/lib/message-selection-position.test.ts packages/ui/src/lib/trailing-resync.test.ts diff --git a/packages/ui/src/components/unified-picker-path.test.ts b/packages/ui/src/components/unified-picker-path.test.ts new file mode 100644 index 00000000..e36ffa0e --- /dev/null +++ b/packages/ui/src/components/unified-picker-path.test.ts @@ -0,0 +1,24 @@ +import assert from "node:assert/strict" +import test from "node:test" +import { splitDisplayPath } from "./unified-picker-path" + +test("splits paths into file and abbreviated directory context", () => { + assert.deepEqual(splitDisplayPath("packages/ui/src/components/unified-picker.tsx"), { + name: "unified-picker.tsx", + start: "packages", + parent: "components", + hasMiddle: true, + }) + assert.deepEqual(splitDisplayPath("src/components/"), { + name: "components/", + start: "src", + parent: "", + hasMiddle: false, + }) + assert.deepEqual(splitDisplayPath("README.md"), { + name: "README.md", + start: "", + parent: "", + hasMiddle: false, + }) +}) diff --git a/packages/ui/src/components/unified-picker-path.ts b/packages/ui/src/components/unified-picker-path.ts new file mode 100644 index 00000000..25831cc9 --- /dev/null +++ b/packages/ui/src/components/unified-picker-path.ts @@ -0,0 +1,11 @@ +export function splitDisplayPath(path: string) { + const isDirectory = path.endsWith("/") + const parts = path.split("/").filter(Boolean) + const folders = parts.slice(0, -1) + return { + name: parts.length > 0 ? `${parts.at(-1)}${isDirectory ? "/" : ""}` : path, + start: folders[0] ?? "", + parent: folders.length > 1 ? folders.at(-1) ?? "" : "", + hasMiddle: folders.length > 2, + } +} diff --git a/packages/ui/src/components/unified-picker.tsx b/packages/ui/src/components/unified-picker.tsx index 6c9e5837..ae84fcdd 100644 --- a/packages/ui/src/components/unified-picker.tsx +++ b/packages/ui/src/components/unified-picker.tsx @@ -5,6 +5,7 @@ import type { OpencodeClient } from "@opencode-ai/sdk/v2/client" import { serverApi } from "../lib/api-client" import { useI18n } from "../lib/i18n" import { getLogger } from "../lib/logger" +import { splitDisplayPath } from "./unified-picker-path" const log = getLogger("actions") @@ -585,6 +586,7 @@ const UnifiedPicker: Component = (props) => { (item) => item.type === "file" && item.file.relativePath === file.relativePath, ) const isFolder = file.isDirectory + const displayPath = splitDisplayPath(file.path) return (
= (props) => { data-picker-selected={itemIndex === selectedIndex()} onClick={() => props.onSelect({ type: "file", file }, "click")} > -
+
= (props) => { /> - {file.path} + + {file.path} + +
) From ab556674f2b2fad014d81444694f80fca9d2577e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Andr=C3=A9?= Date: Tue, 28 Jul 2026 14:07:57 +0200 Subject: [PATCH 2/2] fix(ui): preserve complete picker path context Keep filename, parent, and root-aligned directory context independently visible while retaining the horizontally scrollable complete directory introduced by the large-workspace picker follow-up. Reset horizontal position when results change, avoid unsupported Array.at calls on older WKWebView releases, and keep the selected attachment payload unchanged. Validated with workspace typecheck, UI production build, focused path tests, diff checks, and Gatekeeper rounds through zero findings. --- .../components/unified-picker-path.test.ts | 13 +++++------ .../ui/src/components/unified-picker-path.ts | 7 +++--- packages/ui/src/components/unified-picker.tsx | 22 +++++++++---------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/packages/ui/src/components/unified-picker-path.test.ts b/packages/ui/src/components/unified-picker-path.test.ts index e36ffa0e..ed341d1a 100644 --- a/packages/ui/src/components/unified-picker-path.test.ts +++ b/packages/ui/src/components/unified-picker-path.test.ts @@ -2,23 +2,20 @@ import assert from "node:assert/strict" import test from "node:test" import { splitDisplayPath } from "./unified-picker-path" -test("splits paths into file and abbreviated directory context", () => { +test("splits paths into file and directory context", () => { assert.deepEqual(splitDisplayPath("packages/ui/src/components/unified-picker.tsx"), { name: "unified-picker.tsx", - start: "packages", parent: "components", - hasMiddle: true, + directory: "packages/ui/src/components", }) assert.deepEqual(splitDisplayPath("src/components/"), { name: "components/", - start: "src", - parent: "", - hasMiddle: false, + parent: "src", + directory: "src", }) assert.deepEqual(splitDisplayPath("README.md"), { name: "README.md", - start: "", parent: "", - hasMiddle: false, + directory: "", }) }) diff --git a/packages/ui/src/components/unified-picker-path.ts b/packages/ui/src/components/unified-picker-path.ts index 25831cc9..a8d92fce 100644 --- a/packages/ui/src/components/unified-picker-path.ts +++ b/packages/ui/src/components/unified-picker-path.ts @@ -3,9 +3,8 @@ export function splitDisplayPath(path: string) { const parts = path.split("/").filter(Boolean) const folders = parts.slice(0, -1) return { - name: parts.length > 0 ? `${parts.at(-1)}${isDirectory ? "/" : ""}` : path, - start: folders[0] ?? "", - parent: folders.length > 1 ? folders.at(-1) ?? "" : "", - hasMiddle: folders.length > 2, + name: parts.length > 0 ? `${parts[parts.length - 1]}${isDirectory ? "/" : ""}` : path, + parent: folders[folders.length - 1] ?? "", + directory: folders.join("/"), } } diff --git a/packages/ui/src/components/unified-picker.tsx b/packages/ui/src/components/unified-picker.tsx index ae84fcdd..5c7d74d5 100644 --- a/packages/ui/src/components/unified-picker.tsx +++ b/packages/ui/src/components/unified-picker.tsx @@ -115,6 +115,7 @@ const UnifiedPicker: Component = (props) => { setTimeout(() => { if (scrollContainerRef) { scrollContainerRef.scrollTop = 0 + scrollContainerRef.scrollLeft = 0 } }, 0) } @@ -595,7 +596,7 @@ const UnifiedPicker: Component = (props) => { data-picker-selected={itemIndex === selectedIndex()} onClick={() => props.onSelect({ type: "file", file }, "click")} > -
+
= (props) => { {file.path}