From d72a84ff40f4920198c4fdc72636660daf726d0e Mon Sep 17 00:00:00 2001 From: Zadak Date: Wed, 1 Jul 2026 16:04:02 +0200 Subject: [PATCH] fix(graph-ui): scope typed-path picker refresh to Windows drive paths The debounced folder-list refresh (and Enter-to-navigate) on the Repository path field now only fires for Windows drive paths (e.g. 'D:/'), where typing is the mechanism for switching drives. POSIX path navigation is left exactly as before. Add a regression test asserting a typed POSIX path does not trigger a re-browse. Signed-off-by: Zadak --- graph-ui/src/components/StatsTab.test.tsx | 20 ++++++++++++++++++++ graph-ui/src/components/StatsTab.tsx | 12 +++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/graph-ui/src/components/StatsTab.test.tsx b/graph-ui/src/components/StatsTab.test.tsx index 9b0aa41c2..00aa20378 100644 --- a/graph-ui/src/components/StatsTab.test.tsx +++ b/graph-ui/src/components/StatsTab.test.tsx @@ -187,6 +187,26 @@ describe("StatsTab index modal", () => { expect(fetchMock).toHaveBeenCalledWith("/api/browse?path=C%3A%2F"); }); }); + + it("does not auto-refresh on POSIX when a path is typed", async () => { + const fetchMock = mockProjectsFetch(); // browse returns POSIX path "/home/dev" + + render( {}} />); + fireEvent.click(await screen.findByRole("button", { name: "Index your first repository" })); + await screen.findByText("alpha"); // initial POSIX listing + + const browseCalls = () => + fetchMock.mock.calls.filter((c) => String(c[0]).startsWith("/api/browse")).length; + const before = browseCalls(); + + fireEvent.change(screen.getByLabelText("Repository path"), { + target: { value: "/usr/local" }, + }); + + /* Wait past the debounce window; a POSIX path must NOT trigger a re-browse. */ + await new Promise((r) => setTimeout(r, 400)); + expect(browseCalls()).toBe(before); + }); }); describe("IndexProgress", () => { diff --git a/graph-ui/src/components/StatsTab.tsx b/graph-ui/src/components/StatsTab.tsx index 7611fec64..62abe7aab 100644 --- a/graph-ui/src/components/StatsTab.tsx +++ b/graph-ui/src/components/StatsTab.tsx @@ -208,12 +208,14 @@ function CreateIndexModal({ onClose, onCreated }: { onClose: () => void; onCreat useEffect(() => { browse(); }, [browse]); useEffect(() => { filterRef.current?.focus(); }, []); - /* When the user types a path directly into the Repository path field, refresh - * the folder listing to match (debounced). Without this the breadcrumb and - * path box updated but the directory list stayed stale — e.g. typing "D:/" - * still showed the previous location's folders. */ + /* Windows only: when the user types a drive path into the Repository path + * field, refresh the folder listing to match (debounced). On Windows, typing + * is the way to switch drives, and without this the breadcrumb and path box + * updated but the directory list stayed stale (e.g. typing "D:/" still showed + * the previous drive's folders). POSIX navigation is left unchanged. */ useEffect(() => { if (!currentPath || currentPath === lastBrowsedRef.current) return; + if (!/^[A-Za-z]:/.test(currentPath.replace(/\\/g, "/"))) return; const id = setTimeout(() => { void browse(currentPath, { silent: true }); }, 350); return () => clearTimeout(id); }, [currentPath, browse]); @@ -300,7 +302,7 @@ function CreateIndexModal({ onClose, onCreated }: { onClose: () => void; onCreat aria-label={t.index.repositoryPath} value={currentPath} onChange={(e) => setCurrentPath(e.target.value)} - onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); void browse(currentPath); } }} + onKeyDown={(e) => { if (e.key === "Enter" && /^[A-Za-z]:/.test(currentPath.replace(/\\/g, "/"))) { e.preventDefault(); void browse(currentPath); } }} className="w-full bg-white/[0.04] border border-white/[0.06] rounded-lg px-3 py-2 text-[12px] text-foreground font-mono outline-none focus:border-primary/40" />