From 3f9bdb62837b64a9bfa3daa6eb9b5097e4e53961 Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Thu, 21 May 2026 05:20:34 +0000 Subject: [PATCH 1/2] fix(task-input): preserve picked folder when adding to recents The useEffect that syncs selectedDirectory from view.folderId was re-running whenever the folders list changed. Picking a new folder via "Open folder..." triggers addFolder, which invalidates getFolders, which refetches folders, which fires this effect and reverts selectedDirectory back to the original view.folderId folder. Track the last applied folderId in a ref so the effect only syncs when view.folderId actually changes, not on every folders refetch. Generated-By: PostHog Code Task-Id: 0dcecc53-ea9a-4a39-8a6e-63973bea25bb --- .../features/task-detail/components/TaskInput.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx index eb00b588c..7872b6d6e 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx @@ -401,12 +401,17 @@ export function TaskInput({ setLastUsedCloudRepository, ]); + const lastInitializedFolderIdRef = useRef(undefined); useEffect(() => { - if (view.folderId) { - const folder = folders.find((f) => f.id === view.folderId); - if (folder) { - setSelectedDirectory(folder.path); - } + if (!view.folderId) { + lastInitializedFolderIdRef.current = undefined; + return; + } + if (lastInitializedFolderIdRef.current === view.folderId) return; + const folder = folders.find((f) => f.id === view.folderId); + if (folder) { + setSelectedDirectory(folder.path); + lastInitializedFolderIdRef.current = view.folderId; } }, [view.folderId, folders]); From 1e60c03222fdeb2cc4342c4f6b114f68d959e377 Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Thu, 21 May 2026 05:39:21 +0000 Subject: [PATCH 2/2] test(task-input): cover folder-id sync regression Extract the folder-id-to-directory sync into useInitialDirectoryFromFolderId so the contract can be tested in isolation: the hook syncs once when the folder list resolves, ignores later folders refetches (the case that broke the picker), and re-syncs only when folderId changes. Generated-By: PostHog Code Task-Id: 0dcecc53-ea9a-4a39-8a6e-63973bea25bb --- .../task-detail/components/TaskInput.tsx | 15 +--- .../useInitialDirectoryFromFolderId.test.ts | 89 +++++++++++++++++++ .../hooks/useInitialDirectoryFromFolderId.ts | 29 ++++++ 3 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 apps/code/src/renderer/features/task-detail/hooks/useInitialDirectoryFromFolderId.test.ts create mode 100644 apps/code/src/renderer/features/task-detail/hooks/useInitialDirectoryFromFolderId.ts diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx index 7872b6d6e..eed4f7a8c 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx @@ -48,6 +48,7 @@ import { useQuery } from "@tanstack/react-query"; import { FOCUSABLE_SELECTOR } from "@utils/overlay"; import { LayoutGroup, motion } from "framer-motion"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useInitialDirectoryFromFolderId } from "../hooks/useInitialDirectoryFromFolderId"; import { usePreviewConfig } from "../hooks/usePreviewConfig"; import { useTaskCreation } from "../hooks/useTaskCreation"; import { CloudGithubMissingNotice } from "./CloudGithubMissingNotice"; @@ -401,19 +402,7 @@ export function TaskInput({ setLastUsedCloudRepository, ]); - const lastInitializedFolderIdRef = useRef(undefined); - useEffect(() => { - if (!view.folderId) { - lastInitializedFolderIdRef.current = undefined; - return; - } - if (lastInitializedFolderIdRef.current === view.folderId) return; - const folder = folders.find((f) => f.id === view.folderId); - if (folder) { - setSelectedDirectory(folder.path); - lastInitializedFolderIdRef.current = view.folderId; - } - }, [view.folderId, folders]); + useInitialDirectoryFromFolderId(view.folderId, folders, setSelectedDirectory); useEffect(() => { setCloudBranchSearchQuery(""); diff --git a/apps/code/src/renderer/features/task-detail/hooks/useInitialDirectoryFromFolderId.test.ts b/apps/code/src/renderer/features/task-detail/hooks/useInitialDirectoryFromFolderId.test.ts new file mode 100644 index 000000000..37a5a62ae --- /dev/null +++ b/apps/code/src/renderer/features/task-detail/hooks/useInitialDirectoryFromFolderId.test.ts @@ -0,0 +1,89 @@ +import type { RegisteredFolder } from "@main/services/folders/schemas"; +import { renderHook } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { useInitialDirectoryFromFolderId } from "./useInitialDirectoryFromFolderId"; + +const folder = (id: string, path: string): RegisteredFolder => ({ + id, + path, + name: id, + remoteUrl: null, + lastAccessed: "2026-05-21T00:00:00Z", + createdAt: "2026-05-21T00:00:00Z", +}); + +describe("useInitialDirectoryFromFolderId", () => { + it("syncs the directory to the folder matching folderId on first render", () => { + const setSelectedDirectory = vi.fn(); + renderHook(() => + useInitialDirectoryFromFolderId( + "a", + [folder("a", "/repos/a")], + setSelectedDirectory, + ), + ); + expect(setSelectedDirectory).toHaveBeenCalledExactlyOnceWith("/repos/a"); + }); + + it("waits for folders to load before syncing", () => { + const setSelectedDirectory = vi.fn(); + const { rerender } = renderHook( + ({ folders }: { folders: RegisteredFolder[] }) => + useInitialDirectoryFromFolderId("a", folders, setSelectedDirectory), + { initialProps: { folders: [] as RegisteredFolder[] } }, + ); + expect(setSelectedDirectory).not.toHaveBeenCalled(); + + rerender({ folders: [folder("a", "/repos/a")] }); + expect(setSelectedDirectory).toHaveBeenCalledExactlyOnceWith("/repos/a"); + }); + + it("does not re-sync when folders changes but folderId stays the same", () => { + const setSelectedDirectory = vi.fn(); + const { rerender } = renderHook( + ({ folders }: { folders: RegisteredFolder[] }) => + useInitialDirectoryFromFolderId("a", folders, setSelectedDirectory), + { initialProps: { folders: [folder("a", "/repos/a")] } }, + ); + expect(setSelectedDirectory).toHaveBeenCalledExactlyOnceWith("/repos/a"); + + // Simulate adding a folder (e.g. after the user picks one via "Open + // folder..."). The folders list changes but the user's pick must not be + // clobbered by re-syncing from the original folderId. + rerender({ + folders: [folder("a", "/repos/a"), folder("b", "/repos/picked")], + }); + expect(setSelectedDirectory).toHaveBeenCalledTimes(1); + }); + + it("re-syncs when folderId changes", () => { + const setSelectedDirectory = vi.fn(); + const folders = [folder("a", "/repos/a"), folder("b", "/repos/b")]; + const { rerender } = renderHook( + ({ folderId }: { folderId: string }) => + useInitialDirectoryFromFolderId( + folderId, + folders, + setSelectedDirectory, + ), + { initialProps: { folderId: "a" } }, + ); + expect(setSelectedDirectory).toHaveBeenLastCalledWith("/repos/a"); + + rerender({ folderId: "b" }); + expect(setSelectedDirectory).toHaveBeenLastCalledWith("/repos/b"); + expect(setSelectedDirectory).toHaveBeenCalledTimes(2); + }); + + it("does nothing when folderId is undefined", () => { + const setSelectedDirectory = vi.fn(); + renderHook(() => + useInitialDirectoryFromFolderId( + undefined, + [folder("a", "/repos/a")], + setSelectedDirectory, + ), + ); + expect(setSelectedDirectory).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/code/src/renderer/features/task-detail/hooks/useInitialDirectoryFromFolderId.ts b/apps/code/src/renderer/features/task-detail/hooks/useInitialDirectoryFromFolderId.ts new file mode 100644 index 000000000..dab03d91c --- /dev/null +++ b/apps/code/src/renderer/features/task-detail/hooks/useInitialDirectoryFromFolderId.ts @@ -0,0 +1,29 @@ +import type { RegisteredFolder } from "@main/services/folders/schemas"; +import { useEffect, useRef } from "react"; + +/** + * Syncs `selectedDirectory` to the path of `folders[view.folderId]` once per + * folderId. The dependency on `folders` is required so the sync still fires + * when the folder list hasn't loaded yet on initial mount, but we must not + * re-sync on later `folders` refetches (e.g. after `addFolder`) — that would + * clobber a folder the user just picked via the file dialog. + */ +export function useInitialDirectoryFromFolderId( + folderId: string | undefined, + folders: RegisteredFolder[], + setSelectedDirectory: (path: string) => void, +) { + const lastInitializedRef = useRef(undefined); + useEffect(() => { + if (!folderId) { + lastInitializedRef.current = undefined; + return; + } + if (lastInitializedRef.current === folderId) return; + const folder = folders.find((f) => f.id === folderId); + if (folder) { + setSelectedDirectory(folder.path); + lastInitializedRef.current = folderId; + } + }, [folderId, folders, setSelectedDirectory]); +}