From adfe2ee53d34e38f7eab4037da988947487290a4 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Mon, 21 Sep 2026 04:22:43 -0400 Subject: [PATCH] fix(macos): keep the fix dialog's steps a checklist and put the one action in the footer Buttons inside each step made the list look like a form, and a footer Close duplicated the X. The footer now carries the active step's action, with a ghost Cancel; a probe that could not answer says so under step 1 instead of showing a check. --- .../shared/MacFolderAccessFixDialog.test.tsx | 26 ++-- .../shared/MacFolderAccessFixDialog.tsx | 121 +++++++++++------- src/renderer/src/i18n/locales/en.json | 6 +- 3 files changed, 96 insertions(+), 57 deletions(-) diff --git a/src/renderer/src/components/shared/MacFolderAccessFixDialog.test.tsx b/src/renderer/src/components/shared/MacFolderAccessFixDialog.test.tsx index 1705076d27d..37511ce4432 100644 --- a/src/renderer/src/components/shared/MacFolderAccessFixDialog.test.tsx +++ b/src/renderer/src/components/shared/MacFolderAccessFixDialog.test.tsx @@ -30,13 +30,12 @@ function restartButton(): HTMLElement { return screen.getByRole('button', { name: /^Restart/ }) } -/** Scoped to the footer: DialogContent's own dismiss X carries the same accessible name. */ -function footerCloseButton(): HTMLElement { +function footerButton(name: string): HTMLElement { const footer = screen.getByRole('dialog').querySelector('[data-slot="dialog-footer"]') if (!(footer instanceof HTMLElement)) { throw new Error('dialog footer did not render') } - return within(footer).getByRole('button', { name: 'Close' }) + return within(footer).getByRole('button', { name }) } beforeEach(() => { @@ -83,27 +82,28 @@ describe('MacFolderAccessFixDialog', () => { expect(restartButton().hasAttribute('disabled')).toBe(false) }) - it('opens step one and blocks Restart when Orca itself is denied', () => { + it('makes System Settings the only action when Orca itself is denied', () => { openWith(false) render() - expect(screen.getByRole('button', { name: 'Open System Settings' })).toBeTruthy() - expect(restartButton().hasAttribute('disabled')).toBe(true) + expect(footerButton('Open System Settings')).toBeTruthy() + expect(screen.queryByRole('button', { name: /^Restart/ })).toBeNull() }) // An unanswered probe must not accuse the user of a missing grant, but the pane stays reachable. - it('keeps both steps available when the probe could not answer', () => { + it('keeps both actions and says so when the probe could not answer', () => { openWith(null) render() - expect(screen.getByRole('button', { name: 'Open System Settings' })).toBeTruthy() + expect(footerButton('Open System Settings')).toBeTruthy() expect(restartButton().hasAttribute('disabled')).toBe(false) + expect(screen.getByText(/Orca couldn’t check this/)).toBeTruthy() }) it('flips step one to done when a later poll reports the grant landed', async () => { openWith(false) render() - expect(restartButton().hasAttribute('disabled')).toBe(true) + expect(screen.queryByRole('button', { name: /^Restart/ })).toBeNull() act(() => { useMacFolderAccessFixStore.getState().observeMismatch({ @@ -171,7 +171,9 @@ describe('MacFolderAccessFixDialog', () => { await waitFor(() => { expect( - screen.getByText('Done. Terminals opened in your Documents folder can read it now.') + screen.getByText( + 'Terminal service restarted. Terminals in your Documents folder should work now.' + ) ).toBeTruthy() }) expect(screen.queryByRole('button', { name: /^Restart/ })).toBeNull() @@ -209,11 +211,11 @@ describe('MacFolderAccessFixDialog', () => { expect(restartButton().hasAttribute('disabled')).toBe(false) }) - it('closes on Close', async () => { + it('closes on Cancel', async () => { openWith(true) render() - await userEvent.click(footerCloseButton()) + await userEvent.click(footerButton('Cancel')) expect(useMacFolderAccessFixStore.getState().open).toBe(false) }) diff --git a/src/renderer/src/components/shared/MacFolderAccessFixDialog.tsx b/src/renderer/src/components/shared/MacFolderAccessFixDialog.tsx index 3910eb9b951..b799653ad3c 100644 --- a/src/renderer/src/components/shared/MacFolderAccessFixDialog.tsx +++ b/src/renderer/src/components/shared/MacFolderAccessFixDialog.tsx @@ -23,13 +23,11 @@ type RestartState = 'idle' | 'busy' | 'done' | 'failed' function Step({ done, label, - helper, - action + helper }: { done: boolean label: string helper?: string - action?: React.ReactNode }): React.JSX.Element { return (
  • @@ -38,10 +36,9 @@ function Step({ ) : (
  • ) @@ -49,38 +46,29 @@ function Step({ function FixSteps({ mismatch, - restartState, - onOpenSettings, - onRestart + restartState }: { mismatch: PtyManagementFolderAccessMismatch restartState: RestartState - onOpenSettings: () => void - onRestart: () => void }): React.JSX.Element { - // Why `!== false`: a probe that could not answer must not accuse the user of a missing grant. - const allowed = mismatch.restartWillHelp !== false - const busy = restartState === 'busy' return ( <>
      - {translate( - 'auto.components.shared.MacFolderAccessFixDialog.openSystemSettings', - 'Open System Settings' - )} - - ) + helper={ + // Why only when unanswered: a probe that could not answer must not accuse the user of a + // missing grant, but it must say why the step is left to them. + mismatch.restartWillHelp === null + ? translate( + 'auto.components.shared.MacFolderAccessFixDialog.stepAllowUnknown', + 'Orca couldn’t check this. Skip it if Orca is already allowed.' + ) + : undefined } /> - {busy ? : null} - {busy - ? translate( - 'auto.components.shared.MacFolderAccessFixDialog.restarting', - 'Restarting…' - ) - : translate('auto.components.shared.MacFolderAccessFixDialog.restart', 'Restart')} - - } />
    {restartState === 'failed' ? ( @@ -118,6 +95,65 @@ function FixSteps({ ) } +/** The footer carries the active step's one action, so the steps stay a checklist. */ +function FixFooter({ + mismatch, + restartState, + onCancel, + onOpenSettings, + onRestart +}: { + mismatch: PtyManagementFolderAccessMismatch + restartState: RestartState + onCancel: () => void + onOpenSettings: () => void + onRestart: () => void +}): React.JSX.Element { + const busy = restartState === 'busy' + const openSettingsLabel = translate( + 'auto.components.shared.MacFolderAccessFixDialog.openSystemSettings', + 'Open System Settings' + ) + if (restartState === 'done') { + return ( + + ) + } + if (mismatch.restartWillHelp === false) { + return ( + <> + + + + ) + } + return ( + <> + {mismatch.restartWillHelp === null ? ( + + ) : ( + + )} + + + ) +} + /** * The remedy for a daemon macOS refuses a folder to (STA-7948), raised from the folder-access * toast. Two steps, because a restart alone only works once Orca itself is allowed again — which @@ -209,22 +245,21 @@ export function MacFolderAccessFixDialog(): React.JSX.Element | null {

    {translate( 'auto.components.shared.MacFolderAccessFixDialog.done', - 'Done. Terminals opened in your {{folder}} can read it now.', + 'Terminal service restarted. Terminals in your {{folder}} should work now.', { folder } )}

    ) : ( - + )} + + void onRestart()} /> - )} - - diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 6dd8141d3c4..5f8958f0cd2 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -6414,8 +6414,10 @@ "restartFailed": "Restart failed. Try again from Settings → Terminal → Manage Sessions.", "title": "Fix access to your {{folder}}", "lead": "macOS is blocking Orca’s terminal service from this folder.", - "done": "Done. Terminals opened in your {{folder}} can read it now.", - "close": "Close" + "done": "Terminal service restarted. Terminals in your {{folder}} should work now.", + "close": "Close", + "cancel": "Cancel", + "stepAllowUnknown": "Orca couldn’t check this. Skip it if Orca is already allowed." }, "macFolderAccessFolderName": { "documents": "Documents folder",