mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
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.
This commit is contained in:
@@ -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(<MacFolderAccessFixDialog />)
|
||||
|
||||
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(<MacFolderAccessFixDialog />)
|
||||
|
||||
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(<MacFolderAccessFixDialog />)
|
||||
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(<MacFolderAccessFixDialog />)
|
||||
|
||||
await userEvent.click(footerCloseButton())
|
||||
await userEvent.click(footerButton('Cancel'))
|
||||
|
||||
expect(useMacFolderAccessFixStore.getState().open).toBe(false)
|
||||
})
|
||||
|
||||
@@ -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 (
|
||||
<li className="flex items-start gap-2">
|
||||
@@ -38,10 +36,9 @@ function Step({
|
||||
) : (
|
||||
<CircleDashed className="mt-0.5 size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
|
||||
)}
|
||||
<div className="flex min-w-0 flex-1 flex-col items-start gap-1.5">
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<span className="text-sm text-foreground">{label}</span>
|
||||
{helper ? <span className="text-xs text-muted-foreground">{helper}</span> : null}
|
||||
{action}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
@@ -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 (
|
||||
<>
|
||||
<ol className="flex flex-col gap-3">
|
||||
<Step
|
||||
done={allowed}
|
||||
done={mismatch.restartWillHelp === true}
|
||||
label={translate(
|
||||
'auto.components.shared.MacFolderAccessFixDialog.stepAllow',
|
||||
'Allow Orca under Files and Folders'
|
||||
)}
|
||||
action={
|
||||
// Why keep the button on an unanswered probe: opening the pane is the one step the
|
||||
// user can always take, and hiding it would strand them.
|
||||
mismatch.restartWillHelp === true ? undefined : (
|
||||
<Button variant="outline" size="sm" onClick={onOpenSettings}>
|
||||
{translate(
|
||||
'auto.components.shared.MacFolderAccessFixDialog.openSystemSettings',
|
||||
'Open System Settings'
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
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
|
||||
}
|
||||
/>
|
||||
<Step
|
||||
@@ -93,17 +81,6 @@ function FixSteps({
|
||||
'auto.components.shared.MacFolderAccessFixDialog.restartConsequence',
|
||||
'Open terminals and agents will restart.'
|
||||
)}
|
||||
action={
|
||||
<Button size="sm" onClick={onRestart} disabled={!allowed || busy}>
|
||||
{busy ? <LoaderCircle className="size-4 animate-spin" /> : null}
|
||||
{busy
|
||||
? translate(
|
||||
'auto.components.shared.MacFolderAccessFixDialog.restarting',
|
||||
'Restarting…'
|
||||
)
|
||||
: translate('auto.components.shared.MacFolderAccessFixDialog.restart', 'Restart')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</ol>
|
||||
{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 (
|
||||
<Button size="sm" onClick={onCancel}>
|
||||
{translate('auto.components.shared.MacFolderAccessFixDialog.close', 'Close')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
if (mismatch.restartWillHelp === false) {
|
||||
return (
|
||||
<>
|
||||
<Button variant="ghost" size="sm" onClick={onCancel}>
|
||||
{translate('auto.components.shared.MacFolderAccessFixDialog.cancel', 'Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" onClick={onOpenSettings}>
|
||||
{openSettingsLabel}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{mismatch.restartWillHelp === null ? (
|
||||
<Button variant="ghost" size="sm" onClick={onOpenSettings} disabled={busy}>
|
||||
{openSettingsLabel}
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="ghost" size="sm" onClick={onCancel} disabled={busy}>
|
||||
{translate('auto.components.shared.MacFolderAccessFixDialog.cancel', 'Cancel')}
|
||||
</Button>
|
||||
)}
|
||||
<Button size="sm" onClick={onRestart} disabled={busy}>
|
||||
{busy ? <LoaderCircle className="size-4 animate-spin" /> : null}
|
||||
{busy
|
||||
? translate('auto.components.shared.MacFolderAccessFixDialog.restarting', 'Restarting…')
|
||||
: translate('auto.components.shared.MacFolderAccessFixDialog.restart', 'Restart')}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
<p className="text-sm text-foreground">
|
||||
{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 }
|
||||
)}
|
||||
</p>
|
||||
) : (
|
||||
<FixSteps
|
||||
<FixSteps mismatch={mismatch} restartState={restartState} />
|
||||
)}
|
||||
<DialogFooter>
|
||||
<FixFooter
|
||||
mismatch={mismatch}
|
||||
restartState={restartState}
|
||||
onCancel={close}
|
||||
onOpenSettings={onOpenSettings}
|
||||
onRestart={() => void onRestart()}
|
||||
/>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={close} disabled={busy}>
|
||||
{translate('auto.components.shared.MacFolderAccessFixDialog.close', 'Close')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user