feat(desktop): let users add custom apps to the Open in menus

KI
2mo ago
+3967-195

Fixes

What & why

Lands @Kalisnoir's worktrees/custom-open-in-apps branch (their two commits are preserved as-is), rebased onto main and finished:

From the contributor's branch

  • Settings → Links → Custom apps: register an app by label plus macOS bundle id and/or application name. Stored as JSON in settings.custom_apps. It sits under the click-action mappers because those are what open the "editor" the apps provide.
  • File rows in the Changes pane: the separate "Open in Editor" item and the "Open file in ▸" submenu are folded into one Open in app ▸ submenu. Its first row is the project default (with the modifier-click hint), then every editor, VS Code ▸, JetBrains ▸, and Custom ▸ when custom apps exist.
  • A custom:<uuid> id namespace (AppRef = ExternalApp | CustomAppId) so EXTERNAL_APPS stays a closed union and the exhaustive command tables in external/helpers.ts keep compiling.
  • A Custom group in every "Open in" menu, and a per-file Open file in ▸ submenu on Changes rows (context menu and dropdown).
  • open -b <bundleId> is tried before open -a <name> on macOS; on Linux the name is the binary.
  • Deleting a custom app clears any v1 global/project default that pointed at it; a launch against a missing ref fails with a clear BAD_REQUEST instead of falling through to shell.openPath.

Added on top while rebasing (627 commits behind main)

  • Regenerated the local-db migration as 0056_add_custom_apps_setting (the branch's 0046 collided with main).
  • Runtime fix: customAppSchema.omit({ id: true }) threw .omit() cannot be used on object schemas containing refinements under zod 4 while constructing the settings router, which took the entire desktop tRPC router down at boot. The input schema is now derived from the unrefined object (customAppInputSchema), with a schema test in packages/local-db.
  • Trigger fix: the three "Open in" trigger buttons and the v1 empty tab still used the static getAppOption, so choosing a custom app left the primary button disabled with no icon. They now use useAppOption, and a shared AppOptionIcon renders a window glyph for apps without a bundled asset.
  • Dangling default: the v2 per-project default lives in the renderer, so deleting its custom app left the trigger dead. useAppOptionWithFallback falls back to Finder once custom apps have loaded.
  • Lingui for all new strings (settings, menu labels) with translations in all 17 locales; check:i18n and the hardcoded-string ratchet pass.
  • Moved the section from the bottom of General to Links and rebuilt it in the terminal-scripts style: a divided container with an Add app button, rows showing label, a Bundle id badge, the identifier in mono, and a hover delete; clicking a row opens an editor dialog (Name / Bundle id / Application name with hints, Delete in the footer). Search entry updated.
  • Add custom app… at the bottom of the Custom submenu in every Open in menu and in the per-file "Open file in" submenu. The Custom group only appears once at least one custom app exists; with none, the menus look exactly as before. It deep-links to /settings/links?addApp=true, which opens the Add dialog on arrival and clears the param.
  • The Application name field also accepts a full .app path (open -a /Applications/Xcode-16.2.app), which pins one install when several versions share a bundle id — the case open -b alone cannot disambiguate.
  • Comment cleanup.

How I tested it

Unit: bun test on external, settings, OpenInExternalDropdown, TopBar, settings routes, and the new packages/local-db schema test (228 pass). bun run typecheck on desktop and local-db, biome check, bun run check:i18n all clean.

End to end in the dev desktop app over CDP, with a throwaway Probe Editor-9.9.0.app (AppleScript bundle, bundle id sh.superset.probe-editor, registered with lsregister) that logs the path it receives:

  • Baseline: open -a "Probe Editor" → Unable to find application named 'Probe Editor'; open -b sh.superset.probe-editor launches it. That is the #4688 mechanism.
  • Settings → Links → Custom apps → Add app: typed label, bundle id and application name into the dialog, submitted → row appears with the badge and mono id, settings.custom_apps holds the entry; clicking the row reopens it prefilled with Delete available.
  • Workspace top bar → Open in ▾ → Custom → Probe Editor → the app received the worktree path. The trigger then reads "Open in Probe Editor" with the window glyph, and its primary click launches the app again.
  • Changes pane → right-click config.json → Open file in → Custom → Probe Editor → the app received the file path.
  • Open in ▾ → Custom → Add custom app… → lands on Settings → Links with the Add dialog already open and no addApp param left in the URL.
  • Remove the app → row and DB entry gone; back in the workspace the trigger falls back to "Open in Finder" instead of going dead.

Follow-ups (not in this PR)

  • Command palette: the "Open in…" submenu lists built-ins only, and a custom preferred app produces no "Open in X" command (the provider is synchronous and has no access to the settings query).
  • CLI: superset settings set defaultEditor still validates against EXTERNAL_APPS, so a custom: id can only be set from the app.
  • Custom apps launch with open -b/open -a only; JetBrains-style -n --args handling and a Linux command field are not offered.
  • No confirmation on delete (the dialog's Delete button is one click).

https://claude.ai/code/session_011rGNHQKmiJejqPdYCm8BP8

Summary by CodeRabbit

  • New Features
    • Added custom app management in Settings → Links, including adding, editing, deleting, and configuring default apps.
    • Custom apps now appear in “Open in” menus across workspace file actions.
    • Added support for macOS bundle IDs, application names, and Linux executable commands.
    • Added guidance for locating bundle IDs and improved app icons and menu organization.
  • Bug Fixes
    • Prevented deleted or unsupported custom apps from silently opening with the system default.
  • Localization
    • Added and updated translations for custom app management and opening files in external apps.