From 56023ccf1440f545ebb4f90d117e493eb2342473 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 9 Mar 2026 09:02:56 -0700 Subject: [PATCH 1/4] Open folder picker immediately on desktop Electron - add `shouldOpenProjectFolderPickerImmediately` to centralize Sidebar behavior - trigger folder browse directly when adding a project in desktop Electron - keep manual path entry for mobile layouts and non-Electron, with tests for all cases --- apps/web/src/components/Sidebar.logic.test.ts | 31 ++++++++++++++++ apps/web/src/components/Sidebar.logic.ts | 6 ++++ apps/web/src/components/Sidebar.tsx | 35 ++++++++++++++----- 3 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 apps/web/src/components/Sidebar.logic.test.ts create mode 100644 apps/web/src/components/Sidebar.logic.ts diff --git a/apps/web/src/components/Sidebar.logic.test.ts b/apps/web/src/components/Sidebar.logic.test.ts new file mode 100644 index 000000000000..5bf53f25fd27 --- /dev/null +++ b/apps/web/src/components/Sidebar.logic.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from "vitest"; +import { shouldOpenProjectFolderPickerImmediately } from "./Sidebar.logic"; + +describe("shouldOpenProjectFolderPickerImmediately", () => { + it("opens the folder picker immediately in Electron on desktop", () => { + expect( + shouldOpenProjectFolderPickerImmediately({ + isElectron: true, + isMobile: false, + }), + ).toBe(true); + }); + + it("keeps manual project entry on mobile Electron layouts", () => { + expect( + shouldOpenProjectFolderPickerImmediately({ + isElectron: true, + isMobile: true, + }), + ).toBe(false); + }); + + it("keeps manual project entry outside Electron", () => { + expect( + shouldOpenProjectFolderPickerImmediately({ + isElectron: false, + isMobile: false, + }), + ).toBe(false); + }); +}); diff --git a/apps/web/src/components/Sidebar.logic.ts b/apps/web/src/components/Sidebar.logic.ts new file mode 100644 index 000000000000..9fa649bc0171 --- /dev/null +++ b/apps/web/src/components/Sidebar.logic.ts @@ -0,0 +1,6 @@ +export function shouldOpenProjectFolderPickerImmediately(input: { + isElectron: boolean; + isMobile: boolean; +}): boolean { + return input.isElectron && !input.isMobile; +} diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 894fde25e916..67d5feccb007 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -32,6 +32,7 @@ import { type Thread } from "../types"; import { derivePendingApprovals } from "../session-logic"; import { gitRemoveWorktreeMutationOptions, gitStatusQueryOptions } from "../lib/gitReactQuery"; import { serverConfigQueryOptions } from "../lib/serverReactQuery"; +import { useMediaQuery } from "../hooks/useMediaQuery"; import { readNativeApi } from "../nativeApi"; import { type DraftThreadEnvMode, useComposerDraftStore } from "../composerDraftStore"; import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore"; @@ -49,6 +50,7 @@ import { } from "./desktopUpdate.logic"; import { Alert, AlertAction, AlertDescription, AlertTitle } from "./ui/alert"; import { Button } from "./ui/button"; +import { shouldOpenProjectFolderPickerImmediately } from "./Sidebar.logic"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; import { @@ -266,6 +268,7 @@ function ProjectFavicon({ cwd }: { cwd: string }) { } export default function Sidebar() { + const isMobile = useMediaQuery("(max-width: 767px)"); const projects = useStore((store) => store.projects); const threads = useStore((store) => store.threads); const markThreadUnread = useStore((store) => store.markThreadUnread); @@ -312,6 +315,11 @@ export default function Sidebar() { const renamingCommittedRef = useRef(false); const renamingInputRef = useRef(null); const [desktopUpdateState, setDesktopUpdateState] = useState(null); + const shouldBrowseForProjectImmediately = shouldOpenProjectFolderPickerImmediately({ + isElectron, + isMobile, + }); + const shouldShowProjectPathEntry = addingProject && !shouldBrowseForProjectImmediately; const pendingApprovalByThreadId = useMemo(() => { const map = new Map(); for (const thread of threads) { @@ -550,12 +558,21 @@ export default function Sidebar() { } if (pickedPath) { await addProjectFromPath(pickedPath); - } else { + } else if (!shouldBrowseForProjectImmediately) { addProjectInputRef.current?.focus(); } setIsPickingFolder(false); }; + const handleStartAddProject = () => { + setAddProjectError(null); + if (shouldBrowseForProjectImmediately) { + void handlePickFolder(); + return; + } + setAddingProject((prev) => !prev); + }; + const cancelRename = useCallback(() => { setRenamingThreadId(null); renamingInputRef.current = null; @@ -1075,21 +1092,23 @@ export default function Sidebar() {