Skip to content

Open in native app - #53

Merged
mutewinter merged 8 commits into
mainfrom
worktree-open-in-native-app
Jul 15, 2026
Merged

Open in native app#53
mutewinter merged 8 commits into
mainfrom
worktree-open-in-native-app

Conversation

@mutewinter

@mutewinter mutewinter commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added task-file Open actions across file previews, the file viewer, and file action menus, replacing “reveal in folder”.
    • Added Open with… (macOS) with app icons when available.
    • Updated preview and fallback CTAs to show “Open in {app}…” when supported; otherwise Download.
  • Bug Fixes
    • Improved dropdown visibility so the menu can remain hidden when only non-Open actions are unavailable.
    • Added user-facing error messaging for file-opening failures.

@mutewinter
mutewinter force-pushed the worktree-open-in-native-app branch from 380fccf to 09260ca Compare July 9, 2026 19:20
@mutewinter

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b4740e0-66ab-4236-bf40-84ced3b06a94

📥 Commits

Reviewing files that changed from the base of the PR and between 8f60b14 and 3936219.

📒 Files selected for processing (1)
  • apps/studio/src/electron-main/lib/file-open-target.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/studio/src/electron-main/lib/file-open-target.ts

📝 Walkthrough

Walkthrough

This PR adds cross-platform task-file opening with default or selected applications. Electron resolves app targets and candidates, exposes RPC handlers, and client hooks/components replace reveal-in-folder actions across menus, previews, fallbacks, and the file viewer.

Changes

Open task file feature

Layer / File(s) Summary
File-open-target resolution
apps/studio/src/electron-main/lib/file-open-target.ts, apps/studio/src/vite-env.d.ts
Adds platform-specific app and candidate resolution, icon handling, caching, persistence, and XDG_DATA_DIRS typing.
RPC routes and error handling
apps/studio/src/electron-main/rpc/routes/utils.ts, apps/studio/src/electron-main/rpc/initialize.ts
Adds file-opening and target-query RPC handlers, exports them, and skips capture for ERROR_OPENING_FILE.
Client hooks and visibility
apps/studio/src/client/hooks/*
Adds opening mutations, target and candidate queries, prefetching, and showOpen visibility.
Open/Open-with UI
apps/studio/src/client/components/open-target-icon.tsx, apps/studio/src/client/components/open-with-menu.tsx
Renders resolved app icons and lazily loads selectable candidate applications.
File interface integration
apps/studio/src/client/components/file-actions-menu.tsx, apps/studio/src/client/components/file-preview-card.tsx, apps/studio/src/client/components/file-preview-fallback.tsx, apps/studio/src/client/components/file-viewer.tsx
Adds Open/Open with actions, replaces reveal-in-folder overlays, and passes task-file data to fallback actions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FileViewer
  participant useOpenTaskFile
  participant rpcClient
  participant utilsRoute
  participant fileOpenTarget

  FileViewer->>useOpenTaskFile: openTaskFile(file)
  useOpenTaskFile->>rpcClient: call utils.openTaskFile
  rpcClient->>utilsRoute: send task-file request
  utilsRoute->>fileOpenTarget: resolve file path
  utilsRoute->>utilsRoute: open file with default app
  utilsRoute-->>rpcClient: return success or error
  rpcClient-->>useOpenTaskFile: return mutation result
Loading
sequenceDiagram
  participant OpenWithMenu
  participant OpenWithCandidates
  participant useTaskFileOpenCandidates
  participant useOpenTaskFileWith

  OpenWithMenu->>OpenWithCandidates: mount submenu
  OpenWithCandidates->>useTaskFileOpenCandidates: fetch candidate apps
  useTaskFileOpenCandidates-->>OpenWithCandidates: return app list
  OpenWithCandidates->>useOpenTaskFileWith: open selected app
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: opening task files in the native app.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-open-in-native-app

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
apps/studio/src/client/components/file-preview-card.tsx (1)

374-389: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Open-target resolution fires eagerly for every rendered media card.

Unlike FileRowCard's hover-based prefetch, useFileActions resolves the open target unconditionally as soon as ImagePreviewCard/VideoPreviewCard mount. In a grid with many files this could fan out several concurrent Electron IPC calls at once. Per-extension caching likely limits repeat cost, but consider verifying this is acceptable for typical grid sizes, or gate resolution similarly to the row's hover-prefetch pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/studio/src/client/components/file-preview-card.tsx` around lines 374 -
389, `useFileActions` currently calls `useTaskFileOpenTarget(file)` eagerly for
every mounted media card, which can trigger many simultaneous open-target
resolutions in grid views. Adjust the `ImagePreviewCard`/`VideoPreviewCard` path
to avoid unconditional resolution, either by gating it behind a user interaction
or by reusing the same hover-prefetch approach as `FileRowCard`, and keep
`showOpen` driven by the resolved target state from `useTaskFileOpenTarget`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/studio/src/client/components/file-actions-menu.tsx`:
- Around line 89-90: The open-visibility logic is duplicated across
file-actions-menu.tsx, useFileActions in file-preview-card.tsx, and
file-viewer.tsx, so update the shared source of truth instead of recomputing it
in each consumer. Fold the `showOpen`/`showOpenWith` derivation into
`useTaskFileOpenTarget` (or a small wrapper around it) and have
`file-actions-menu`, `useFileActions`, and `file-viewer` consume that shared
result so the visibility rules stay consistent in one place.
- Around line 83-90: The dropdown can render an empty menu when a file only has
the task-based Open action because the local showOpen state from
useTaskFileOpenTarget is not included in hasFileActions, causing the
FileActionsMenu early return to null. Update the FileActionsMenu rendering guard
so it considers showOpen alongside the other action flags, and keep the
trigger/button visibility logic aligned with the same Open state used by the
menu content.

In `@apps/studio/src/client/components/file-preview-card.tsx`:
- Around line 158-161: The `hasFileActions` guard in `file-preview-card.tsx` is
missing `fileActions.showOpen`, which causes open-only files to lose both the
actions trigger and the context menu. Update the `hasFileActions` calculation in
`FilePreviewCard` to include `showOpen` so the "..." button and `ContextMenu`
still render when Open is the only available action.

In `@apps/studio/src/electron-main/lib/file-open-target.ts`:
- Around line 319-330: The helper-process lookup path in file-open-target.ts can
hang indefinitely because the cached promises in inFlightTargets,
sessionTargets, and candidatesCache never settle if osascript, xdg-mime, or
powershell stalls. Add explicit timeouts around the lookup helpers used by these
caches, and make sure timed-out calls reject so the cached promise entries are
cleared and future lookups can retry. Apply the timeout handling in the lookup
flow that builds candidate targets and returns the cached results.
- Around line 211-227: loadDiskCache is assigning diskCache before the async
file read/parsing finishes, which can let concurrent callers see and mutate an
empty cache. Move the shared cache assignment to after the persisted cache has
been fully read and validated, and make sure loadDiskCache either returns the
already-populated Map or initializes it once from the parsed data so late
completion cannot overwrite newer entries.

In `@apps/studio/src/electron-main/rpc/routes/utils.ts`:
- Around line 337-364: The route in utils.ts currently accepts any absolute app
path even though execFile is used safely; before calling the open launcher in
the handler for the file-open flow, validate input.appPath against
getFileOpenCandidates(fullPath) and reject unauthorized apps with an existing
error path. Use the surrounding handler logic, resolvePathWithinTaskDir, and the
execFileAsync("open", ["-a", ...]) call site to add the check just before
launch.

---

Nitpick comments:
In `@apps/studio/src/client/components/file-preview-card.tsx`:
- Around line 374-389: `useFileActions` currently calls
`useTaskFileOpenTarget(file)` eagerly for every mounted media card, which can
trigger many simultaneous open-target resolutions in grid views. Adjust the
`ImagePreviewCard`/`VideoPreviewCard` path to avoid unconditional resolution,
either by gating it behind a user interaction or by reusing the same
hover-prefetch approach as `FileRowCard`, and keep `showOpen` driven by the
resolved target state from `useTaskFileOpenTarget`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ae43e33a-e999-43a3-b316-b415c28d4656

📥 Commits

Reviewing files that changed from the base of the PR and between 9616332 and 09260ca.

📒 Files selected for processing (13)
  • apps/studio/src/client/components/file-actions-menu.tsx
  • apps/studio/src/client/components/file-preview-card.tsx
  • apps/studio/src/client/components/file-preview-fallback.tsx
  • apps/studio/src/client/components/file-viewer.tsx
  • apps/studio/src/client/components/open-target-icon.tsx
  • apps/studio/src/client/components/open-with-menu.tsx
  • apps/studio/src/client/hooks/use-file-action-visibility.ts
  • apps/studio/src/client/hooks/use-open-task-file.ts
  • apps/studio/src/client/hooks/use-task-file-open-target.ts
  • apps/studio/src/electron-main/lib/file-open-target.ts
  • apps/studio/src/electron-main/rpc/initialize.ts
  • apps/studio/src/electron-main/rpc/routes/utils.ts
  • apps/studio/src/vite-env.d.ts

Comment thread apps/studio/src/client/components/file-actions-menu.tsx Outdated
Comment on lines +89 to +90
const showOpen = isPending || appName != null;
const showOpenWith = appName != null && isMacOS();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Consolidate duplicated open-visibility derivation.

showOpen/showOpenWith are independently recomputed with identical logic in file-actions-menu.tsx, file-preview-card.tsx (useFileActions), and file-viewer.tsx. Consider folding these into useTaskFileOpenTarget (or a small wrapper) so all consumers share one source of truth — this would have caught the early-return bug above before it shipped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/studio/src/client/components/file-actions-menu.tsx` around lines 89 -
90, The open-visibility logic is duplicated across file-actions-menu.tsx,
useFileActions in file-preview-card.tsx, and file-viewer.tsx, so update the
shared source of truth instead of recomputing it in each consumer. Fold the
`showOpen`/`showOpenWith` derivation into `useTaskFileOpenTarget` (or a small
wrapper around it) and have `file-actions-menu`, `useFileActions`, and
`file-viewer` consume that shared result so the visibility rules stay consistent
in one place.

Comment thread apps/studio/src/client/components/file-preview-card.tsx
Comment thread apps/studio/src/electron-main/lib/file-open-target.ts Outdated
Comment thread apps/studio/src/electron-main/lib/file-open-target.ts Outdated
Comment thread apps/studio/src/electron-main/rpc/routes/utils.ts
@mutewinter

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@mutewinter
mutewinter force-pushed the worktree-open-in-native-app branch 2 times, most recently from af0fe61 to f5a1d65 Compare July 10, 2026 22:34
- openTaskFile + getTaskFileIcon RPC handlers (shell.openPath, app.getFileIcon)
- Open action with the native file icon in shared file menus and viewer header
- image/video hover overlays swap reveal for open; reveal stays in context menus
- per-platform default-app resolver (NSWorkspace via JXA, registry via
  PowerShell, xdg-mime + .desktop) cached per extension in the main process
- menus, viewer header, and preview fallback label the action "Open in {app}"
  with the real app icon; file-type icon when the app can't be resolved
- preview-not-available promotes open over download when an app is known
- resolve the real associated app (name + icon) via NSWorkspace/registry
  instead of the file-type icon; persist per-extension across runs (SWR TTL)
  and warm on hover so the affordance appears only once ready
- add an "Open with" submenu listing every capable app (macOS), plus an
  openTaskFileWith handler; gate open on a resolved app so unopenable types
  (no association) don't offer a failing action
- treat "no app for this file" as an expected condition: quiet the RPC
  exception capture and toast instead of logging a stack
- move Download into the file-viewer overflow menu behind Open in {app}
- fix header overlap: filename flexes/truncates, actions stay pinned
@mutewinter
mutewinter force-pushed the worktree-open-in-native-app branch from f5a1d65 to d06b8f6 Compare July 15, 2026 18:23
@mutewinter
mutewinter marked this pull request as ready for review July 15, 2026 18:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/studio/src/client/components/file-viewer.tsx`:
- Around line 419-431: Add aria-label={openLabel} to the Open button rendered in
the showOpen branch, preserving the existing visible label and click behavior
while providing an accessible name when the label is hidden.

In `@apps/studio/src/electron-main/lib/file-open-target.ts`:
- Around line 411-419: Preserve the distinction between resolver errors and
confirmed absence: in resolveTarget, propagate all resolveAssociatedApp failures
instead of converting non-timeout errors to null; platform resolvers already
return null when nothing is found. Also update the candidate-resolution catch at
apps/studio/src/electron-main/lib/file-open-target.ts lines 359-363 to propagate
process, parse, and schema errors so its rejection handler removes
candidatesCache; the script’s empty-array result already represents no
applications.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 548fbf00-2be9-43d1-a43f-d3d35bff734d

📥 Commits

Reviewing files that changed from the base of the PR and between 09260ca and d06b8f6.

📒 Files selected for processing (13)
  • apps/studio/src/client/components/file-actions-menu.tsx
  • apps/studio/src/client/components/file-preview-card.tsx
  • apps/studio/src/client/components/file-preview-fallback.tsx
  • apps/studio/src/client/components/file-viewer.tsx
  • apps/studio/src/client/components/open-target-icon.tsx
  • apps/studio/src/client/components/open-with-menu.tsx
  • apps/studio/src/client/hooks/use-file-action-visibility.ts
  • apps/studio/src/client/hooks/use-open-task-file.ts
  • apps/studio/src/client/hooks/use-task-file-open-target.ts
  • apps/studio/src/electron-main/lib/file-open-target.ts
  • apps/studio/src/electron-main/rpc/initialize.ts
  • apps/studio/src/electron-main/rpc/routes/utils.ts
  • apps/studio/src/vite-env.d.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • apps/studio/src/client/hooks/use-file-action-visibility.ts
  • apps/studio/src/vite-env.d.ts
  • apps/studio/src/client/hooks/use-open-task-file.ts
  • apps/studio/src/client/components/file-preview-fallback.tsx
  • apps/studio/src/client/components/open-target-icon.tsx
  • apps/studio/src/client/components/open-with-menu.tsx
  • apps/studio/src/client/hooks/use-task-file-open-target.ts
  • apps/studio/src/client/components/file-actions-menu.tsx
  • apps/studio/src/electron-main/rpc/initialize.ts
  • apps/studio/src/client/components/file-preview-card.tsx
  • apps/studio/src/electron-main/rpc/routes/utils.ts

Comment thread apps/studio/src/client/components/file-viewer.tsx
Comment thread apps/studio/src/electron-main/lib/file-open-target.ts Outdated
@mutewinter
mutewinter merged commit 0388f08 into main Jul 15, 2026
2 checks passed
@mutewinter
mutewinter deleted the worktree-open-in-native-app branch July 23, 2026 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant