From 7bb365fd16eb18ab2204c7d8685aff5ff0a3cba8 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 e35bf86d858d1ff1d0c5fce0d98041aa746224d6 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]); +}