mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-11-13 12:13:42 +01:00
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:
1 parent
c78732ecaf
commit
13ef9c60f1
5 files changed
+47
-4
No files matched your search
@@ -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<HTMLDivElement>) => {
|
||||
const el = scrollRef.current;
|
||||
@@ -307,6 +315,7 @@ export function FlowList({ style, className }: Props) {
|
||||
<div
|
||||
ref={headerScrollRef}
|
||||
onWheel={handleHeaderWheel}
|
||||
onScroll={handleHeaderScroll}
|
||||
style={{ marginRight: scrollbarWidth }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
|
||||
@@ -42,7 +42,6 @@ export function FlowTimeline({ flow }: { flow: Flow }) {
|
||||
) : (
|
||||
<Tooltip
|
||||
wrap
|
||||
onlyWhenTruncated
|
||||
content={event.detail}
|
||||
delay={CONTROL_TOOLTIP_DELAY}
|
||||
>
|
||||
|
||||
@@ -165,7 +165,13 @@ export const SearchBar = memo(function SearchBar() {
|
||||
⌘F
|
||||
</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
|
||||
items={[
|
||||
{
|
||||
|
||||
@@ -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", () => {
|
||||
const items = itemsById(buildSidebarTree([flow("https://slack.com/", slack)], 1));
|
||||
expect(items.get("app:Slack")?.path).toBeUndefined();
|
||||
|
||||
@@ -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++;
|
||||
|
||||
Reference in new issue
Block a user