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 <noreply@anthropic.com>
This commit is contained in:
Gregory SchierandClaude Opus 5.5 authored and Stetta task tk_mw9j1flbfooxxx0a committed 2026-11-09 10:54:09 -07:00
1 parent c78732ecaf
commit 13ef9c60f1
5 files changed
+47 -4

No files matched your search

+9
View File
@@ -215,6 +215,14 @@ export function FlowList({ style, className }: Props) {
} }
}, [hold, loadOlder, setFollowing, ROW_HEIGHT]); }, [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. // The header scrolls the list sideways, as it has no scrollbar of its own.
const handleHeaderWheel = useCallback((e: WheelEvent<HTMLDivElement>) => { const handleHeaderWheel = useCallback((e: WheelEvent<HTMLDivElement>) => {
const el = scrollRef.current; const el = scrollRef.current;
@@ -307,6 +315,7 @@ export function FlowList({ style, className }: Props) {
<div <div
ref={headerScrollRef} ref={headerScrollRef}
onWheel={handleHeaderWheel} onWheel={handleHeaderWheel}
onScroll={handleHeaderScroll}
style={{ marginRight: scrollbarWidth }} style={{ marginRight: scrollbarWidth }}
className="overflow-hidden" className="overflow-hidden"
> >
@@ -42,7 +42,6 @@ export function FlowTimeline({ flow }: { flow: Flow }) {
) : ( ) : (
<Tooltip <Tooltip
wrap wrap
onlyWhenTruncated
content={event.detail} content={event.detail}
delay={CONTROL_TOOLTIP_DELAY} delay={CONTROL_TOOLTIP_DELAY}
> >
+7 -1
View File
@@ -165,7 +165,13 @@ export const SearchBar = memo(function SearchBar() {
⌘F ⌘F
</kbd> </kbd>
) : ( ) : (
<div className="flex items-center self-center ml-1 mr-0.5"> // oxlint-disable-next-line jsx-a11y/no-static-element-interactions -- keeps focus in the editor
<div
// Pressing these leaves the editor focused, so a wrapped box does not collapse and
// move them before the click lands.
onMouseDown={(e) => e.preventDefault()}
className="flex items-center self-center ml-1 mr-0.5"
>
<Dropdown <Dropdown
items={[ items={[
{ {
+18
View File
@@ -177,6 +177,24 @@ describe("programs outside an app", () => {
}); });
}); });
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", () => { test("an app in a bundle has no path to reveal", () => {
const items = itemsById(buildSidebarTree([flow("https://slack.com/", slack)], 1)); const items = itemsById(buildSidebarTree([flow("https://slack.com/", slack)], 1));
expect(items.get("app:Slack")?.path).toBeUndefined(); expect(items.get("app:Slack")?.path).toBeUndefined();
+13 -2
View File
@@ -166,6 +166,11 @@ function appQueryFor(app: string, system: boolean): string {
return system ? `${fieldTerm("is", "system")} ${query}` : query; 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 */ /** A node being built, its children by id */
interface Trie { interface Trie {
item: SidebarItem; item: SidebarItem;
@@ -185,6 +190,9 @@ function child(parent: Trie, row: PlaceRow): Trie {
if (row.decrypt != null) node.item.decrypt = row.decrypt; if (row.decrypt != null) node.item.decrypt = row.decrypt;
if (row.path != null) node.item.path = row.path; if (row.path != null) node.item.path = row.path;
parent.children.set(row.id, node); 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++; node.item.count++;
return node; return node;
@@ -272,10 +280,13 @@ export function buildSidebarTree(
} }
if (folder != null) item.sublabel = folder; if (folder != null) item.sublabel = folder;
if (flow.clientPath != null) item.appPath = flow.clientPath; if (flow.clientPath != null) item.appPath = flow.clientPath;
// A program outside an app bundle: its executable const program = programPath(flow);
if (flow.clientBundleId == null && flow.clientPath != null) item.path = flow.clientPath; if (program != null) item.path = program;
app = { item, children: new Map() }; app = { item, children: new Map() };
parent.children.set(id, app); 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++; app.item.count++;
parent.item.count++; parent.item.count++;