From 2b5d46e66c7b6527d58ffc1104f10e1e2b07791b Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 8 Oct 2026 20:58:13 -0700 Subject: [PATCH 1/3] test(desktop): cover quiet navigation and full access consent --- apps/desktop/e2e/browser-page-dialogs.spec.ts | 34 +++++++++++++++ apps/desktop/e2e/local-files.spec.ts | 41 +++++++++++++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/apps/desktop/e2e/browser-page-dialogs.spec.ts b/apps/desktop/e2e/browser-page-dialogs.spec.ts index 344e9fbdc19..ea3830b16e0 100644 --- a/apps/desktop/e2e/browser-page-dialogs.spec.ts +++ b/apps/desktop/e2e/browser-page-dialogs.spec.ts @@ -364,6 +364,40 @@ createRoot(document.getElementById('root')).render(createElement(Fixture));`, await panelAction({ action: 'reload' }) await expect.poll(() => pageTitle()).toBe('form') + await check('clean reloads and navigation never ask to discard changes', async () => { + await panelAction({ action: 'reload' }) + await expect.poll(() => pageTitle()).toBe('form') + expect(await pageDialog()).toBeNull() + await panelAction({ action: 'navigate', url: `${site}/next` }) + await expect.poll(pageUrl).toBe(`${site}/next`) + expect(await pageDialog()).toBeNull() + await panelAction({ action: 'back' }) + await expect.poll(pageUrl).toBe(`${site}/form`) + expect(await pageDialog()).toBeNull() + }) + + await check('clearing a draft removes the leave warning', async () => { + await userInput('#draft', 'temporary draft') + await inPage("document.getElementById('draft').value = ''") + await panelAction({ action: 'navigate', url: `${site}/next` }) + await expect.poll(pageUrl).toBe(`${site}/next`) + expect(await pageDialog()).toBeNull() + await panelAction({ action: 'navigate', url: `${site}/form` }) + await expect.poll(pageUrl).toBe(`${site}/form`) + }) + + await check('same-page navigation preserves a draft without a leave warning', async () => { + await userInput('#draft', 'same-page draft') + await panelAction({ action: 'navigate', url: `${site}/form#section` }) + await expect.poll(pageUrl).toBe(`${site}/form#section`) + expect(await pageDialog()).toBeNull() + expect(await inPage("document.getElementById('draft').value")).toBe('same-page draft') + await panelAction({ action: 'back' }) + await expect.poll(pageUrl).toBe(`${site}/form`) + expect(await pageDialog()).toBeNull() + await inPage("document.getElementById('draft').value = ''") + }) + await check('leaving a draft from the URL bar asks, and Stay keeps it', async () => { await userInput('#draft', 'draft') await expect diff --git a/apps/desktop/e2e/local-files.spec.ts b/apps/desktop/e2e/local-files.spec.ts index bc55bbc2514..e0786ccf5c0 100644 --- a/apps/desktop/e2e/local-files.spec.ts +++ b/apps/desktop/e2e/local-files.spec.ts @@ -315,7 +315,40 @@ createRoot(document.getElementById('settings')).render( await expect( window.getByRole('switch', { name: 'Full file access', exact: true }) ).not.toBeChecked() - await window.getByRole('switch', { name: 'Full file access', exact: true }).click() + const permission = await requestPermission({ operation: 'read', toolCallId: 'fullAccess' }) + const confirmationShown = app.waitForEvent('window') + await permission.prompt + .getByRole('button', { name: 'Allow all files…', exact: true }) + .click({ noWaitAfter: true }) + const confirmation = await confirmationShown + await expect(confirmation.getByRole('button', { name: 'Cancel', exact: true })).toBeFocused() + const returnedPrompt = app.waitForEvent('window') + await confirmation + .getByRole('button', { name: 'Cancel', exact: true }) + .click({ noWaitAfter: true }) + const folderPrompt = await returnedPrompt + expect( + await window.evaluate(async () => + ( + globalThis as typeof globalThis & { simDesktop: SimDesktopApi } + ).simDesktop.settings.getPreferences() + ) + ).toMatchObject({ fullFileAccess: false }) + const acceptedConfirmation = app.waitForEvent('window') + await folderPrompt + .getByRole('button', { name: 'Allow all files…', exact: true }) + .click({ noWaitAfter: true }) + const allowAll = await acceptedConfirmation + await allowAll.screenshot({ + path: test.info().outputPath('full-file-access-confirmation.png'), + }) + await allowAll + .getByRole('button', { name: 'Allow all files', exact: true }) + .click({ noWaitAfter: true }) + expect(await permission.result).toMatchObject({ + ok: true, + data: { text: 'full access contents' }, + }) await expect( window.getByRole('switch', { name: 'Full file access', exact: true }) ).toBeChecked() @@ -383,11 +416,11 @@ createRoot(document.getElementById('settings')).render( await expect( window.getByRole('switch', { name: 'Full file access', exact: true }) ).not.toBeChecked() - const permission = await requestPermission({ operation: 'read', toolCallId: 'fullAccess' }) - await permission.prompt + const revoked = await requestPermission({ operation: 'read', toolCallId: 'fullAccess' }) + await revoked.prompt .getByRole('button', { name: "Don't allow", exact: true }) .click({ noWaitAfter: true }) - expect(await permission.result).toMatchObject({ ok: false }) + expect(await revoked.result).toMatchObject({ ok: false }) }) await test.step('a folder grant works in another chat but does not permit symlink escapes', async () => { expect(await invoke({ operation: 'read', toolCallId: 'otherChat' })).toMatchObject({ From d4f8483e1c7f119b20a559178f118000d13cb6c2 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 8 Oct 2026 21:12:36 -0700 Subject: [PATCH 2/3] improvement(desktop): add explicit full file access from folder consent --- apps/desktop/e2e/browser-page-dialogs.spec.ts | 10 ++- apps/desktop/e2e/local-files.spec.ts | 30 ++++++++ apps/desktop/src/main/desktop-settings.ts | 6 +- apps/desktop/src/main/index.ts | 8 +- .../src/main/local-file-permissions.ts | 77 ++++++++++++++----- apps/desktop/src/preload/index.ts | 7 ++ .../browser-session/browser-page-dialog.tsx | 4 +- .../settings/components/desktop/desktop.tsx | 1 + packages/desktop-bridge/src/index.ts | 2 + 9 files changed, 119 insertions(+), 26 deletions(-) diff --git a/apps/desktop/e2e/browser-page-dialogs.spec.ts b/apps/desktop/e2e/browser-page-dialogs.spec.ts index ea3830b16e0..d5de0236343 100644 --- a/apps/desktop/e2e/browser-page-dialogs.spec.ts +++ b/apps/desktop/e2e/browser-page-dialogs.spec.ts @@ -365,7 +365,11 @@ createRoot(document.getElementById('root')).render(createElement(Fixture));`, await expect.poll(() => pageTitle()).toBe('form') await check('clean reloads and navigation never ask to discard changes', async () => { + await inPage("document.documentElement.dataset.reloadProbe = 'before'") await panelAction({ action: 'reload' }) + await expect + .poll(() => inPage('document.documentElement.dataset.reloadProbe')) + .toBeUndefined() await expect.poll(() => pageTitle()).toBe('form') expect(await pageDialog()).toBeNull() await panelAction({ action: 'navigate', url: `${site}/next` }) @@ -395,7 +399,11 @@ createRoot(document.getElementById('root')).render(createElement(Fixture));`, await panelAction({ action: 'back' }) await expect.poll(pageUrl).toBe(`${site}/form`) expect(await pageDialog()).toBeNull() - await inPage("document.getElementById('draft').value = ''") + await inPage(`setTimeout(() => { location.href = ${JSON.stringify(`${site}/next`)} })`) + await expect.poll(pageUrl).toBe(`${site}/next`) + expect(await pageDialog()).toBeNull() + await panelAction({ action: 'navigate', url: `${site}/form` }) + await expect.poll(pageUrl).toBe(`${site}/form`) }) await check('leaving a draft from the URL bar asks, and Stay keeps it', async () => { diff --git a/apps/desktop/e2e/local-files.spec.ts b/apps/desktop/e2e/local-files.spec.ts index e0786ccf5c0..f78694e9c39 100644 --- a/apps/desktop/e2e/local-files.spec.ts +++ b/apps/desktop/e2e/local-files.spec.ts @@ -304,7 +304,36 @@ createRoot(document.getElementById('settings')).render( .click({ noWaitAfter: true }) expect(await survivor).toMatchObject({ ok: true, data: { text: 'shared contents' } }) }) + await test.step('expired requests cannot enable Full file access from a confirmation', async () => { + if (!app) throw new Error('Desktop app is not running') + calls.expiringFullAccess = { + toolName: 'read_local_file', + args: { path: join(outside, 'private.txt') }, + } + const permission = await requestPermission({ + operation: 'read', + toolCallId: 'expiringFullAccess', + }) + const shown = app.waitForEvent('window') + await permission.prompt + .getByRole('button', { name: 'Allow all files…', exact: true }) + .click({ noWaitAfter: true }) + const confirmation = await shown + calls.expiringFullAccess = undefined + await confirmation + .getByRole('button', { name: 'Allow all files', exact: true }) + .click({ noWaitAfter: true }) + expect(await permission.result).toMatchObject({ ok: false }) + expect( + await window.evaluate(async () => + ( + globalThis as typeof globalThis & { simDesktop: SimDesktopApi } + ).simDesktop.settings.getPreferences() + ) + ).toMatchObject({ fullFileAccess: false }) + }) await test.step('Full file access is opt-in, survives restart, and stops granting access when disabled', async () => { + if (!app) throw new Error('Desktop app is not running') const fullFolder = join(root, 'Full access') mkdirSync(fullFolder) writeFileSync(join(fullFolder, 'file.txt'), 'full access contents') @@ -912,6 +941,7 @@ createRoot(document.getElementById('settings')).render( await expect( window.getByText('Could not update file access', { exact: true }) ).toBeVisible() + await expect(toggle).not.toBeChecked() expect( await window.evaluate(async () => ( diff --git a/apps/desktop/src/main/desktop-settings.ts b/apps/desktop/src/main/desktop-settings.ts index af8119fd943..3f9d8627bdd 100644 --- a/apps/desktop/src/main/desktop-settings.ts +++ b/apps/desktop/src/main/desktop-settings.ts @@ -53,6 +53,7 @@ export interface DesktopSettingsService { interface DesktopSettingsServiceDeps { config: ConfigStore + onFullFileAccessChanged?: (preferences: DesktopPreferences) => void getMainWindow: () => BrowserWindow | null openMainWindowAt: (route?: string) => void setAutoDownloadUpdates: (enabled: boolean) => void @@ -185,9 +186,12 @@ export function createDesktopSettingsService( deps.config.set('fullFileAccess', enabled) if (!deps.config.flush()) { deps.config.set('fullFileAccess', false) + deps.onFullFileAccessChanged?.(read()) throw new Error('Could not save file access settings') } - return read() + const preferences = read() + deps.onFullFileAccessChanged?.(preferences) + return preferences }, setPreventSleepWhileRunning(enabled) { deps.config.set('preventSleepWhileRunning', enabled) diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 8bac14e70ee..71b2f67449e 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -172,7 +172,10 @@ function main(): void { }) const localFilePermissions = new LocalFilePermissions( localFilesystem, - () => config.get('fullFileAccess') === true + () => config.get('fullFileAccess') === true, + () => { + desktopSettings.setFullFileAccess(true) + } ) const clearLocalFileAccess = async () => { config.set('fullFileAccess', false) @@ -546,6 +549,9 @@ function main(): void { const desktopSettings = createDesktopSettingsService({ config, + onFullFileAccessChanged: (preferences) => { + broadcast('desktop:settings:full-file-access-changed', preferences) + }, getMainWindow, openMainWindowAt: (route) => void openMainWindowAt(route), setAutoDownloadUpdates: (enabled) => updater?.setAutoDownload(enabled), diff --git a/apps/desktop/src/main/local-file-permissions.ts b/apps/desktop/src/main/local-file-permissions.ts index fd9490cba4e..ce68c358b3a 100644 --- a/apps/desktop/src/main/local-file-permissions.ts +++ b/apps/desktop/src/main/local-file-permissions.ts @@ -54,7 +54,8 @@ export class LocalFilePermissions { constructor( private readonly filesystem: LocalFilesystemService, - private readonly fullFileAccess: () => boolean = () => false + private readonly fullFileAccess: () => boolean = () => false, + private readonly enableFullFileAccess?: () => void ) {} async authorize( @@ -71,18 +72,7 @@ export class LocalFilePermissions { throw new Error('A valid destination workspace and folder are required for imports.') const path = await realpath(nativePath(authorization.args.path)) if (this.fullFileAccess()) { - const info = await stat(path) - const folder = info.isDirectory() ? path : dirname(path) - const identity = await stat(folder, { bigint: true }) - return this.authorizedAccess( - { - path, - resolve: realpath, - open: (requested, directory = false) => - openNativeFile(folder, relative(folder, requested), identity, directory), - }, - { ...context, isCurrent: () => context.isCurrent() && this.fullFileAccess() } - ) + return this.unrestrictedAccess(path, context) } const existing = await this.filesystem.nativeAccess(path) if (existing) return this.authorizedAccess(existing, context) @@ -114,11 +104,30 @@ export class LocalFilePermissions { } pending.contexts.add(context) await this.waitForDecision(pending, context) + if (this.fullFileAccess()) return this.unrestrictedAccess(path, context) const access = await this.filesystem.nativeAccess(path) if (!access) throw new Error('The approved folder is no longer available.') return this.authorizedAccess(access, context) } + private async unrestrictedAccess( + path: string, + context: LocalFilePermissionContext + ): Promise { + const info = await stat(path) + const folder = info.isDirectory() ? path : dirname(path) + const identity = await stat(folder, { bigint: true }) + return this.authorizedAccess( + { + path, + resolve: realpath, + open: (requested, directory = false) => + openNativeFile(folder, relative(folder, requested), identity, directory), + }, + { ...context, isCurrent: () => context.isCurrent() && this.fullFileAccess() } + ) + } + private waitForDecision( pending: PendingFolderDecision, context: LocalFilePermissionContext @@ -170,7 +179,7 @@ export class LocalFilePermissions { signal: AbortSignal ): Promise { const context = await this.currentContext(contexts, signal) - if (await this.filesystem.nativeAccess(folder)) return + if (this.fullFileAccess() || (await this.filesystem.nativeAccess(folder))) return const root = await lstat(folder, { bigint: true }) if (!root.isDirectory()) throw new Error('The folder is no longer available.') const displayedPath = JSON.stringify(folder).replace( @@ -184,16 +193,42 @@ export class LocalFilePermissions { signal, title: 'Allow access to this folder?', message: displayedPath, - detail: `Sim can read files in this folder and its subfolders, use them across chats, and import them into your workspaces on ${context.origin}.\n\nManage or remove access in File → Folder Access.`, - buttons: ['Allow folder', "Don't allow"], + detail: `Sim can read and import files from this folder and its subfolders across chats on ${context.origin}.\n\nManage access in File → Folder Access.`, + buttons: [ + 'Allow folder', + "Don't allow", + ...(this.enableFullFileAccess ? ['Allow all files…'] : []), + ], defaultId: 1, cancelId: 1, } - const result = await (parent ? showShellDialog(parent, options) : showShellDialog(options)) - signal.throwIfAborted() - if (result.response !== 0) throw new Error('The user did not allow this local file access.') - const current = await this.currentContext(contexts, signal) - await this.filesystem.grantDirectory({ path: folder }, current.generation, root) + while (true) { + await this.currentContext(contexts, signal) + const result = await (parent ? showShellDialog(parent, options) : showShellDialog(options)) + signal.throwIfAborted() + if (result.response === 2 && this.enableFullFileAccess) { + const confirmation = { + signal, + title: 'Allow full file access?', + message: `Sim can read and import files from any folder on this computer across chats on ${context.origin}.`, + detail: 'Turn this off in Settings → Desktop → Full file access.', + buttons: ['Allow all files', 'Cancel'], + defaultId: 1, + cancelId: 1, + } + const answer = await (parent + ? showShellDialog(parent, confirmation) + : showShellDialog(confirmation)) + await this.currentContext(contexts, signal) + if (answer.response !== 0) continue + this.enableFullFileAccess() + return + } + if (result.response !== 0) throw new Error('The user did not allow this local file access.') + const current = await this.currentContext(contexts, signal) + await this.filesystem.grantDirectory({ path: folder }, current.generation, root) + return + } } private async authorizedAccess( diff --git a/apps/desktop/src/preload/index.ts b/apps/desktop/src/preload/index.ts index 3a181a33eee..3704ce86a74 100644 --- a/apps/desktop/src/preload/index.ts +++ b/apps/desktop/src/preload/index.ts @@ -181,6 +181,13 @@ const api: SimDesktopApi = { ipcRenderer.invoke('desktop:settings:set', key, value), setFullFileAccess: (enabled: boolean): Promise => ipcRenderer.invoke('desktop:settings:set-full-file-access', enabled), + onFullFileAccessChanged: ( + callback: (preferences: DesktopPreferences) => void + ): (() => void) => { + const listener = (_event: unknown, preferences: DesktopPreferences) => callback(preferences) + ipcRenderer.on('desktop:settings:full-file-access-changed', listener) + return () => ipcRenderer.removeListener('desktop:settings:full-file-access-changed', listener) + }, setPreventSleepWhileRunning: (enabled: boolean): Promise => ipcRenderer.invoke('desktop:settings:set-prevent-sleep', enabled), setBrowserSearchSuggestionsEnabled: (enabled: boolean): Promise => diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-dialog.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-dialog.tsx index 63b7cc67f36..3503d33a5ae 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-dialog.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-dialog.tsx @@ -17,8 +17,8 @@ export function BrowserPageDialogModal({ dialog, open, onAnswer }: BrowserPageDi !nextOpen && answer(false)} - title='Leave site?' - text='Changes you made may not be saved.' + title='Leave page?' + text='Changes on this page may not be saved.' defaultAction='dismiss' dismissLabel='Stay' confirm={{ label: 'Leave', onClick: () => answer(true) }} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/desktop/desktop.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/desktop/desktop.tsx index 9d9591eef17..699233498d1 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/desktop/desktop.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/desktop/desktop.tsx @@ -52,6 +52,7 @@ export function Desktop() { ?.getDevice() .then((device) => setRunsInBackground(device !== null)) .catch(() => setRunsInBackground(false)) + return bridge.settings.onFullFileAccessChanged?.(setPreferences) }, [router, workspaceId]) const updatePreference = async (key: DesktopPreferenceKey, value: boolean) => { diff --git a/packages/desktop-bridge/src/index.ts b/packages/desktop-bridge/src/index.ts index 26ac81c3971..76148ab7d87 100644 --- a/packages/desktop-bridge/src/index.ts +++ b/packages/desktop-bridge/src/index.ts @@ -988,6 +988,8 @@ interface SimDesktopSettingsApi { setPreventSleepWhileRunning?(enabled: boolean): Promise /** Optional for installed shells that predate the explicit full-file-access setting. */ setFullFileAccess?(enabled: boolean): Promise + /** Keeps settings in sync when folder consent changes access in the native shell. */ + onFullFileAccessChanged?(callback: (preferences: DesktopPreferences) => void): () => void notify(payload: DesktopNotificationPayload): Promise /** Overrides the appearance requested by browser pages. */ setBrowserTheme(theme: DesktopAppearanceTheme): Promise From 0cedab11ef515082477ceafc6df144a80a78f3d6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 8 Oct 2026 22:31:40 -0700 Subject: [PATCH 3/3] fix(desktop): clarify consent actions and await native test readiness --- apps/desktop/e2e/browser-page-dialogs.spec.ts | 6 ++++++ apps/desktop/e2e/executor-sim.ts | 1 + apps/desktop/e2e/local-files.spec.ts | 15 +++++++++------ apps/desktop/e2e/smoke.spec.ts | 3 ++- apps/desktop/src/main/desktop-settings.ts | 4 +++- apps/desktop/src/main/local-file-permissions.ts | 6 +++--- .../settings/components/desktop/desktop.tsx | 2 +- 7 files changed, 25 insertions(+), 12 deletions(-) diff --git a/apps/desktop/e2e/browser-page-dialogs.spec.ts b/apps/desktop/e2e/browser-page-dialogs.spec.ts index d5de0236343..e66012fbbaa 100644 --- a/apps/desktop/e2e/browser-page-dialogs.spec.ts +++ b/apps/desktop/e2e/browser-page-dialogs.spec.ts @@ -433,6 +433,12 @@ createRoot(document.getElementById('root')).render(createElement(Fixture));`, await userInput('#draft') await panelAction({ action: 'reload' }) await expect(shell.getByRole('button', { name: 'Stay', exact: true })).toBeFocused() + await shell.getByRole('dialog').screenshot({ + path: test.info().outputPath('leave-page-modal.png'), + animations: 'allow', + caret: 'initial', + }) + await expect(shell.getByRole('button', { name: 'Stay', exact: true })).toBeFocused() await shell.keyboard.press('Escape') await expect.poll(pageDialog).toBeNull() await expect diff --git a/apps/desktop/e2e/executor-sim.ts b/apps/desktop/e2e/executor-sim.ts index 0fd7f3d3153..f2b8e381042 100644 --- a/apps/desktop/e2e/executor-sim.ts +++ b/apps/desktop/e2e/executor-sim.ts @@ -462,6 +462,7 @@ export async function launch( app.context().pages().forEach(leaveDialogsToDesktop) app.context().on('page', leaveDialogsToDesktop) const window = await app.firstWindow() + await window.waitForURL((url) => url.origin === sim.origin, { waitUntil: 'load' }) return { app, window } } diff --git a/apps/desktop/e2e/local-files.spec.ts b/apps/desktop/e2e/local-files.spec.ts index f78694e9c39..cfe20ba2244 100644 --- a/apps/desktop/e2e/local-files.spec.ts +++ b/apps/desktop/e2e/local-files.spec.ts @@ -316,12 +316,12 @@ createRoot(document.getElementById('settings')).render( }) const shown = app.waitForEvent('window') await permission.prompt - .getByRole('button', { name: 'Allow all files…', exact: true }) + .getByRole('button', { name: 'Full file access', exact: true }) .click({ noWaitAfter: true }) const confirmation = await shown calls.expiringFullAccess = undefined await confirmation - .getByRole('button', { name: 'Allow all files', exact: true }) + .getByRole('button', { name: 'Enable', exact: true }) .click({ noWaitAfter: true }) expect(await permission.result).toMatchObject({ ok: false }) expect( @@ -347,7 +347,7 @@ createRoot(document.getElementById('settings')).render( const permission = await requestPermission({ operation: 'read', toolCallId: 'fullAccess' }) const confirmationShown = app.waitForEvent('window') await permission.prompt - .getByRole('button', { name: 'Allow all files…', exact: true }) + .getByRole('button', { name: 'Full file access', exact: true }) .click({ noWaitAfter: true }) const confirmation = await confirmationShown await expect(confirmation.getByRole('button', { name: 'Cancel', exact: true })).toBeFocused() @@ -365,14 +365,14 @@ createRoot(document.getElementById('settings')).render( ).toMatchObject({ fullFileAccess: false }) const acceptedConfirmation = app.waitForEvent('window') await folderPrompt - .getByRole('button', { name: 'Allow all files…', exact: true }) + .getByRole('button', { name: 'Full file access', exact: true }) .click({ noWaitAfter: true }) const allowAll = await acceptedConfirmation await allowAll.screenshot({ path: test.info().outputPath('full-file-access-confirmation.png'), }) await allowAll - .getByRole('button', { name: 'Allow all files', exact: true }) + .getByRole('button', { name: 'Enable', exact: true }) .click({ noWaitAfter: true }) expect(await permission.result).toMatchObject({ ok: true, @@ -939,7 +939,10 @@ createRoot(document.getElementById('settings')).render( try { await toggle.click() await expect( - window.getByText('Could not update file access', { exact: true }) + window.getByText( + 'Could not save file access settings. Your previous setting may return after restarting Sim.', + { exact: true } + ) ).toBeVisible() await expect(toggle).not.toBeChecked() expect( diff --git a/apps/desktop/e2e/smoke.spec.ts b/apps/desktop/e2e/smoke.spec.ts index 08ace958c77..7867c67d108 100644 --- a/apps/desktop/e2e/smoke.spec.ts +++ b/apps/desktop/e2e/smoke.spec.ts @@ -138,6 +138,7 @@ test.describe('desktop shell smoke', () => { test('OAuth popups share the session without inheriting the privileged preload', async () => { app = await launchApp(origin) const window = await app.firstWindow() + await window.waitForURL(`${origin}/home`, { waitUntil: 'load' }) await window.evaluate(() => { document.cookie = 'sim-e2e-session=shared; Path=/; SameSite=Lax' }) @@ -319,7 +320,7 @@ test.describe('desktop shell smoke', () => { await window.locator('#server').click() const picker = await pickerPromise - expect(picker.url()).toBe('sim-shell://pages/server.html') + await expect(picker).toHaveURL('sim-shell://pages/server.html') await expect(picker.getByRole('dialog', { name: 'Sim server', exact: true })).toBeVisible() await expect(picker.getByLabel('Server URL')).toHaveValue('http://127.0.0.1:1') await expect(picker.getByLabel('Server URL')).toBeFocused() diff --git a/apps/desktop/src/main/desktop-settings.ts b/apps/desktop/src/main/desktop-settings.ts index 3f9d8627bdd..a237202a12f 100644 --- a/apps/desktop/src/main/desktop-settings.ts +++ b/apps/desktop/src/main/desktop-settings.ts @@ -187,7 +187,9 @@ export function createDesktopSettingsService( if (!deps.config.flush()) { deps.config.set('fullFileAccess', false) deps.onFullFileAccessChanged?.(read()) - throw new Error('Could not save file access settings') + throw new Error( + 'Could not save file access settings. Your previous setting may return after restarting Sim.' + ) } const preferences = read() deps.onFullFileAccessChanged?.(preferences) diff --git a/apps/desktop/src/main/local-file-permissions.ts b/apps/desktop/src/main/local-file-permissions.ts index ce68c358b3a..9504f591327 100644 --- a/apps/desktop/src/main/local-file-permissions.ts +++ b/apps/desktop/src/main/local-file-permissions.ts @@ -197,7 +197,7 @@ export class LocalFilePermissions { buttons: [ 'Allow folder', "Don't allow", - ...(this.enableFullFileAccess ? ['Allow all files…'] : []), + ...(this.enableFullFileAccess ? ['Full file access'] : []), ], defaultId: 1, cancelId: 1, @@ -209,10 +209,10 @@ export class LocalFilePermissions { if (result.response === 2 && this.enableFullFileAccess) { const confirmation = { signal, - title: 'Allow full file access?', + title: 'Enable full file access?', message: `Sim can read and import files from any folder on this computer across chats on ${context.origin}.`, detail: 'Turn this off in Settings → Desktop → Full file access.', - buttons: ['Allow all files', 'Cancel'], + buttons: ['Enable', 'Cancel'], defaultId: 1, cancelId: 1, } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/desktop/desktop.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/desktop/desktop.tsx index 699233498d1..7e3b8bd6ded 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/desktop/desktop.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/desktop/desktop.tsx @@ -77,7 +77,7 @@ export function Desktop() { const { pending: fullFileAccessPending, mutate: setFullFileAccess } = useDesktopPreferenceMutation( async (bridge, enabled: boolean) => bridge.settings.setFullFileAccess?.(enabled), - 'Could not update file access', + 'Could not save file access settings. Your previous setting may return after restarting Sim.', setPreferences )