also looking at this
Claude/app issues review e7f207
#16
8 files
+112
-35
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -18,7 +18,12 @@ 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 | |
| 24 | 29 | /** A failure the person can act on, carrying the privacy pane that settles it when there is one. */ |
| @@ -41,6 +46,9 @@ const automationAdvice = (app: string) => `Launchpad needs your permission to co | ||
| 41 | 46 | export function describeScriptFailure(app: string, outcome: ScriptOutcome): ScriptFailure { |
| 42 | 47 | const code = typeof outcome.error?.code === 'number' ? outcome.error.code : undefined |
| 43 | 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.` } | |
| 44 | 52 | if (code === undefined) return automation |
| 45 | 53 | if (code === -1743 || code === -1744) return automation |
| 46 | 54 | if (code === -25211 || code === -1719) return { message: 'Allow Launchpad under System Settings > Privacy & Security > Accessibility, then try again.', permission: 'accessibility' } |
| @@ -91,6 +91,12 @@ test('a failed script is explained by its error number, and older hosts keep the | ||
| 91 | 91 | expect(failure(-1728, "Can't get window 3.")).toEqual({ message: "Contacts could not do that: Can't get window 3." }) |
| 92 | 92 | expect(failure(-1728)).toEqual({ message: 'Contacts could not do that (error -1728).' }) |
| 93 | 93 | expect(failure(undefined, 'only a message')).toMatchObject({ permission: 'automation' }) |
| 94 | // Craft reports a bridge that could not start as `reason`, with no error number. Nothing was refused, so nothing is granted. | |
| 95 | const unavailable = describeScriptFailure('Finder', { ok: false, reason: 'NSAppleScript unavailable' }) | |
| 96 | expect(unavailable.message).toBe('Launchpad could not run that script: NSAppleScript unavailable. The script never reached Finder, so no permission is missing.') | |
| 97 | expect(unavailable.permission).toBeUndefined() | |
| 98 | // An error number still wins, because it describes what the event itself did. | |
| 99 | expect(describeScriptFailure('Finder', { ok: false, reason: 'whatever', error: { code: -1743 } })).toMatchObject({ permission: 'automation' }) | |
| 94 | 100 | }) |
| 95 | 101 | |
| 96 | 102 | // Compiling catches syntax errors without running anything or reading any data. It does not validate app terms: |