T-9NAX-open-in-for-files
State: planning/proposed · Impact: medium · Complexity: medium
The vault-root “open with…” menu already exists, but there is no way to open a single file — a resource a skill carries, a note in the tree, a tab — in an installed app. This task extends the opener facade to file paths and gives the shell one shared “Open in…” menu component, replacing two near-duplicate menus that grew because app-frame cannot import from an experience.
| Location | Role today |
|---|---|
solutions/determined/crates/determined-platform/src/openers.rs | Vault-root-only facade over path_opener 0.5: qualifies, resolve_default, vault_openers, open_vault; every candidate must accepts_directories |
solutions/determined/crates/determined-platform/src/api/v1/openers.rs | get_vault_openers/open_vault routes; its doc comment states files inside a vault stay on workspace::open_path’s containment guard |
solutions/determined/crates/determined-platform/src/api/v1/workspace.rs#open_path | Vault-relative path open with a containment guard; not an opener-choice surface |
solutions/determined/crates/determined-core/src/wire/mod.rs#VaultOpener | {app_id, name, is_default}, a hand-mirrored subset of path_opener::PathOpener, vault-root shaped |
solutions/determined/packages/shell/app/components/ShellVaultOpenMenu.vue | The vault card’s Open ▾ menu; its own header comment explains why it does not share code with VaultsOpenMenu.vue — shell sits below experiences and cannot import one |
solutions/determined/experiences/vaults/app/components/VaultsOpenMenu.vue | The vaults experience’s richer Open-plus-chevron menu, the near-duplicate ShellVaultOpenMenu.vue’s comment points at |
solutions/determined/packages/shell/app/composables/useVaultRegistry.ts#openers | Fetches and caches VaultOpener[] per vault id from openerGetVaultOpeners; vault-root only |
packages/ts/ui/app/components/DMenu.vue | Generic grouped, selectable menu (DMenuGroup/DMenuItem); what both existing open menus render through |
solutions/determined/experiences/polish/app/services/types.ts | Names listOpenersForPath/openWith/setDefaultOpener in its wave-2 gap table: reference methods with no endpoint behind them |
solutions/determined/experiences/polish/app/components/PolishCodePreviewPane.vue | Header comment: “Open in Obsidian” was cut because no endpoint opens a URL and no @tauri-apps/* import belongs in an experience |
docs/LIBRARIES.md | Tier-3 rust-path-opener entry: path-opener 0.5.x already supports jump-to-line targets (open_at) and command previews |
Proposed
Section titled “Proposed”determined-platform/src/openers.rs gains a file-path opener path: a new openers_for_path route
(api/v1/openers.rs) lists openers that qualify for a file — filtered by FileSupport instead of
accepts_directories — and opening one dispatches through path_opener::open_at so a
jump-to-line target can ride along later. A new, domain-free ShellOpenInMenu.vue in
@sksizer/app-frame takes a list of openers and a select callback — no vault or skill noun in its
props — and becomes what ShellVaultOpenMenu.vue and VaultsOpenMenu.vue render inside, closing
the reason their bodies diverged today. Agent Manager’s tree rows and tabs each add an “Open in…”
trigger over this menu; a palette command “Open in…” reaches whatever the current selection is.
Approach
Section titled “Approach”- Add a file-path qualifying predicate in
openers.rs(accepts a file, not a directory —qualifiestoday hard-codesaccepts_directories) and anopeners_for_path(vault_id, path)function returningVec<VaultOpener>for that path. - Add an open dispatch for a file path (extending
OpenVaultRequest’s shape or a new request type) that callspath_opener::open_at, keeping the containment guard fromworkspace::open_pathso a path outside the vault is refused before any opener sees it. - Add the routes to
api/v1/openers.rs:openers_for_path(GET) and the file-open dispatch (POST), following the existing route-shape conventions documented in that module. - Extract
ShellOpenInMenu.vueinpackages/ts/app-frame/app/components/: propsopenersplus anonSelectcallback, rendering throughDMenu; no vault or skill vocabulary. - Reimplement
ShellVaultOpenMenu.vueandVaultsOpenMenu.vue’s bodies overShellOpenInMenu.vue, keeping each component’s own trigger button and vault-specific verbs (focus/window/recheck) around the shared menu. - Add an “Open in…” trigger to
ApComponentTree.vuerows andShellDocumentTabs.vuetabs, each callingopeners_for_path/the open dispatch through a shell composable extendinguseVaultRegistry. - Register the palette command “Open in…” over the current selection (tree row or active tab).
| Area | Note |
|---|---|
solutions/determined/crates/determined-platform/src/openers.rs | File-path qualifying predicate, openers_for_path, an open_at dispatch |
solutions/determined/crates/determined-platform/src/api/v1/openers.rs | openers_for_path and file-open routes |
solutions/determined/crates/determined-core/src/wire/mod.rs | Request/response types for a file-path open, reusing VaultOpener |
packages/ts/app-frame/app/components/ShellOpenInMenu.vue | Domain-free openers-plus-callback menu |
solutions/determined/packages/shell/app/components/ShellVaultOpenMenu.vue | Render through ShellOpenInMenu.vue |
solutions/determined/experiences/vaults/app/components/VaultsOpenMenu.vue | Render through ShellOpenInMenu.vue |
solutions/determined/experiences/agent-pants/app/components/ApComponentTree.vue | ”Open in…” trigger per row |
packages/ts/app-frame/app/components/ShellDocumentTabs.vue | ”Open in…” trigger per tab |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
openers_for_pathreturns only openers that qualify for a file (not directory-only ones like the current vault-root menu), verified by a unit test mirroringopeners.rs’s existingqualifiescoverage. - AC-2: Opening a file from the tree and from a tab both dispatch through
path_opener::open_at, verified by at least one test per surface. - AC-3: A path outside the vault is refused before any opener launches, exercised the same
way
workspace::open_path’s containment guard already is. - AC-4:
ShellVaultOpenMenu.vueandVaultsOpenMenu.vueboth render their “Open in” rows throughShellOpenInMenu.vue, andgrep -rn ShellOpenInMenu packages/ts/app-framefinds the new component with no vault or skill identifier in its props. - AC-5: The command palette lists “Open in…” and it opens the current selection.
Out of scope
Section titled “Out of scope”- Jump-to-line targeting itself (
path_opener::Target); this task wires the dispatch call, a later task can pass a real line/column. openWith/setDefaultOpenerfor polish’s own wave-2 gap table — this task closes the gap at the platform layer; polish adopting it is separate.- Any change to
path_opener’s own crate (docs/LIBRARIES.md’s tier-3 entry): consumed as published,path-opener = "0.5".
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”From the user’s Agent Manager planning request of 2026-09-07, expanded in
solutions/agent-pants/docs/vault/design/workflow-and-ux-assessment.md. ShellVaultOpenMenu.vue’s
own header comment already documents the constraint this task resolves: shell sits below
experiences in the layer chain, so a shared menu had to move down to @sksizer/app-frame rather
than up.