From f8411d121e5fccaa473752f3f19de81094e47528 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 20 Apr 2026 12:40:39 +0700 Subject: [PATCH] Separate explorer checkout from panel state --- packages/app/src/app/_layout.tsx | 18 +- .../app/src/components/agent-stream-view.tsx | 13 +- .../app/src/components/explorer-sidebar.tsx | 22 +- .../src/components/headers/menu-header.tsx | 5 +- packages/app/src/components/left-sidebar.tsx | 12 +- packages/app/src/components/terminal-pane.tsx | 8 +- .../src/screens/agent/draft-agent-screen.tsx | 60 +++- .../app/src/screens/open-project-screen.tsx | 6 +- .../screens/workspace/workspace-screen.tsx | 51 +++- .../src/stores/explorer-checkout-context.ts | 23 ++ packages/app/src/stores/panel-store.ts | 265 +++++++++--------- 11 files changed, 295 insertions(+), 188 deletions(-) create mode 100644 packages/app/src/stores/explorer-checkout-context.ts diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx index 3b5279dbf..47159bcb8 100644 --- a/packages/app/src/app/_layout.tsx +++ b/packages/app/src/app/_layout.tsx @@ -388,9 +388,11 @@ function AppContainer({ const { theme } = useUnistyles(); const daemons = useHosts(); const { settings, updateSettings } = useAppSettings(); - const toggleAgentList = usePanelStore((state) => state.toggleAgentList); - const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer); - const toggleBothSidebars = usePanelStore((state) => state.toggleBothSidebars); + const toggleMobileAgentList = usePanelStore((state) => state.toggleMobileAgentList); + const toggleMobileFileExplorer = usePanelStore((state) => state.toggleMobileFileExplorer); + const toggleDesktopAgentList = usePanelStore((state) => state.toggleDesktopAgentList); + const toggleDesktopFileExplorer = usePanelStore((state) => state.toggleDesktopFileExplorer); + const toggleDesktopSidebars = usePanelStore((state) => state.toggleDesktopSidebars); const toggleFocusMode = usePanelStore((state) => state.toggleFocusMode); const isFocusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled); const agentListOpen = usePanelStore((state) => state.desktop.agentListOpen); @@ -405,6 +407,8 @@ function AppContainer({ const isCompactLayout = useIsCompactFormFactor(); const chromeEnabled = chromeEnabledOverride ?? daemons.length > 0; const pathname = usePathname(); + const toggleAgentList = isCompactLayout ? toggleMobileAgentList : toggleDesktopAgentList; + const toggleFileExplorer = isCompactLayout ? toggleMobileFileExplorer : toggleDesktopFileExplorer; // TODO: stop matching pathname here as a branch. `chromeEnabled` should not // conflate workspace/project-specific chrome (sidebar, mobile gesture) with // global concerns like keyboard shortcuts. Split those out so settings (and @@ -445,7 +449,7 @@ function AppContainer({ isMobile: isCompactLayout, toggleAgentList, toggleFileExplorer, - toggleBothSidebars, + toggleBothSidebars: toggleDesktopSidebars, toggleFocusMode, cycleTheme, }); @@ -490,7 +494,7 @@ function MobileGestureWrapper({ chromeEnabled: boolean; }) { const mobileView = usePanelStore((state) => state.mobileView); - const openAgentList = usePanelStore((state) => state.openAgentList); + const showMobileAgentList = usePanelStore((state) => state.showMobileAgentList); const horizontalScroll = useHorizontalScrollOptional(); const { translateX, @@ -507,8 +511,8 @@ function MobileGestureWrapper({ const handleGestureOpen = useCallback(() => { gestureAnimatingRef.current = true; - openAgentList(); - }, [openAgentList, gestureAnimatingRef]); + showMobileAgentList(); + }, [showMobileAgentList, gestureAnimatingRef]); const openGesture = useMemo( () => diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index 13c56d006..19174b041 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -125,7 +125,8 @@ const AgentStreamViewComponent = forwardRef>( new Set(), ); - const openFileExplorer = usePanelStore((state) => state.openFileExplorer); + const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer); + const openDesktopFileExplorer = usePanelStore((state) => state.openDesktopFileExplorer); const setExplorerTabForCheckout = usePanelStore((state) => state.setExplorerTabForCheckout); // Get serverId (fallback to agent's serverId if not provided) @@ -205,15 +206,21 @@ const AgentStreamViewComponent = forwardRef state.mobileView); const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen); - const closeToAgent = usePanelStore((state) => state.closeToAgent); + const showMobileAgent = usePanelStore((state) => state.showMobileAgent); + const closeDesktopFileExplorer = usePanelStore((state) => state.closeDesktopFileExplorer); const explorerTab = usePanelStore((state) => state.explorerTab); const explorerWidth = usePanelStore((state) => state.explorerWidth); const setExplorerTabForCheckout = usePanelStore((state) => state.setExplorerTabForCheckout); @@ -107,15 +108,26 @@ export function ExplorerSidebar({ mobileView, desktopFileExplorerOpen, }); - closeToAgent(); + if (isMobile) { + showMobileAgent(); + return; + } + closeDesktopFileExplorer(); }, - [closeToAgent, desktopFileExplorerOpen, isOpen, mobileView], + [ + closeDesktopFileExplorer, + desktopFileExplorerOpen, + isMobile, + isOpen, + mobileView, + showMobileAgent, + ], ); const handleCloseFromGesture = useCallback(() => { gestureAnimatingRef.current = true; - closeToAgent(); - }, [closeToAgent, gestureAnimatingRef]); + showMobileAgent(); + }, [gestureAnimatingRef, showMobileAgent]); const enableSidebarCloseGesture = isMobile && isOpen; diff --git a/packages/app/src/components/headers/menu-header.tsx b/packages/app/src/components/headers/menu-header.tsx index 7074dbb2b..e0cc79f8a 100644 --- a/packages/app/src/components/headers/menu-header.tsx +++ b/packages/app/src/components/headers/menu-header.tsx @@ -48,7 +48,8 @@ export function SidebarMenuToggle({ const isMobile = useIsCompactFormFactor(); const mobileView = usePanelStore((state) => state.mobileView); const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen); - const toggleAgentList = usePanelStore((state) => state.toggleAgentList); + const toggleMobileAgentList = usePanelStore((state) => state.toggleMobileAgentList); + const toggleDesktopAgentList = usePanelStore((state) => state.toggleDesktopAgentList); const toggleShortcutKeys = getShortcutOs() === "mac" ? ["mod", "B"] : ["mod", "."]; const isOpen = isMobile ? mobileView === "agent-list" : desktopAgentListOpen; @@ -57,7 +58,7 @@ export function SidebarMenuToggle({ return ( state.mobileView); const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen); - const closeToAgent = usePanelStore((state) => state.closeToAgent); + const showMobileAgent = usePanelStore((state) => state.showMobileAgent); const pathname = usePathname(); const daemons = useHosts(); const activeServerIdFromPath = useMemo(() => parseServerIdFromPathname(pathname), [pathname]); @@ -208,18 +208,18 @@ export const LeftSidebar = memo(function LeftSidebar({ const openProjectPicker = useOpenProjectPicker(activeServerId); const handleOpenProjectMobile = useCallback(() => { - closeToAgent(); + showMobileAgent(); void openProjectPicker(); - }, [closeToAgent, openProjectPicker]); + }, [showMobileAgent, openProjectPicker]); const handleOpenProjectDesktop = useCallback(() => { void openProjectPicker(); }, [openProjectPicker]); const handleSettingsMobile = useCallback(() => { - closeToAgent(); + showMobileAgent(); router.push(buildSettingsRoute()); - }, [closeToAgent]); + }, [showMobileAgent]); const handleSettingsDesktop = useCallback(() => { router.push(buildSettingsRoute()); @@ -272,7 +272,7 @@ export const LeftSidebar = memo(function LeftSidebar({ insetsTop={insets.top} insetsBottom={insets.bottom} isOpen={isOpen} - closeToAgent={closeToAgent} + closeToAgent={showMobileAgent} handleOpenProject={handleOpenProjectMobile} handleSettings={handleSettingsMobile} handleViewMoreNavigate={handleViewMoreNavigate} diff --git a/packages/app/src/components/terminal-pane.tsx b/packages/app/src/components/terminal-pane.tsx index bd8f9fecc..a69e16a68 100644 --- a/packages/app/src/components/terminal-pane.tsx +++ b/packages/app/src/components/terminal-pane.tsx @@ -89,8 +89,8 @@ export function TerminalPane({ serverId, cwd, terminalId, isPaneFocused }: Termi const xtermTheme = useMemo(() => toXtermTheme(theme.colors.terminal), [theme]); const isMobile = useIsCompactFormFactor(); const mobileView = usePanelStore((state) => state.mobileView); - const openAgentList = usePanelStore((state) => state.openAgentList); - const openFileExplorer = usePanelStore((state) => state.openFileExplorer); + const showMobileAgentList = usePanelStore((state) => state.showMobileAgentList); + const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer); const swipeGesturesEnabled = isMobile && mobileView === "agent"; const { shift: keyboardShift, style: keyboardPaddingStyle } = useKeyboardShiftStyle({ mode: "padding", @@ -573,13 +573,13 @@ export function TerminalPane({ serverId, cwd, terminalId, isPaneFocused }: Termi if (!swipeGesturesEnabled) { return; } - openAgentList(); + showMobileAgentList(); }} onSwipeLeft={() => { if (!swipeGesturesEnabled) { return; } - openFileExplorer(); + showMobileFileExplorer(); }} onInput={handleTerminalData} onResize={handleTerminalResize} diff --git a/packages/app/src/screens/agent/draft-agent-screen.tsx b/packages/app/src/screens/agent/draft-agent-screen.tsx index d579a72fa..39271189f 100644 --- a/packages/app/src/screens/agent/draft-agent-screen.tsx +++ b/packages/app/src/screens/agent/draft-agent-screen.tsx @@ -36,7 +36,11 @@ import { useHostRuntimeIsConnected, } from "@/runtime/host-runtime"; import { ExplorerSidebarAnimationProvider } from "@/contexts/explorer-sidebar-animation-context"; -import { usePanelStore, type ExplorerCheckoutContext } from "@/stores/panel-store"; +import { usePanelStore } from "@/stores/panel-store"; +import { + setActiveExplorerCheckout, + type ExplorerCheckoutContext, +} from "@/stores/explorer-checkout-context"; import { MAX_CONTENT_WIDTH, useIsCompactFormFactor } from "@/constants/layout"; import { WelcomeScreen } from "@/components/welcome-screen"; import type { Agent } from "@/contexts/session-context"; @@ -237,10 +241,12 @@ function DraftAgentScreenContent({ const isMobile = useIsCompactFormFactor(); const mobileView = usePanelStore((state) => state.mobileView); const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen); - const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer); - const openFileExplorer = usePanelStore((state) => state.openFileExplorer); - const closeFileExplorer = usePanelStore((state) => state.closeFileExplorer); - const setActiveExplorerCheckout = usePanelStore((state) => state.setActiveExplorerCheckout); + const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer); + const toggleMobileFileExplorer = usePanelStore((state) => state.toggleMobileFileExplorer); + const showMobileAgent = usePanelStore((state) => state.showMobileAgent); + const openDesktopFileExplorer = usePanelStore((state) => state.openDesktopFileExplorer); + const toggleDesktopFileExplorer = usePanelStore((state) => state.toggleDesktopFileExplorer); + const closeDesktopFileExplorer = usePanelStore((state) => state.closeDesktopFileExplorer); const activateExplorerTabForCheckout = usePanelStore( (state) => state.activateExplorerTabForCheckout, ); @@ -646,18 +652,39 @@ function DraftAgentScreenContent({ return; } activateExplorerTabForCheckout(draftExplorerCheckout); - openFileExplorer(); - }, [activateExplorerTabForCheckout, draftExplorerCheckout, openFileExplorer]); + if (isMobile) { + showMobileFileExplorer(); + return; + } + openDesktopFileExplorer(); + }, [ + activateExplorerTabForCheckout, + draftExplorerCheckout, + isMobile, + openDesktopFileExplorer, + showMobileFileExplorer, + ]); const handleToggleExplorer = useCallback(() => { if (!canOpenExplorer) { return; } if (isExplorerOpen) { - toggleFileExplorer(); + if (isMobile) { + toggleMobileFileExplorer(); + return; + } + toggleDesktopFileExplorer(); return; } openExplorerForDraftCheckout(); - }, [canOpenExplorer, isExplorerOpen, openExplorerForDraftCheckout, toggleFileExplorer]); + }, [ + canOpenExplorer, + isExplorerOpen, + isMobile, + openExplorerForDraftCheckout, + toggleDesktopFileExplorer, + toggleMobileFileExplorer, + ]); const explorerOpenGesture = useExplorerOpenGesture({ enabled: isMobile && mobileView === "agent" && canOpenExplorer, onOpen: openExplorerForDraftCheckout, @@ -956,8 +983,19 @@ function DraftAgentScreenContent({ if (!isFocused || canOpenExplorer || !isExplorerOpen) { return; } - closeFileExplorer(); - }, [canOpenExplorer, closeFileExplorer, isExplorerOpen, isFocused]); + if (isMobile) { + showMobileAgent(); + return; + } + closeDesktopFileExplorer(); + }, [ + canOpenExplorer, + closeDesktopFileExplorer, + isExplorerOpen, + isFocused, + isMobile, + showMobileAgent, + ]); useEffect(() => { return () => { setActiveExplorerCheckout(null); diff --git a/packages/app/src/screens/open-project-screen.tsx b/packages/app/src/screens/open-project-screen.tsx index a519b74cf..67207fa25 100644 --- a/packages/app/src/screens/open-project-screen.tsx +++ b/packages/app/src/screens/open-project-screen.tsx @@ -20,7 +20,7 @@ import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon"; import { PairDeviceModal } from "@/desktop/components/pair-device-modal"; export function OpenProjectScreen({ serverId }: { serverId: string }) { - const openAgentList = usePanelStore((s) => s.openAgentList); + const openDesktopAgentList = usePanelStore((s) => s.openDesktopAgentList); const openProjectPicker = useOpenProjectPicker(serverId); const hasHydrated = useSessionStore((s) => s.sessions[serverId]?.hasHydratedWorkspaces ?? false); const hasProjects = useHasWorkspaces(serverId); @@ -31,9 +31,9 @@ export function OpenProjectScreen({ serverId }: { serverId: string }) { useEffect(() => { if (!isCompactLayout) { - openAgentList(); + openDesktopAgentList(); } - }, [isCompactLayout, openAgentList]); + }, [isCompactLayout, openDesktopAgentList]); return ( diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx index 73dfaccda..635540710 100644 --- a/packages/app/src/screens/workspace/workspace-screen.tsx +++ b/packages/app/src/screens/workspace/workspace-screen.tsx @@ -48,7 +48,11 @@ import { WorkspaceScriptsButton } from "@/screens/workspace/workspace-scripts-bu import { ExplorerSidebarAnimationProvider } from "@/contexts/explorer-sidebar-animation-context"; import { useToast } from "@/contexts/toast-context"; import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture"; -import { usePanelStore, type ExplorerCheckoutContext } from "@/stores/panel-store"; +import { usePanelStore } from "@/stores/panel-store"; +import { + setActiveExplorerCheckout, + type ExplorerCheckoutContext, +} from "@/stores/explorer-checkout-context"; import { useSessionStore } from "@/stores/session-store"; import { buildWorkspaceTabPersistenceKey, @@ -843,13 +847,14 @@ function WorkspaceScreenContent({ const mobileView = usePanelStore((state) => state.mobileView); const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen); - const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer); - const openFileExplorer = usePanelStore((state) => state.openFileExplorer); + const showMobileFileExplorer = usePanelStore((state) => state.showMobileFileExplorer); + const toggleMobileFileExplorer = usePanelStore((state) => state.toggleMobileFileExplorer); + const openDesktopFileExplorer = usePanelStore((state) => state.openDesktopFileExplorer); + const toggleDesktopFileExplorer = usePanelStore((state) => state.toggleDesktopFileExplorer); const activateExplorerTabForCheckout = usePanelStore( (state) => state.activateExplorerTabForCheckout, ); - const closeToAgent = usePanelStore((state) => state.closeToAgent); - const setActiveExplorerCheckout = usePanelStore((state) => state.setActiveExplorerCheckout); + const showMobileAgent = usePanelStore((state) => state.showMobileAgent); const isExplorerOpen = isMobile ? mobileView === "file-explorer" : desktopFileExplorerOpen; @@ -876,16 +881,36 @@ function WorkspaceScreenContent({ return; } activateExplorerTabForCheckout(activeExplorerCheckout); - openFileExplorer(); - }, [activateExplorerTabForCheckout, activeExplorerCheckout, openFileExplorer]); + if (isMobile) { + showMobileFileExplorer(); + return; + } + openDesktopFileExplorer(); + }, [ + activateExplorerTabForCheckout, + activeExplorerCheckout, + isMobile, + openDesktopFileExplorer, + showMobileFileExplorer, + ]); const handleToggleExplorer = useCallback(() => { if (isExplorerOpen) { - toggleFileExplorer(); + if (isMobile) { + toggleMobileFileExplorer(); + return; + } + toggleDesktopFileExplorer(); return; } openExplorerForWorkspace(); - }, [isExplorerOpen, openExplorerForWorkspace, toggleFileExplorer]); + }, [ + isExplorerOpen, + isMobile, + openExplorerForWorkspace, + toggleDesktopFileExplorer, + toggleMobileFileExplorer, + ]); const explorerOpenGesture = useExplorerOpenGesture({ enabled: isMobile && mobileView === "agent", @@ -899,14 +924,14 @@ function WorkspaceScreenContent({ const handler = BackHandler.addEventListener("hardwareBackPress", () => { if (isExplorerOpen) { - closeToAgent(); + showMobileAgent(); return true; } return false; }); return () => handler.remove(); - }, [closeToAgent, isExplorerOpen, isRouteFocused]); + }, [isExplorerOpen, isRouteFocused, showMobileAgent]); const persistenceKey = useMemo( () => @@ -1190,7 +1215,7 @@ function WorkspaceScreenContent({ const handleOpenFileFromExplorer = useCallback( function handleOpenFileFromExplorer(filePath: string) { if (isMobile) { - closeToAgent(); + showMobileAgent(); } if (!persistenceKey) { return; @@ -1200,7 +1225,7 @@ function WorkspaceScreenContent({ navigateToTabId(tabId); } }, - [closeToAgent, isMobile, navigateToTabId, openWorkspaceTabFocused, persistenceKey], + [isMobile, navigateToTabId, openWorkspaceTabFocused, persistenceKey, showMobileAgent], ); const handleOpenFileFromChat = useCallback( diff --git a/packages/app/src/stores/explorer-checkout-context.ts b/packages/app/src/stores/explorer-checkout-context.ts new file mode 100644 index 000000000..392142398 --- /dev/null +++ b/packages/app/src/stores/explorer-checkout-context.ts @@ -0,0 +1,23 @@ +export interface ExplorerCheckoutContext { + serverId: string; + cwd: string; + isGit: boolean; +} + +let activeExplorerCheckout: ExplorerCheckoutContext | null = null; + +export function getActiveExplorerCheckout(): ExplorerCheckoutContext | null { + return activeExplorerCheckout; +} + +export function setActiveExplorerCheckout(checkout: ExplorerCheckoutContext | null) { + if ( + activeExplorerCheckout?.serverId === checkout?.serverId && + activeExplorerCheckout?.cwd === checkout?.cwd && + activeExplorerCheckout?.isGit === checkout?.isGit + ) { + return; + } + + activeExplorerCheckout = checkout; +} diff --git a/packages/app/src/stores/panel-store.ts b/packages/app/src/stores/panel-store.ts index f68513832..efc6a3253 100644 --- a/packages/app/src/stores/panel-store.ts +++ b/packages/app/src/stores/panel-store.ts @@ -8,8 +8,13 @@ import { resolveExplorerTabForCheckout, type ExplorerTab, } from "./explorer-tab-memory"; +import { + getActiveExplorerCheckout, + type ExplorerCheckoutContext, +} from "./explorer-checkout-context"; import { isWeb } from "@/constants/platform"; export type { ExplorerTab } from "./explorer-tab-memory"; +export type { ExplorerCheckoutContext } from "./explorer-checkout-context"; /** * Mobile panel state machine. @@ -37,12 +42,6 @@ interface DesktopSidebarState { } export type SortOption = "name" | "modified" | "size"; -export interface ExplorerCheckoutContext { - serverId: string; - cwd: string; - isGit: boolean; -} - export const DEFAULT_SIDEBAR_WIDTH = 320; export const MIN_SIDEBAR_WIDTH = 200; export const MAX_SIDEBAR_WIDTH = 600; @@ -68,21 +67,25 @@ interface PanelState { explorerTabByCheckout: Record; expandedPathsByWorkspace: Record; diffExpandedPathsByWorkspace: Record; - activeExplorerCheckout: ExplorerCheckoutContext | null; sidebarWidth: number; explorerWidth: number; explorerSortOption: SortOption; explorerFilesSplitRatio: number; // Actions - openAgentList: () => void; - openFileExplorer: () => void; - closeFileExplorer: () => void; - closeToAgent: () => void; - toggleAgentList: () => void; - toggleFileExplorer: () => void; - toggleBothSidebars: () => void; toggleFocusMode: () => void; + showMobileAgent: () => void; + showMobileAgentList: () => void; + showMobileFileExplorer: () => void; + toggleMobileAgentList: () => void; + toggleMobileFileExplorer: () => void; + openDesktopAgentList: () => void; + closeDesktopAgentList: () => void; + toggleDesktopAgentList: () => void; + openDesktopFileExplorer: () => void; + closeDesktopFileExplorer: () => void; + toggleDesktopFileExplorer: () => void; + toggleDesktopSidebars: () => void; // File explorer settings actions setExplorerTab: (tab: ExplorerTab) => void; @@ -90,7 +93,6 @@ interface PanelState { setExpandedPathsForWorkspace: (workspaceKey: string, paths: string[]) => void; setDiffExpandedPathsForWorkspace: (workspaceKey: string, paths: string[]) => void; activateExplorerTabForCheckout: (checkout: ExplorerCheckoutContext) => void; - setActiveExplorerCheckout: (checkout: ExplorerCheckoutContext | null) => void; setSidebarWidth: (width: number) => void; setExplorerWidth: (width: number) => void; setExplorerSortOption: (option: SortOption) => void; @@ -116,14 +118,17 @@ function clampExplorerFilesSplitRatio(ratio: number): number { return clampNumber(ratio, MIN_EXPLORER_FILES_SPLIT_RATIO, MAX_EXPLORER_FILES_SPLIT_RATIO); } -function resolveExplorerTabFromActiveCheckout(state: PanelState): ExplorerTab | null { - if (!state.activeExplorerCheckout) { +function resolveExplorerTabFromCheckout( + state: PanelState, + checkout: ExplorerCheckoutContext | null, +): ExplorerTab | null { + if (!checkout) { return null; } return resolveExplorerTabForCheckout({ - serverId: state.activeExplorerCheckout.serverId, - cwd: state.activeExplorerCheckout.cwd, - isGit: state.activeExplorerCheckout.isGit, + serverId: checkout.serverId, + cwd: checkout.cwd, + isGit: checkout.isGit, explorerTabByCheckout: state.explorerTabByCheckout, }); } @@ -148,103 +153,118 @@ export const usePanelStore = create()( explorerTabByCheckout: {}, expandedPathsByWorkspace: {}, diffExpandedPathsByWorkspace: {}, - activeExplorerCheckout: null, sidebarWidth: DEFAULT_SIDEBAR_WIDTH, explorerWidth: DEFAULT_EXPLORER_SIDEBAR_WIDTH, explorerSortOption: "name", explorerFilesSplitRatio: DEFAULT_EXPLORER_FILES_SPLIT_RATIO, - openAgentList: () => - set((state) => ({ - mobileView: "agent-list" as const, - desktop: { ...state.desktop, agentListOpen: true }, - })), - - openFileExplorer: () => - set((state) => { - const resolvedTab = resolveExplorerTabFromActiveCheckout(state); - return { - mobileView: "file-explorer" as MobilePanelView, - desktop: { ...state.desktop, fileExplorerOpen: true }, - ...(resolvedTab ? { explorerTab: resolvedTab } : {}), - }; - }), - closeFileExplorer: () => - set((state) => ({ - mobileView: state.mobileView === "file-explorer" ? ("agent" as const) : state.mobileView, - desktop: { - ...state.desktop, - fileExplorerOpen: false, - }, - })), - - closeToAgent: () => - set((state) => ({ - mobileView: "agent" as const, - // On desktop, closing depends on which panel triggered it - // This is called when closing via gesture/backdrop, so we close the currently active mobile panel - desktop: { - ...state.desktop, - agentListOpen: state.mobileView === "agent-list" ? false : state.desktop.agentListOpen, - fileExplorerOpen: - state.mobileView === "file-explorer" ? false : state.desktop.fileExplorerOpen, - }, - })), - - toggleAgentList: () => - set((state) => ({ - // Mobile: toggle between agent and agent-list - mobileView: (state.mobileView === "agent-list" - ? "agent" - : "agent-list") as MobilePanelView, - desktop: { - ...state.desktop, - agentListOpen: !state.desktop.agentListOpen, - }, - })), - - toggleFileExplorer: () => - set((state) => { - // Mobile: toggle between agent and file-explorer - const willOpenMobile = state.mobileView !== "file-explorer"; - const willOpenDesktop = !state.desktop.fileExplorerOpen; - const nextMobileView: MobilePanelView = willOpenMobile ? "file-explorer" : "agent"; - const nextDesktop = { - ...state.desktop, - fileExplorerOpen: willOpenDesktop, - }; - const nextState: Pick & - Partial> = { - mobileView: nextMobileView, - desktop: nextDesktop, - }; - if (willOpenMobile || willOpenDesktop) { - const resolvedTab = resolveExplorerTabFromActiveCheckout(state); - if (resolvedTab) { - nextState.explorerTab = resolvedTab; - } - } - return nextState; - }), - toggleFocusMode: () => set((state) => ({ desktop: { ...state.desktop, focusModeEnabled: !state.desktop.focusModeEnabled }, })), - toggleBothSidebars: () => + showMobileAgent: () => + set((state) => { + if (state.mobileView === "agent") { + return state; + } + return { mobileView: "agent" as const }; + }), + + showMobileAgentList: () => + set((state) => { + if (state.mobileView === "agent-list") { + return state; + } + return { mobileView: "agent-list" as const }; + }), + + showMobileFileExplorer: () => + set((state) => { + const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout()); + return { + mobileView: "file-explorer" as const, + ...(resolvedTab ? { explorerTab: resolvedTab } : {}), + }; + }), + + toggleMobileAgentList: () => + set((state) => ({ + mobileView: state.mobileView === "agent-list" ? "agent" : "agent-list", + })), + + toggleMobileFileExplorer: () => + set((state) => { + if (state.mobileView === "file-explorer") { + return { mobileView: "agent" as const }; + } + const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout()); + return { + mobileView: "file-explorer" as const, + ...(resolvedTab ? { explorerTab: resolvedTab } : {}), + }; + }), + + openDesktopAgentList: () => + set((state) => { + if (state.desktop.agentListOpen) { + return state; + } + return { desktop: { ...state.desktop, agentListOpen: true } }; + }), + + closeDesktopAgentList: () => + set((state) => { + if (!state.desktop.agentListOpen) { + return state; + } + return { desktop: { ...state.desktop, agentListOpen: false } }; + }), + + toggleDesktopAgentList: () => + set((state) => ({ + desktop: { ...state.desktop, agentListOpen: !state.desktop.agentListOpen }, + })), + + openDesktopFileExplorer: () => + set((state) => { + const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout()); + return { + desktop: { ...state.desktop, fileExplorerOpen: true }, + ...(resolvedTab ? { explorerTab: resolvedTab } : {}), + }; + }), + + closeDesktopFileExplorer: () => + set((state) => { + if (!state.desktop.fileExplorerOpen) { + return state; + } + return { desktop: { ...state.desktop, fileExplorerOpen: false } }; + }), + + toggleDesktopFileExplorer: () => + set((state) => { + const willOpen = !state.desktop.fileExplorerOpen; + const resolvedTab = willOpen + ? resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout()) + : null; + return { + desktop: { ...state.desktop, fileExplorerOpen: willOpen }, + ...(resolvedTab ? { explorerTab: resolvedTab } : {}), + }; + }), + + toggleDesktopSidebars: () => set((state) => { - // If either sidebar is open, close both. Otherwise, open both. const anyOpen = state.desktop.agentListOpen || state.desktop.fileExplorerOpen; if (anyOpen) { return { - mobileView: "agent" as MobilePanelView, desktop: { ...state.desktop, agentListOpen: false, fileExplorerOpen: false }, }; } - const resolvedTab = resolveExplorerTabFromActiveCheckout(state); + const resolvedTab = resolveExplorerTabFromCheckout(state, getActiveExplorerCheckout()); return { - mobileView: "agent" as MobilePanelView, desktop: { ...state.desktop, agentListOpen: true, fileExplorerOpen: true }, ...(resolvedTab ? { explorerTab: resolvedTab } : {}), }; @@ -280,7 +300,6 @@ export const usePanelStore = create()( })), activateExplorerTabForCheckout: (checkout) => set((state) => ({ - activeExplorerCheckout: checkout, explorerTab: resolveExplorerTabForCheckout({ serverId: checkout.serverId, cwd: checkout.cwd, @@ -288,18 +307,6 @@ export const usePanelStore = create()( explorerTabByCheckout: state.explorerTabByCheckout, }), })), - setActiveExplorerCheckout: (checkout) => - set((state) => { - const current = state.activeExplorerCheckout; - if ( - current?.serverId === checkout?.serverId && - current?.cwd === checkout?.cwd && - current?.isGit === checkout?.isGit - ) { - return state; - } - return { activeExplorerCheckout: checkout }; - }), setSidebarWidth: (width) => set({ sidebarWidth: clampSidebarWidth(width) }), setExplorerWidth: (width) => set({ explorerWidth: clampWidth(width) }), setExplorerSortOption: (option) => set({ explorerSortOption: option }), @@ -400,8 +407,6 @@ export const usePanelStore = create()( state.diffExpandedPathsByWorkspace = {}; } - state.activeExplorerCheckout = null; - return state as PanelState; }, partialize: (state) => ({ @@ -435,12 +440,12 @@ export function usePanelState(isMobile: boolean) { return { isAgentListOpen: store.mobileView === "agent-list", isFileExplorerOpen: store.mobileView === "file-explorer", - openAgentList: store.openAgentList, - openFileExplorer: store.openFileExplorer, - closeAgentList: store.closeToAgent, - closeFileExplorer: store.closeToAgent, - toggleAgentList: store.toggleAgentList, - toggleFileExplorer: store.toggleFileExplorer, + openAgentList: store.showMobileAgentList, + openFileExplorer: store.showMobileFileExplorer, + closeAgentList: store.showMobileAgent, + closeFileExplorer: store.showMobileAgent, + toggleAgentList: store.toggleMobileAgentList, + toggleFileExplorer: store.toggleMobileFileExplorer, // Explorer settings explorerTab: store.explorerTab, explorerTabByCheckout: store.explorerTabByCheckout, @@ -450,7 +455,6 @@ export function usePanelState(isMobile: boolean) { setExplorerTab: store.setExplorerTab, setExplorerTabForCheckout: store.setExplorerTabForCheckout, activateExplorerTabForCheckout: store.activateExplorerTabForCheckout, - setActiveExplorerCheckout: store.setActiveExplorerCheckout, setExplorerWidth: store.setExplorerWidth, setExplorerSortOption: store.setExplorerSortOption, setExplorerFilesSplitRatio: store.setExplorerFilesSplitRatio, @@ -461,18 +465,12 @@ export function usePanelState(isMobile: boolean) { return { isAgentListOpen: store.desktop.agentListOpen, isFileExplorerOpen: store.desktop.fileExplorerOpen, - openAgentList: store.openAgentList, - openFileExplorer: store.openFileExplorer, - closeAgentList: () => - usePanelStore.setState((state) => ({ - desktop: { ...state.desktop, agentListOpen: false }, - })), - closeFileExplorer: () => - usePanelStore.setState((state) => ({ - desktop: { ...state.desktop, fileExplorerOpen: false }, - })), - toggleAgentList: store.toggleAgentList, - toggleFileExplorer: store.toggleFileExplorer, + openAgentList: store.openDesktopAgentList, + openFileExplorer: store.openDesktopFileExplorer, + closeAgentList: store.closeDesktopAgentList, + closeFileExplorer: store.closeDesktopFileExplorer, + toggleAgentList: store.toggleDesktopAgentList, + toggleFileExplorer: store.toggleDesktopFileExplorer, // Explorer settings explorerTab: store.explorerTab, explorerTabByCheckout: store.explorerTabByCheckout, @@ -482,7 +480,6 @@ export function usePanelState(isMobile: boolean) { setExplorerTab: store.setExplorerTab, setExplorerTabForCheckout: store.setExplorerTabForCheckout, activateExplorerTabForCheckout: store.activateExplorerTabForCheckout, - setActiveExplorerCheckout: store.setActiveExplorerCheckout, setExplorerWidth: store.setExplorerWidth, setExplorerSortOption: store.setExplorerSortOption, setExplorerFilesSplitRatio: store.setExplorerFilesSplitRatio,