From 13ef9c60f18f1bbb7f748126c14f3fb48e596b93 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 9 Nov 2026 10:54:09 -0700 Subject: [PATCH] Fix review findings in the flow list polish Search actions keep the editor focused so a wrapped box does not collapse under the click. Header scrolls from focus move the rows too. Timeline details always show their tooltip. A sidebar row shared by same-named folders or programs in different places offers no path rather than an arbitrary one. Co-Authored-By: Claude Opus 5.5 --- apps/yaak-proxy/components/FlowList.tsx | 9 +++++++++ apps/yaak-proxy/components/FlowTimeline.tsx | 1 - apps/yaak-proxy/components/SearchBar.tsx | 8 +++++++- apps/yaak-proxy/lib/sidebarTree.test.ts | 18 ++++++++++++++++++ apps/yaak-proxy/lib/sidebarTree.ts | 15 +++++++++++++-- 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/apps/yaak-proxy/components/FlowList.tsx b/apps/yaak-proxy/components/FlowList.tsx index 383fe64aa..b4f42b7bb 100644 --- a/apps/yaak-proxy/components/FlowList.tsx +++ b/apps/yaak-proxy/components/FlowList.tsx @@ -215,6 +215,14 @@ export function FlowList({ style, className }: Props) { } }, [hold, loadOlder, setFollowing, ROW_HEIGHT]); + // Focus moving to a sort button out of view scrolls the header itself; the rows follow. + const handleHeaderScroll = useCallback(() => { + const header = headerScrollRef.current; + const el = scrollRef.current; + if (header == null || el == null || header.scrollLeft === el.scrollLeft) return; + el.scrollLeft = header.scrollLeft; + }, []); + // The header scrolls the list sideways, as it has no scrollbar of its own. const handleHeaderWheel = useCallback((e: WheelEvent) => { const el = scrollRef.current; @@ -307,6 +315,7 @@ export function FlowList({ style, className }: Props) {
diff --git a/apps/yaak-proxy/components/FlowTimeline.tsx b/apps/yaak-proxy/components/FlowTimeline.tsx index 064baef73..51e93bb56 100644 --- a/apps/yaak-proxy/components/FlowTimeline.tsx +++ b/apps/yaak-proxy/components/FlowTimeline.tsx @@ -42,7 +42,6 @@ export function FlowTimeline({ flow }: { flow: Flow }) { ) : ( diff --git a/apps/yaak-proxy/components/SearchBar.tsx b/apps/yaak-proxy/components/SearchBar.tsx index 77c45fe94..ff7dba372 100644 --- a/apps/yaak-proxy/components/SearchBar.tsx +++ b/apps/yaak-proxy/components/SearchBar.tsx @@ -165,7 +165,13 @@ export const SearchBar = memo(function SearchBar() { ⌘F ) : ( -
+ // oxlint-disable-next-line jsx-a11y/no-static-element-interactions -- keeps focus in the editor +
e.preventDefault()} + className="flex items-center self-center ml-1 mr-0.5" + > { }); }); +test("a row shared by folders or programs in different places names no path", () => { + const tool = { clientName: "claude", clientPath: "/usr/local/bin/claude" }; + const items = itemsById( + buildSidebarTree( + [ + flow("https://a.com/", { ...tool, clientCwd: "/src/yaak" }), + flow("https://b.com/", { ...tool, clientCwd: "/worktrees/123/yaak" }), + flow("https://c.com/", { ...tool, clientPath: "/opt/bin/claude", clientCwd: "/src/ml" }), + ], + 3, + ), + ); + expect(items.get("app:claude|cwd:yaak")?.kind).toBe("folder"); + expect(items.get("app:claude|cwd:yaak")?.path).toBeUndefined(); + expect(items.get("app:claude|cwd:ml")?.path).toBe("/src/ml"); + expect(items.get("app:claude")?.path).toBeUndefined(); +}); + test("an app in a bundle has no path to reveal", () => { const items = itemsById(buildSidebarTree([flow("https://slack.com/", slack)], 1)); expect(items.get("app:Slack")?.path).toBeUndefined(); diff --git a/apps/yaak-proxy/lib/sidebarTree.ts b/apps/yaak-proxy/lib/sidebarTree.ts index ff16027f2..b8728fc0f 100644 --- a/apps/yaak-proxy/lib/sidebarTree.ts +++ b/apps/yaak-proxy/lib/sidebarTree.ts @@ -166,6 +166,11 @@ function appQueryFor(app: string, system: boolean): string { return system ? `${fieldTerm("is", "system")} ${query}` : query; } +/** A program outside an app bundle: its executable, for Reveal in Finder */ +function programPath(flow: Flow): string | undefined { + return flow.clientBundleId == null ? (flow.clientPath ?? undefined) : undefined; +} + /** A node being built, its children by id */ interface Trie { item: SidebarItem; @@ -185,6 +190,9 @@ function child(parent: Trie, row: PlaceRow): Trie { if (row.decrypt != null) node.item.decrypt = row.decrypt; if (row.path != null) node.item.path = row.path; parent.children.set(row.id, node); + } else if (node.item.path !== row.path) { + // Folders of the same name in different places share a row, which then names no one path. + delete node.item.path; } node.item.count++; return node; @@ -272,10 +280,13 @@ export function buildSidebarTree( } if (folder != null) item.sublabel = folder; if (flow.clientPath != null) item.appPath = flow.clientPath; - // A program outside an app bundle: its executable - if (flow.clientBundleId == null && flow.clientPath != null) item.path = flow.clientPath; + const program = programPath(flow); + if (program != null) item.path = program; app = { item, children: new Map() }; parent.children.set(id, app); + } else if (app.item.path !== programPath(flow)) { + // Programs of the same name in different places share a row, which names no one path. + delete app.item.path; } app.item.count++; parent.item.count++;