Skip to content

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.

LocationRole today
solutions/determined/crates/determined-platform/src/openers.rsVault-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.rsget_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_pathVault-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.vueThe 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.vueThe 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#openersFetches and caches VaultOpener[] per vault id from openerGetVaultOpeners; vault-root only
packages/ts/ui/app/components/DMenu.vueGeneric grouped, selectable menu (DMenuGroup/DMenuItem); what both existing open menus render through
solutions/determined/experiences/polish/app/services/types.tsNames listOpenersForPath/openWith/setDefaultOpener in its wave-2 gap table: reference methods with no endpoint behind them
solutions/determined/experiences/polish/app/components/PolishCodePreviewPane.vueHeader comment: “Open in Obsidian” was cut because no endpoint opens a URL and no @tauri-apps/* import belongs in an experience
docs/LIBRARIES.mdTier-3 rust-path-opener entry: path-opener 0.5.x already supports jump-to-line targets (open_at) and command previews

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.

  1. Add a file-path qualifying predicate in openers.rs (accepts a file, not a directory — qualifies today hard-codes accepts_directories) and an openers_for_path(vault_id, path) function returning Vec<VaultOpener> for that path.
  2. Add an open dispatch for a file path (extending OpenVaultRequest’s shape or a new request type) that calls path_opener::open_at, keeping the containment guard from workspace::open_path so a path outside the vault is refused before any opener sees it.
  3. 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.
  4. Extract ShellOpenInMenu.vue in packages/ts/app-frame/app/components/: props openers plus an onSelect callback, rendering through DMenu; no vault or skill vocabulary.
  5. Reimplement ShellVaultOpenMenu.vue and VaultsOpenMenu.vue’s bodies over ShellOpenInMenu.vue, keeping each component’s own trigger button and vault-specific verbs (focus/window/recheck) around the shared menu.
  6. Add an “Open in…” trigger to ApComponentTree.vue rows and ShellDocumentTabs.vue tabs, each calling openers_for_path/the open dispatch through a shell composable extending useVaultRegistry.
  7. Register the palette command “Open in…” over the current selection (tree row or active tab).
AreaNote
solutions/determined/crates/determined-platform/src/openers.rsFile-path qualifying predicate, openers_for_path, an open_at dispatch
solutions/determined/crates/determined-platform/src/api/v1/openers.rsopeners_for_path and file-open routes
solutions/determined/crates/determined-core/src/wire/mod.rsRequest/response types for a file-path open, reusing VaultOpener
packages/ts/app-frame/app/components/ShellOpenInMenu.vueDomain-free openers-plus-callback menu
solutions/determined/packages/shell/app/components/ShellVaultOpenMenu.vueRender through ShellOpenInMenu.vue
solutions/determined/experiences/vaults/app/components/VaultsOpenMenu.vueRender 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
  • AC-1: openers_for_path returns only openers that qualify for a file (not directory-only ones like the current vault-root menu), verified by a unit test mirroring openers.rs’s existing qualifies coverage.
  • 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.vue and VaultsOpenMenu.vue both render their “Open in” rows through ShellOpenInMenu.vue, and grep -rn ShellOpenInMenu packages/ts/app-frame finds 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.
  • Jump-to-line targeting itself (path_opener::Target); this task wires the dispatch call, a later task can pass a real line/column.
  • openWith/setDefaultOpener for 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".
  • none

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.


← Back to Tasks