feat(desktop): let users add custom apps to the Open in menus
KI
2mo ago+3967+3967+0123456789012345678901234567890123456789-195-195-012345678901234567890123456789
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) soEXTERNAL_APPSstays a closed union and the exhaustive command tables inexternal/helpers.tskeep 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 beforeopen -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 refinementsunder 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 inpackages/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 useuseAppOption, and a sharedAppOptionIconrenders 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.
useAppOptionWithFallbackfalls back to Finder once custom apps have loaded. - Lingui for all new strings (settings, menu labels) with translations in all 17 locales;
check:i18nand 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
.apppath (open -a /Applications/Xcode-16.2.app), which pins one install when several versions share a bundle id — the caseopen -balone 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-editorlaunches 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_appsholds 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
addAppparam 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 defaultEditorstill validates againstEXTERNAL_APPS, so acustom:id can only be set from the app. - Custom apps launch with
open -b/open -aonly; JetBrains-style-n --argshandling and a Linuxcommandfield 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.