From 8d8c7111d96d8c9451573e942746f16fb9a555da Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 28 Jun 2026 14:39:33 -0500 Subject: [PATCH] refactor(desktop): keep remote fs routing inside the fs facade Let UI callers ask for folders/files without knowing remote-picker limits: selectDesktopPaths now normalizes remote directory selection to a single folder inside the facade. Project creation and composer context picking no longer branch on remote mode; they route through desktop-fs helpers just like git callers route through desktopGit(). Behavior unchanged except remote folder context now works through the same backend picker path. --- .../src/app/chat/hooks/use-composer-actions.ts | 5 ++--- apps/desktop/src/lib/desktop-fs.test.ts | 6 +++--- apps/desktop/src/lib/desktop-fs.ts | 4 ++-- apps/desktop/src/store/projects.test.ts | 3 +-- apps/desktop/src/store/projects.ts | 11 +++++++---- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/desktop/src/app/chat/hooks/use-composer-actions.ts b/apps/desktop/src/app/chat/hooks/use-composer-actions.ts index f72f97823982c..a8afdd128306b 100644 --- a/apps/desktop/src/app/chat/hooks/use-composer-actions.ts +++ b/apps/desktop/src/app/chat/hooks/use-composer-actions.ts @@ -5,7 +5,7 @@ import { droppedFileInlineRef } from '@/app/chat/composer/inline-refs' import { formatRefValue } from '@/components/assistant-ui/directive-text' import { useI18n } from '@/i18n' import { attachmentId, contextPath, pathLabel } from '@/lib/chat-runtime' -import { isDesktopFsRemoteMode, readDesktopFileDataUrl, selectDesktopPaths } from '@/lib/desktop-fs' +import { readDesktopFileDataUrl, selectDesktopPaths } from '@/lib/desktop-fs' import { addComposerAttachment, type ComposerAttachment, @@ -266,8 +266,7 @@ export function useComposerActions({ activeSessionId, currentCwd, requestGateway const paths = await selectDesktopPaths({ title: kind === 'file' ? 'Add files as context' : 'Add folders as context', defaultPath: currentCwd || undefined, - directories: kind === 'folder', - multiple: kind === 'folder' && isDesktopFsRemoteMode() ? false : undefined + directories: kind === 'folder' }) if (!paths?.length) { diff --git a/apps/desktop/src/lib/desktop-fs.test.ts b/apps/desktop/src/lib/desktop-fs.test.ts index 0d5955313c60c..0e057aef19216 100644 --- a/apps/desktop/src/lib/desktop-fs.test.ts +++ b/apps/desktop/src/lib/desktop-fs.test.ts @@ -132,15 +132,15 @@ describe('desktop filesystem facade', () => { expect(selectPaths).not.toHaveBeenCalled() }) - it('does not treat the remote directory picker as a general file picker', async () => { + it('limits the remote picker to single-directory selection', async () => { const remoteSelect = vi.fn(async () => ['/remote/project']) $connection.set({ mode: 'remote' } as never) setDesktopFsRemotePicker({ selectPaths: remoteSelect }) await expect(selectDesktopPaths({ directories: false, multiple: false })).resolves.toEqual([]) - await expect(selectDesktopPaths({ directories: true, multiple: true })).resolves.toEqual([]) + await expect(selectDesktopPaths({ directories: true })).resolves.toEqual(['/remote/project']) - expect(remoteSelect).not.toHaveBeenCalled() + expect(remoteSelect).toHaveBeenCalledWith({ directories: true, multiple: false }) expect(selectPaths).not.toHaveBeenCalled() }) }) diff --git a/apps/desktop/src/lib/desktop-fs.ts b/apps/desktop/src/lib/desktop-fs.ts index 1451ac81e7936..1c0510e54da35 100644 --- a/apps/desktop/src/lib/desktop-fs.ts +++ b/apps/desktop/src/lib/desktop-fs.ts @@ -178,9 +178,9 @@ export async function selectDesktopPaths(options?: HermesSelectPathsOptions): Pr return desktop.selectPaths(options) } - if (!options?.directories || options.multiple !== false) { + if (!options?.directories) { return [] } - return remotePicker ? remotePicker.selectPaths(options) : [] + return remotePicker ? remotePicker.selectPaths({ ...options, multiple: false }) : [] } diff --git a/apps/desktop/src/store/projects.test.ts b/apps/desktop/src/store/projects.test.ts index 32749f2b19ced..2c2af5f019789 100644 --- a/apps/desktop/src/store/projects.test.ts +++ b/apps/desktop/src/store/projects.test.ts @@ -68,13 +68,12 @@ describe('pickProjectFolder', () => { vi.clearAllMocks() }) - it('uses the remote-aware directory picker locally (no backend default cwd probe)', async () => { + it('uses the remote-aware directory picker locally', async () => { isDesktopFsRemoteMode.mockReturnValue(false) selectDesktopPaths.mockResolvedValue(['/local/repo']) await expect(pickProjectFolder()).resolves.toBe('/local/repo') expect(selectDesktopPaths).toHaveBeenCalledWith({ defaultPath: undefined, directories: true, multiple: false }) - expect(desktopDefaultCwd).not.toHaveBeenCalled() }) it('seeds the picker with the backend cwd on a remote gateway', async () => { diff --git a/apps/desktop/src/store/projects.ts b/apps/desktop/src/store/projects.ts index eb993cd400f90..a77a991018d99 100644 --- a/apps/desktop/src/store/projects.ts +++ b/apps/desktop/src/store/projects.ts @@ -2,7 +2,7 @@ import { atom } from 'nanostores' import { liveSessionProjectId, type SidebarProjectTree } from '@/app/chat/sidebar/projects/workspace-groups' import type { HermesGitBranch } from '@/global' -import { desktopDefaultCwd, isDesktopFsRemoteMode, selectDesktopPaths, writeDesktopFileText } from '@/lib/desktop-fs' +import { desktopDefaultCwd, selectDesktopPaths, writeDesktopFileText } from '@/lib/desktop-fs' import { desktopGit } from '@/lib/desktop-git' import { persistentAtom } from '@/lib/persisted' import { activeGateway, ensureActiveGatewayOpen } from '@/store/gateway' @@ -282,7 +282,7 @@ export async function fetchProjectSessions(projectId: string): Promise { - const scan = window.hermesDesktop?.git?.scanRepos + const scan = desktopGit()?.scanRepos if (!scan || (didScanRepos && !force)) { return @@ -738,8 +738,11 @@ export async function copyPath(path: null | string): Promise { // mode opens the native dialog. Returns the absolute path, or null if cancelled. export async function pickProjectFolder(): Promise { try { - const defaultPath = isDesktopFsRemoteMode() ? (await desktopDefaultCwd())?.cwd : undefined - const [dir] = await selectDesktopPaths({ defaultPath, directories: true, multiple: false }) + const [dir] = await selectDesktopPaths({ + defaultPath: (await desktopDefaultCwd())?.cwd, + directories: true, + multiple: false + }) return dir || null } catch {