ReviewOS

also looking at this

stacks/launchpad

Claude/app issues review e7f207

#16
Merged glennmichael123 wants to merge claude/app-issues-review-e7f207 into main
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.

resources/functions/applescript.tsmodified+9-1
Changes to resources/functions/applescript.ts
@@ -18,7 +18,12 @@ export const cleanField = (value: string | undefined) => {
1818 return text === 'missing value' ? '' : text
1919}
2020
21export 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 */
26export interface ScriptOutcome { ok: boolean, result?: string, reason?: string, error?: { code?: number, message?: string } }
2227export interface ScriptFailure { message: string, permission?: 'accessibility' | 'automation' }
2328
2429/** 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
4146export function describeScriptFailure(app: string, outcome: ScriptOutcome): ScriptFailure {
4247 const code = typeof outcome.error?.code === 'number' ? outcome.error.code : undefined
4348 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.` }
4452 if (code === undefined) return automation
4553 if (code === -1743 || code === -1744) return automation
4654 if (code === -25211 || code === -1719) return { message: 'Allow Launchpad under System Settings > Privacy & Security > Accessibility, then try again.', permission: 'accessibility' }
tests/applescript.test.tsmodified+6-0
Changes to tests/applescript.test.ts
@@ -91,6 +91,12 @@ test('a failed script is explained by its error number, and older hosts keep the
9191 expect(failure(-1728, "Can't get window 3.")).toEqual({ message: "Contacts could not do that: Can't get window 3." })
9292 expect(failure(-1728)).toEqual({ message: 'Contacts could not do that (error -1728).' })
9393 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' })
94100})
95101
96102// Compiling catches syntax errors without running anything or reading any data. It does not validate app terms: