also looking at this
Claude/app issues review e7f207
#16
8 files
+112
-35
| @@ -18,9 +18,26 @@ export const cleanField = (value: string | undefined) => { | ||
| 18 | 18 | return text === 'missing value' ? '' : text |
| 19 | 19 | } |
| 20 | 20 | |
| 21 | export interface ScriptOutcome { ok: boolean, result?: string, error?: { code?: number, message?: string } } | |
| 21 | /** | |
| 22 | * `error` is what a host that reports AppleScript error numbers returns. `reason` is what the Craft runtime returns | |
| 23 | * when the bridge itself could not start, such as `NSAppleScript unavailable`: no Apple event is ever sent, so nothing | |
| 24 | * is refused and nothing appears in the Automation pane. Craft's own `AppleScriptResult` declares neither. | |
| 25 | */ | |
| 26 | export interface ScriptOutcome { ok: boolean, result?: string, reason?: string, error?: { code?: number, message?: string } } | |
| 22 | 27 | export interface ScriptFailure { message: string, permission?: 'accessibility' | 'automation' } |
| 23 | 28 | |
| 29 | /** A failure the person can act on, carrying the privacy pane that settles it when there is one. */ | |
| 30 | export class PermissionError extends Error { | |
| 31 | constructor(message: string, readonly permission?: 'accessibility' | 'automation') { super(message) } | |
| 32 | } | |
| 33 | ||
| 34 | /** | |
| 35 | * The Automation pane has no way to add an app: a row for Launchpad appears only once macOS has asked, and each app | |
| 36 | * Launchpad controls is a separate switch underneath it. So the advice names both the prompt and the switch, instead of | |
| 37 | * sending someone to a pane that may still be empty. | |
| 38 | */ | |
| 39 | const automationAdvice = (app: string) => `Launchpad needs your permission to control ${app}. Choose OK when macOS asks, or, if you answered Don't Allow before, switch ${app} on under Launchpad in System Settings > Privacy & Security > Automation.` | |
| 40 | ||
| 24 | 41 | /** |
| 25 | 42 | * What to tell someone when a script did not run. Newer Craft hosts report the AppleScript error number, which separates |
| 26 | 43 | * a refused permission from a script that broke; older hosts report only that it failed, so the permission hint stays the |
| @@ -28,12 +45,17 @@ export interface ScriptFailure { message: string, permission?: 'accessibility' | | ||
| 28 | 45 | */ |
| 29 | 46 | export function describeScriptFailure(app: string, outcome: ScriptOutcome): ScriptFailure { |
| 30 | 47 | const code = typeof outcome.error?.code === 'number' ? outcome.error.code : undefined |
| 31 | const automation: ScriptFailure = { message: `Allow Launchpad to control ${app} under System Settings > Privacy & Security > Automation, then try again.`, permission: 'automation' } | |
| 48 | const automation: ScriptFailure = { message: automationAdvice(app), permission: 'automation' } | |
| 49 | // A script that never ran was never refused, so sending someone to a privacy pane would only waste their time. | |
| 50 | const reason = outcome.reason?.trim() | |
| 51 | if (code === undefined && reason) return { message: `Launchpad could not run that script: ${reason}. The script never reached ${app}, so no permission is missing.` } | |
| 32 | 52 | if (code === undefined) return automation |
| 33 | 53 | if (code === -1743 || code === -1744) return automation |
| 34 | 54 | if (code === -25211 || code === -1719) return { message: 'Allow Launchpad under System Settings > Privacy & Security > Accessibility, then try again.', permission: 'accessibility' } |
| 35 | 55 | if (code === -128) return { message: 'The request was cancelled.' } |
| 36 | 56 | if (code === -600) return { message: `${app} is not running and could not be opened.` } |
| 57 | // A dialog of the app's own, such as the Finder warning before the Trash is emptied, holds the event open until it times out. | |
| 58 | if (code === -1712) return { message: `${app} is waiting for an answer. Switch to ${app}, finish what it is asking, then try again.` } | |
| 37 | 59 | const detail = outcome.error?.message?.trim() |
| 38 | 60 | return { message: detail ? `${app} could not do that: ${detail}` : `${app} could not do that (error ${code}).` } |
| 39 | 61 | } |