From 537f561d585cf9a970d304b0599236e898a0090f Mon Sep 17 00:00:00 2001
From: Neil <4138956+nwparker@users.noreply.github.com>
Date: Sat, 24 Oct 2026 18:01:06 -0700
Subject: [PATCH] Revert "fix(terminal): a file link opens in the workspace it
was clicked in (#32320)" (#33304)
* Revert "fix(terminal): a file link opens in the workspace it was clicked in (#32320)"
This reverts commit 359fec1a09f78198a8b0213e7bad53d62a2f7f00.
* test: drop redundant openFiles cast restored by the revert
---
.../editor/EditorPanelHeaderPath.tsx | 6 -
.../editor/editor-autosave-holds.ts | 2 +-
.../src/components/editor/editor-autosave.ts | 7 +-
.../editor/editor-buffer-adapter.ts | 2 +-
.../editor-external-change-tab-reset.ts | 2 +-
.../editor-external-watch-path-index.ts | 6 +-
...torPanelRemoteSiblingContentState.test.tsx | 29 ---
.../terminal-file-link-owner-resolution.ts | 75 -------
.../terminal-file-open-routing.ts | 111 +++++-----
...terminal-file-other-workspace-hint.test.ts | 33 ---
.../terminal-file-other-workspace-hint.ts | 111 ----------
.../terminal-file-other-workspace-owner.ts | 37 ----
.../terminal-link-file-open-routing.test.ts | 74 ++-----
.../terminal-link-handlers-test-fixtures.ts | 10 +-
.../terminal-link-host-workspace-file.test.ts | 45 ----
.../terminal-link-open-beside-source.test.ts | 19 +-
...-link-other-workspace-hint.sonner.test.tsx | 197 ------------------
...tor-external-watch-background-trim.test.ts | 45 +---
...editor-external-watch-disk-verification.ts | 3 +-
...tor-external-watch-event-reconciliation.ts | 16 +-
...xternal-watch-other-workspace-view.test.ts | 101 ---------
.../editor-external-watch-overflow.test.ts | 23 --
.../hooks/editor-external-watch-overflow.ts | 6 +-
.../hooks/editor-external-watch-targets.ts | 23 +-
src/renderer/src/i18n/locales/en.json | 7 +-
src/renderer/src/i18n/locales/es.json | 7 +-
src/renderer/src/i18n/locales/fr.json | 7 +-
src/renderer/src/i18n/locales/ja.json | 7 +-
src/renderer/src/i18n/locales/ko.json | 7 +-
src/renderer/src/i18n/locales/pt.json | 7 +-
src/renderer/src/i18n/locales/zh.json | 7 +-
src/renderer/src/lib/open-file-owner-scope.ts | 63 ------
...-graph-outside-workspace-file-tabs.test.ts | 36 ----
.../mobile-session-surfaces.ts | 11 +-
tests/e2e/golden-terminal-file-link.spec.ts | 43 ++--
35 files changed, 127 insertions(+), 1058 deletions(-)
delete mode 100644 src/renderer/src/components/terminal-pane/terminal-file-link-owner-resolution.ts
delete mode 100644 src/renderer/src/components/terminal-pane/terminal-file-other-workspace-hint.test.ts
delete mode 100644 src/renderer/src/components/terminal-pane/terminal-file-other-workspace-hint.ts
delete mode 100644 src/renderer/src/components/terminal-pane/terminal-file-other-workspace-owner.ts
delete mode 100644 src/renderer/src/components/terminal-pane/terminal-link-other-workspace-hint.sonner.test.tsx
delete mode 100644 src/renderer/src/hooks/editor-external-watch-other-workspace-view.test.ts
delete mode 100644 src/renderer/src/lib/open-file-owner-scope.ts
delete mode 100644 src/renderer/src/runtime/sync-runtime-graph-outside-workspace-file-tabs.test.ts
diff --git a/src/renderer/src/components/editor/EditorPanelHeaderPath.tsx b/src/renderer/src/components/editor/EditorPanelHeaderPath.tsx
index 7e2033e69e9..b3b5ce617e5 100644
--- a/src/renderer/src/components/editor/EditorPanelHeaderPath.tsx
+++ b/src/renderer/src/components/editor/EditorPanelHeaderPath.tsx
@@ -12,7 +12,6 @@ import {
import { useShortcutLabel } from '@/hooks/useShortcutLabel'
import { isImeCompositionKeyDown } from '@/lib/ime-composition-keyboard-event'
import { translate } from '@/i18n/i18n'
-import { Badge } from '@/components/ui/badge'
import type { OpenFile } from '@/store/slices/editor'
import { isVirtualEditorFile } from '@/store/slices/editor/tabs/editor-tab-content-type'
import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from '@/lib/close-all-context-menus'
@@ -120,11 +119,6 @@ export function EditorPanelHeaderPath({
{displayPath.fileName}
)}
- {!isRenaming && activeFile.readOnly === true && (
-
- {translate('auto.components.editor.EditorPanelHeader.readOnlyBadge', 'Read-only')}
-
- )}
{/* Why: the toast is opacity-0 rather than display-none, so leaving it
mounted reserves ~100px of the row from the rename field for a
message that cannot fire while renaming. */}
diff --git a/src/renderer/src/components/editor/editor-autosave-holds.ts b/src/renderer/src/components/editor/editor-autosave-holds.ts
index 87c0086b299..e06760c6fff 100644
--- a/src/renderer/src/components/editor/editor-autosave-holds.ts
+++ b/src/renderer/src/components/editor/editor-autosave-holds.ts
@@ -55,7 +55,7 @@ export function pathMutationHoldKeys(
target: EditorPathMutationTarget
): Set {
const keys = new Set(
- getOpenFilesForExternalFileChange(state.openFiles, target, state).map((file) =>
+ getOpenFilesForExternalFileChange(state.openFiles, target).map((file) =>
editorFileHoldKey(state, file)
)
)
diff --git a/src/renderer/src/components/editor/editor-autosave.ts b/src/renderer/src/components/editor/editor-autosave.ts
index 927e3ce085a..a70e8eefd08 100644
--- a/src/renderer/src/components/editor/editor-autosave.ts
+++ b/src/renderer/src/components/editor/editor-autosave.ts
@@ -1,7 +1,5 @@
import { joinPath } from '@/lib/path'
-import { openFileOwnerWorktreeId } from '@/lib/open-file-owner-scope'
import type { OpenFile } from '@/store/slices/editor'
-import type { AppState } from '@/store/types'
import { areLocalWindowsWslPathAliases } from '../../../../shared/cross-platform-path'
import { isLocalWindowsDesktopClient } from '@/lib/desktop-window-chrome'
import {
@@ -144,8 +142,7 @@ export function normalizeAutoSaveDelayMs(value: unknown): number {
export function getOpenFilesForExternalFileChange(
openFiles: OpenFile[],
- target: EditorPathMutationTarget,
- catalog?: AppState
+ target: EditorPathMutationTarget
): OpenFile[] {
if (target.indexedOpenFiles) {
return target.indexedOpenFiles.matches(openFiles)
@@ -154,7 +151,7 @@ export function getOpenFilesForExternalFileChange(
const hasRuntimeOwnerFilter = Object.hasOwn(target, 'runtimeEnvironmentId')
const targetRuntimeOwner = target.runtimeEnvironmentId?.trim() || null
return openFiles.filter((file) => {
- if (openFileOwnerWorktreeId(file, catalog) !== target.worktreeId) {
+ if (file.worktreeId !== target.worktreeId) {
return false
}
if (
diff --git a/src/renderer/src/components/editor/editor-buffer-adapter.ts b/src/renderer/src/components/editor/editor-buffer-adapter.ts
index 2b3004f6167..704fc84e5db 100644
--- a/src/renderer/src/components/editor/editor-buffer-adapter.ts
+++ b/src/renderer/src/components/editor/editor-buffer-adapter.ts
@@ -84,7 +84,7 @@ export function externalChangeRecordViewIds(
target: EditorPathMutationTarget
): Set {
const storeFiles = state?.openFiles ?? []
- const matched = getOpenFilesForExternalFileChange([...panelFiles, ...storeFiles], target, state)
+ const matched = getOpenFilesForExternalFileChange([...panelFiles, ...storeFiles], target)
return new Set(
matched.flatMap((file) =>
state && storeFiles.some((tab) => tab.id === file.id)
diff --git a/src/renderer/src/components/editor/editor-external-change-tab-reset.ts b/src/renderer/src/components/editor/editor-external-change-tab-reset.ts
index 6c7c3ca6f38..de05c0b4dc2 100644
--- a/src/renderer/src/components/editor/editor-external-change-tab-reset.ts
+++ b/src/renderer/src/components/editor/editor-external-change-tab-reset.ts
@@ -14,7 +14,7 @@ export function createEditorExternalChangeTabReset(store: AppStoreApi): (event:
const state = store.getState()
// Why no sibling expansion: views of one file share a record, so each matches by path itself.
- const matchingFiles = getOpenFilesForExternalFileChange(state.openFiles, detail, state)
+ const matchingFiles = getOpenFilesForExternalFileChange(state.openFiles, detail)
if (matchingFiles.length === 0) {
return
}
diff --git a/src/renderer/src/components/editor/editor-external-watch-path-index.ts b/src/renderer/src/components/editor/editor-external-watch-path-index.ts
index 9fb64e813f3..3ce5f2a432b 100644
--- a/src/renderer/src/components/editor/editor-external-watch-path-index.ts
+++ b/src/renderer/src/components/editor/editor-external-watch-path-index.ts
@@ -1,8 +1,6 @@
import { joinPath } from '@/lib/path'
-import { openFileOwnerWorktreeId } from '@/lib/open-file-owner-scope'
import { getExternalFileChangeRelativePath } from '@/components/right-sidebar/useFileExplorerWatch'
import type { OpenFile } from '@/store/slices/editor'
-import type { AppState } from '@/store/types'
import type { FsChangedPayload } from '../../../../shared/filesystem-entry-types'
import {
getLocalWindowsWslPathIdentity,
@@ -11,8 +9,6 @@ import {
} from '../../../../shared/cross-platform-path'
type WatchScope = {
- /** Where a read-only cross-workspace view's owner is looked up; without it, views are not matched. */
- catalog?: AppState
worktreeId: string
worktreePath: string
runtimeEnvironmentId: string | null
@@ -124,7 +120,7 @@ class IndexedOpenFileLookup {
let hasCombinedDiffConsumer = false
for (const [index, file] of openFiles.entries()) {
if (
- openFileOwnerWorktreeId(file, scope.catalog) !== scope.worktreeId ||
+ file.worktreeId !== scope.worktreeId ||
openFileRuntimeOwner(file) !== scope.runtimeEnvironmentId
) {
continue
diff --git a/src/renderer/src/components/editor/useEditorPanelRemoteSiblingContentState.test.tsx b/src/renderer/src/components/editor/useEditorPanelRemoteSiblingContentState.test.tsx
index 700c6a28384..8bef9d62b8e 100644
--- a/src/renderer/src/components/editor/useEditorPanelRemoteSiblingContentState.test.tsx
+++ b/src/renderer/src/components/editor/useEditorPanelRemoteSiblingContentState.test.tsx
@@ -193,35 +193,6 @@ describe('remote sibling editor content routing', () => {
expect(mocks.readRuntimeFileContent).not.toHaveBeenCalled()
})
- it('moves a restored read-only server tab of another workspace file to that workspace', async () => {
- const activeFile = createOpenFile({
- id: '/work/repo-b/docs/readme.md',
- filePath: '/work/repo-b/docs/readme.md',
- relativePath: '/work/repo-b/docs/readme.md',
- worktreeId: 'repo-a::/work/repo-a',
- runtimeEnvironmentId: 'runtime-1',
- readOnly: true
- })
- const route = {
- worktreeId: 'repo-b::/work/repo-b',
- relativePath: 'docs/readme.md',
- executionHostId: 'runtime:runtime-1'
- } as const
- mocks.findWorkspaceFileRoute.mockReturnValue(route)
-
- await act(async () => root?.render())
-
- // Why: a server reads only by workspace-relative path, so no view of it stays in another workspace.
- await vi.waitFor(() =>
- expect(mocks.migrateRestoredEditorFileOwner).toHaveBeenCalledWith(
- activeFile.id,
- route,
- 'runtime-1'
- )
- )
- expect(mocks.readRuntimeFileContent).not.toHaveBeenCalled()
- })
-
it('reports a sibling-owner collision instead of remaining in loading state', async () => {
const activeFile = createOpenFile({
id: '/work/repo-b/docs/readme.md',
diff --git a/src/renderer/src/components/terminal-pane/terminal-file-link-owner-resolution.ts b/src/renderer/src/components/terminal-pane/terminal-file-link-owner-resolution.ts
deleted file mode 100644
index a671413e44e..00000000000
--- a/src/renderer/src/components/terminal-pane/terminal-file-link-owner-resolution.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { statUserOpenedPath } from '@/lib/user-opened-local-path'
-import type { TerminalFileContext } from './terminal-file-path-mapping'
-import {
- CLIENT_LOCAL_FILE_TARGET,
- resolveHostWorkspaceFile,
- type HostWorkspaceFile
-} from './terminal-host-workspace-file'
-
-export type TerminalLinkFileOwner = {
- fileContext: TerminalFileContext
- worktreeId: string
- worktreePath: string
- /** The path to open: the host's spelling when the host resolved it. */
- filePath: string
- stat: { isDirectory: boolean; escapesWorktree: boolean }
- /** Another host workspace the link opens in. */
- hostWorkspaceFile: HostWorkspaceFile | null
- hostGrantId: string | null
- clientLocal: boolean
-}
-
-/**
- * Who owns a clicked link's file and how it may be read: a host grant, this computer, another
- * workspace on the host (opened there), or the source workspace. Throws when the host cannot answer.
- */
-export async function resolveTerminalLinkFileOwner(args: {
- fileContext: TerminalFileContext
- worktreeId: string
- worktreePath: string
- filePath: string
- terminalHandle?: string | null
-}): Promise {
- const { fileContext, filePath } = args
- const resolved: TerminalLinkFileOwner = {
- fileContext,
- worktreeId: args.worktreeId,
- worktreePath: args.worktreePath,
- filePath,
- stat: { isDirectory: false, escapesWorktree: false },
- hostWorkspaceFile: null,
- hostGrantId: null,
- clientLocal: false
- }
- const linkOwner = await resolveHostWorkspaceFile(fileContext, filePath, args.terminalHandle)
- if (linkOwner?.kind === 'grant') {
- return {
- ...resolved,
- hostGrantId: linkOwner.grantId,
- filePath: linkOwner.absolutePath,
- stat: { isDirectory: false, escapesWorktree: true }
- }
- }
- if (linkOwner?.kind === 'client') {
- return {
- ...resolved,
- clientLocal: true,
- fileContext: { ...fileContext, target: CLIENT_LOCAL_FILE_TARGET, connectionId: undefined },
- stat: { isDirectory: linkOwner.isDirectory, escapesWorktree: false }
- }
- }
- if (!linkOwner) {
- return { ...resolved, stat: await statUserOpenedPath(fileContext, filePath) }
- }
- // Why: the host already stat'ed the path inside the workspace that holds it; open it there.
- const { worktreeId, worktreePath } = linkOwner
- return {
- ...resolved,
- filePath: linkOwner.absolutePath,
- hostWorkspaceFile: linkOwner,
- worktreeId,
- worktreePath,
- fileContext: { ...fileContext, worktreeId, worktreePath },
- stat: { isDirectory: linkOwner.isDirectory, escapesWorktree: false }
- }
-}
diff --git a/src/renderer/src/components/terminal-pane/terminal-file-open-routing.ts b/src/renderer/src/components/terminal-pane/terminal-file-open-routing.ts
index 06c8aab0c7d..7dd43859a78 100644
--- a/src/renderer/src/components/terminal-pane/terminal-file-open-routing.ts
+++ b/src/renderer/src/components/terminal-pane/terminal-file-open-routing.ts
@@ -1,7 +1,7 @@
import { getWorkspaceFilePreviewPlan, openFileInBrowserTab } from '@/lib/file-preview'
import { downloadAndOpenRemoteTerminalFile } from './terminal-remote-file-download-open'
+import { findWorkspaceFileRoute } from '@/lib/runtime-workspace-file-route'
import { isPathInsideWorktree, toWorktreeRelativePath } from '@/lib/terminal-links'
-import type { RuntimeWorkspaceFileRoute } from '@/lib/runtime-workspace-file-route'
import { canClientOsOpenWorkspaceFile } from '@/lib/workspace-file-host-routing'
import { isMissingRuntimePathError } from '@/runtime/runtime-file-metadata-client'
import { useAppStore } from '@/store'
@@ -18,19 +18,17 @@ import {
getConnectionExecutionHostId,
toRuntimeExecutionHostId
} from '../../../../shared/execution-host'
+import { statUserOpenedPath } from '@/lib/user-opened-local-path'
+import {
+ CLIENT_LOCAL_FILE_TARGET,
+ resolveHostWorkspaceFile,
+ type HostWorkspaceFile
+} from './terminal-host-workspace-file'
+import { isFloatingWorkspaceId } from '../../../../shared/floating-workspace-worktree'
import { captureEditorOpenLinkIntent } from '@/store/slices/editor/tabs/editor-open-target-group'
import { reserveDetectedFilePathOpenTurn } from './terminal-file-open-turn'
import { openHtmlFileInBrowser } from './terminal-html-file-browser-open'
import { showOpenInGroupRefusedToast } from './terminal-file-open-in-group-toast'
-import {
- dismissFileInOtherWorkspaceHint,
- showFileInOtherWorkspaceHint
-} from './terminal-file-other-workspace-hint'
-import {
- otherWorkspaceViewFields,
- siblingWorkspaceLink
-} from './terminal-file-other-workspace-owner'
-import { resolveTerminalLinkFileOwner } from './terminal-file-link-owner-resolution'
import { isExplicitEditorScopeKept, openLinkedFileInEditor } from './terminal-file-link-editor-open'
export {
@@ -91,7 +89,6 @@ export function openDetectedFilePath(
terminalLinkWslDistro(deps.wslDistro, runtimeEnvironmentId)
)
const isCurrentOpen = reserveDetectedFilePathOpenTurn()
- dismissFileInOtherWorkspaceHint()
// Why: capture at activation; focus may move while the host resolves the path.
const linkIntent = captureEditorOpenLinkIntent(
useAppStore.getState(),
@@ -112,9 +109,13 @@ export function openDetectedFilePath(
}
void (async () => {
- const sourceContext = getTerminalFileContext(worktreeId, worktreePath, runtimeEnvironmentId)
- const sourceRuntimeOwnerId = runtimeTargetEnvironmentId(sourceContext.target)
- if (!sourceContext.sourceHostResolved) {
+ let statResult
+ let fileContext = getTerminalFileContext(worktreeId, worktreePath, runtimeEnvironmentId)
+ const sourceRuntimeOwnerId = runtimeTargetEnvironmentId(fileContext.target)
+ let hostWorkspaceFile: HostWorkspaceFile | null = null
+ let hostGrantId: string | null = null
+ let clientLocal = false
+ if (!fileContext.sourceHostResolved) {
// Why: with no owner the host is unknown — refuse rather than touch this machine's files.
deps.onOpenFailure?.({
verdict: 'unverifiable',
@@ -126,7 +127,7 @@ export function openDetectedFilePath(
const worktreeRootLink = resolveKnownWorktreeRootPathLink(
mappedFilePath,
useAppStore.getState(),
- sourceContext
+ fileContext
)
if (worktreeRootLink) {
if (refuseExplicitNonEditorRoute()) {
@@ -145,15 +146,33 @@ export function openDetectedFilePath(
}
}
- let owner
try {
- owner = await resolveTerminalLinkFileOwner({
- fileContext: sourceContext,
- worktreeId,
- worktreePath,
- filePath: mappedFilePath,
- terminalHandle: deps.terminalHandle
- })
+ const linkOwner = await resolveHostWorkspaceFile(
+ fileContext,
+ mappedFilePath,
+ deps.terminalHandle
+ )
+ if (linkOwner?.kind === 'grant') {
+ hostGrantId = linkOwner.grantId
+ mappedFilePath = linkOwner.absolutePath
+ statResult = { isDirectory: false, escapesWorktree: true }
+ } else if (linkOwner?.kind === 'client') {
+ clientLocal = true
+ fileContext = {
+ ...fileContext,
+ target: CLIENT_LOCAL_FILE_TARGET,
+ connectionId: undefined
+ }
+ statResult = { isDirectory: linkOwner.isDirectory, escapesWorktree: false }
+ } else if (linkOwner) {
+ hostWorkspaceFile = linkOwner
+ // Why: the host already stat'ed the path inside the workspace that holds it; open it there.
+ ;({ worktreeId, worktreePath, absolutePath: mappedFilePath } = hostWorkspaceFile)
+ fileContext = { ...fileContext, worktreeId, worktreePath }
+ statResult = { isDirectory: hostWorkspaceFile.isDirectory, escapesWorktree: false }
+ } else {
+ statResult = await statUserOpenedPath(fileContext, mappedFilePath)
+ }
} catch (error) {
if (isCurrentOpen() && deps.onOpenFailure) {
// Why: loss of contact with the host is not evidence the file is gone.
@@ -168,9 +187,6 @@ export function openDetectedFilePath(
if (!isCurrentOpen()) {
return
}
- const { fileContext, stat: statResult, hostWorkspaceFile, hostGrantId, clientLocal } = owner
- let otherWorkspaceOwner: RuntimeWorkspaceFileRoute | null = null
- ;({ worktreeId, worktreePath, filePath: mappedFilePath } = owner)
const canOpenWithSystemDefault = shouldOpenTerminalFileWithSystemDefault(fileContext)
// Why: a remote workspace's hint and rows promise Orca before the path resolves, and an
@@ -241,8 +257,10 @@ export function openDetectedFilePath(
}
}
+ const store = useAppStore.getState()
+ let targetWorktreeId = worktreeId
const runtimeOwnerId = runtimeTargetEnvironmentId(fileContext.target) ?? sourceRuntimeOwnerId
- const targetExecutionHostId =
+ let targetExecutionHostId =
hostWorkspaceFile?.executionHostId ??
(runtimeOwnerId
? toRuntimeExecutionHostId(runtimeOwnerId)
@@ -255,27 +273,35 @@ export function openDetectedFilePath(
if (maybeRelative !== null && maybeRelative.length > 0 && !statResult.escapesWorktree) {
relativePath = maybeRelative
}
- } else if (hostGrantId === null && !clientLocal && !hostWorkspaceFile) {
- const sibling = siblingWorkspaceLink(targetExecutionHostId, mappedFilePath, worktreeId)
- otherWorkspaceOwner = sibling.view
- if (sibling.openInOwner) {
- ;({ worktreeId, relativePath } = sibling.openInOwner)
+ } else if (
+ hostGrantId === null &&
+ !isFloatingWorkspaceId(worktreeId) &&
+ store.openFiles.some(
+ (openFile) => openFile.filePath === mappedFilePath && openFile.worktreeId !== worktreeId
+ )
+ ) {
+ // Why: early resolution is only needed to avoid an existing sibling-tab collision.
+ const siblingRoute = findWorkspaceFileRoute(store, targetExecutionHostId, mappedFilePath)
+ if (siblingRoute) {
+ targetWorktreeId = siblingRoute.worktreeId
+ targetExecutionHostId = siblingRoute.executionHostId
+ relativePath = siblingRoute.relativePath
}
}
if (
explicitGroupRequest &&
- !isExplicitEditorScopeKept(linkIntent, worktreeId, targetExecutionHostId, clientLocal)
+ !isExplicitEditorScopeKept(linkIntent, targetWorktreeId, targetExecutionHostId, clientLocal)
) {
// Why: refuse before activating the destination or touching editor state.
refuseExplicitNonEditorRoute()
return
}
- if (worktreeId) {
+ if (targetWorktreeId) {
// Why: the route may name a folder-workspace key, and the same worktree id can exist
// on several hosts — dispatch by workspace shape and keep the resolved host.
- activateAndRevealWorkspace(worktreeId, {
+ activateAndRevealWorkspace(targetWorktreeId, {
providesInitialSurface: true,
executionHostId: targetExecutionHostId
})
@@ -285,11 +311,10 @@ export function openDetectedFilePath(
{
filePath: mappedFilePath,
relativePath,
- worktreeId: worktreeId || '',
+ worktreeId: targetWorktreeId || '',
runtimeEnvironmentId: clientLocal ? null : runtimeEnvironmentId,
- // Why: a tab's owner must match its workspace's to be editable; this one is this computer's,
- // or another workspace's, which "Open there" edits under its own owner.
- ...(clientLocal ? { readOnly: true } : otherWorkspaceViewFields(otherWorkspaceOwner)),
+ // Why: a tab's owner must match its workspace's to be editable; this one is this computer's.
+ ...(clientLocal && targetWorktreeId === worktreeId ? { readOnly: true } : {}),
// Why: the grant is the only authority to read a host file outside its workspaces.
...(hostGrantId !== null ? { readOnly: true, terminalArtifactGrantId: hostGrantId } : {}),
// Why: absolute SSH paths outside the worktree otherwise look identical
@@ -306,14 +331,6 @@ export function openDetectedFilePath(
)
if (!opened) {
refuseExplicitNonEditorRoute()
- } else if (otherWorkspaceOwner && isCurrentOpen()) {
- showFileInOtherWorkspaceHint({
- fileId: opened.fileId,
- filePath: mappedFilePath,
- owner: otherWorkspaceOwner,
- runtimeEnvironmentId: runtimeOwnerId,
- position: { line, column }
- })
}
})()
}
diff --git a/src/renderer/src/components/terminal-pane/terminal-file-other-workspace-hint.test.ts b/src/renderer/src/components/terminal-pane/terminal-file-other-workspace-hint.test.ts
deleted file mode 100644
index 972b801e673..00000000000
--- a/src/renderer/src/components/terminal-pane/terminal-file-other-workspace-hint.test.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { describe, expect, it, vi } from 'vitest'
-
-const openLinkedFileInEditorMock = vi.hoisted(() => vi.fn())
-
-vi.mock('@/store', () => ({ useAppStore: { getState: () => ({}) } }))
-vi.mock('@/lib/worktree-activation', () => ({ activateAndRevealWorkspace: vi.fn() }))
-vi.mock('@/store/slices/editor/focus/editor-focus-reveal', () => ({
- cancelPendingEditorLineRevealFrames: vi.fn()
-}))
-vi.mock('./terminal-file-link-editor-open', () => ({
- openLinkedFileInEditor: openLinkedFileInEditorMock
-}))
-
-import { openFileInOwningWorkspace } from './terminal-file-other-workspace-hint'
-import { reserveDetectedFilePathOpenTurn } from './terminal-file-open-turn'
-
-describe('"Open there" from the other-workspace hint', () => {
- it('yields its line reveal to a later link click', () => {
- openFileInOwningWorkspace({
- fileId: 'view',
- filePath: '/b/report.md',
- owner: { worktreeId: 'wt-b', relativePath: 'report.md', executionHostId: 'local' },
- runtimeEnvironmentId: null,
- position: { line: 3, column: null }
- })
- const isCurrent: () => boolean = openLinkedFileInEditorMock.mock.calls[0][3]
- expect(isCurrent()).toBe(true)
-
- reserveDetectedFilePathOpenTurn()
-
- expect(isCurrent()).toBe(false)
- })
-})
diff --git a/src/renderer/src/components/terminal-pane/terminal-file-other-workspace-hint.ts b/src/renderer/src/components/terminal-pane/terminal-file-other-workspace-hint.ts
deleted file mode 100644
index f3e3d9cf2db..00000000000
--- a/src/renderer/src/components/terminal-pane/terminal-file-other-workspace-hint.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { toast } from 'sonner'
-import { translate } from '@/i18n/i18n'
-import { resolveWorktreeDisplayName } from '@/lib/worktree-default-display-name'
-import { activateAndRevealWorkspace } from '@/lib/worktree-activation'
-import type { RuntimeWorkspaceFileRoute } from '@/lib/runtime-workspace-file-route'
-import { useAppStore } from '@/store'
-import { selectKnownWorktreeById } from '@/store/selectors'
-import { getIndexedRepoMap } from '@/store/worktree-repo-index'
-import { openLinkedFileInEditor } from './terminal-file-link-editor-open'
-import { reserveDetectedFilePathOpenTurn } from './terminal-file-open-turn'
-
-// Why a fresh id per hint: sonner applies a dismissal later, so reusing one id would let the
-// previous hint's dismissal remove the hint that replaced it.
-let hintSequence = 0
-let shownHintId: string | null = null
-
-export type FileInOtherWorkspace = {
- /** The tab the link opened here. */
- fileId: string
- filePath: string
- owner: RuntimeWorkspaceFileRoute
- runtimeEnvironmentId: string | null | undefined
- position: { line: number | null; column: number | null }
-}
-
-/** Opens the file as its owning workspace's own file, with that workspace in front. */
-export function openFileInOwningWorkspace(file: FileInOtherWorkspace): void {
- const { owner } = file
- // Why: a later link click supersedes this open's line reveal, as it would any link open.
- const isCurrentOpen = reserveDetectedFilePathOpenTurn()
- activateAndRevealWorkspace(owner.worktreeId, {
- providesInitialSurface: true,
- executionHostId: owner.executionHostId
- })
- openLinkedFileInEditor(
- {
- filePath: file.filePath,
- relativePath: owner.relativePath,
- worktreeId: owner.worktreeId,
- runtimeEnvironmentId: file.runtimeEnvironmentId
- },
- null,
- file.position,
- isCurrentOpen
- )
-}
-
-/** A newer link click supersedes the hint, so its action never opens an older link's file. */
-export function dismissFileInOtherWorkspaceHint(): void {
- if (shownHintId) {
- toast.dismiss(shownHintId)
- shownHintId = null
- }
-}
-
-/** The link opened here; this names the workspace that holds the file and offers to go there. */
-export function showFileInOtherWorkspaceHint(file: FileInOtherWorkspace): void {
- const state = useAppStore.getState()
- // Why: a reused editable tab is not the read-only view this hint describes.
- if (state.openFiles.find((openFile) => openFile.id === file.fileId)?.readOnly !== true) {
- return
- }
- const worktree = selectKnownWorktreeById(state, file.owner.worktreeId, file.owner.executionHostId)
- const workspaceName = worktree ? resolveWorktreeDisplayName(worktree) : ''
- // Why: worktrees of different projects share names like "main"; the project tells them apart.
- const repoName =
- worktree && 'repoId' in worktree
- ? getIndexedRepoMap(state.repos).get(worktree.repoId)?.displayName
- : undefined
- const hintId = `terminal-file-other-workspace-${++hintSequence}`
- shownHintId = hintId
- const hidden = (): void => {
- if (shownHintId === hintId) {
- shownHintId = null
- }
- }
- toast(
- workspaceName
- ? translate(
- 'auto.components.terminal.pane.TerminalLinkActionPopover.fileInOtherWorkspace',
- 'This file is in {{value0}}',
- {
- value0:
- repoName && repoName !== workspaceName
- ? `${repoName} · ${workspaceName}`
- : workspaceName
- }
- )
- : translate(
- 'auto.components.terminal.pane.TerminalLinkActionPopover.fileInAnotherWorkspace',
- 'This file is in another workspace'
- ),
- {
- id: hintId,
- duration: 8000,
- description: translate(
- 'auto.components.terminal.pane.TerminalLinkActionPopover.openedHereReadOnly',
- 'Opened here as read-only'
- ),
- action: {
- label: translate(
- 'auto.components.terminal.pane.TerminalLinkActionPopover.openInOwningWorkspace',
- 'Open there'
- ),
- onClick: () => openFileInOwningWorkspace(file)
- },
- onDismiss: hidden,
- onAutoClose: hidden
- }
- )
-}
diff --git a/src/renderer/src/components/terminal-pane/terminal-file-other-workspace-owner.ts b/src/renderer/src/components/terminal-pane/terminal-file-other-workspace-owner.ts
deleted file mode 100644
index 73103741f70..00000000000
--- a/src/renderer/src/components/terminal-pane/terminal-file-other-workspace-owner.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import {
- findWorkspaceFileRoute,
- type RuntimeWorkspaceFileRoute
-} from '@/lib/runtime-workspace-file-route'
-import { useAppStore } from '@/store'
-import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../../../../shared/execution-host'
-import { isFloatingWorkspaceId } from '../../../../shared/floating-workspace-worktree'
-
-/**
- * A link to a file another workspace on the same host holds. On this computer (local, WSL and
- * folder workspaces) it is viewed here, read-only (#32158), since this computer reads the absolute
- * path directly. SSH and server hosts read files only by workspace-relative path, so their file
- * still opens in the owning workspace, which also reuses a tab already open there.
- */
-export function siblingWorkspaceLink(
- executionHostId: ExecutionHostId,
- absolutePath: string,
- sourceWorktreeId: string
-): { view: RuntimeWorkspaceFileRoute | null; openInOwner: RuntimeWorkspaceFileRoute | null } {
- // Why: floating-workspace files stay in their panel even when a project also contains the path.
- if (isFloatingWorkspaceId(sourceWorktreeId)) {
- return { view: null, openInOwner: null }
- }
- const found = findWorkspaceFileRoute(useAppStore.getState(), executionHostId, absolutePath)
- const owner = found && found.worktreeId !== sourceWorktreeId ? found : null
- return executionHostId === LOCAL_EXECUTION_HOST_ID
- ? { view: owner, openInOwner: null }
- : { view: null, openInOwner: owner }
-}
-
-/** A view here of another local workspace's file: this computer's file, never saved here. */
-export function otherWorkspaceViewFields(owner: RuntimeWorkspaceFileRoute | null): {
- readOnly?: true
- runtimeEnvironmentId?: null
-} {
- return owner ? { readOnly: true, runtimeEnvironmentId: null } : {}
-}
diff --git a/src/renderer/src/components/terminal-pane/terminal-link-file-open-routing.test.ts b/src/renderer/src/components/terminal-pane/terminal-link-file-open-routing.test.ts
index 1daf4249f6b..5df3efdc217 100644
--- a/src/renderer/src/components/terminal-pane/terminal-link-file-open-routing.test.ts
+++ b/src/renderer/src/components/terminal-pane/terminal-link-file-open-routing.test.ts
@@ -1,6 +1,5 @@
-import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { describe, expect, it, vi } from 'vitest'
import { activateAndRevealWorkspace, activateAndRevealWorktree } from '@/lib/worktree-activation'
-import { getConnectionId } from '@/lib/connection-context'
import { openDetectedFilePath } from './terminal-link-handlers'
import { createTerminalLinkTestDoubles } from './terminal-link-handlers-test-fixtures'
import {
@@ -12,7 +11,6 @@ import {
} from './terminal-link-handlers-test-harness'
const findWorkspaceFileRouteMock = vi.hoisted(() => vi.fn())
-const showHintMock = vi.hoisted(() => vi.fn())
const doubles = createTerminalLinkTestDoubles()
const {
storeState,
@@ -48,17 +46,8 @@ vi.mock('@/lib/runtime-workspace-file-route', () => ({
findWorkspaceFileRoute: findWorkspaceFileRouteMock
}))
-vi.mock('./terminal-file-other-workspace-hint', () => ({
- dismissFileInOtherWorkspaceHint: vi.fn(),
- showFileInOtherWorkspaceHint: showHintMock
-}))
-
installTerminalLinkTestEnvironment(doubles)
-beforeEach(() => {
- findWorkspaceFileRouteMock.mockReset()
-})
-
describe('handleOscLink', () => {
it('opens local .html file paths in Orca browser tabs when links route to Orca', async () => {
setPlatform('Macintosh')
@@ -153,83 +142,50 @@ describe('handleOscLink', () => {
})
})
- it('opens a sibling folder-workspace file read-only here and names its workspace', async () => {
+ it('opens a sibling folder-workspace path under its owning host and workspace', async () => {
const filePath = '/sibling/docs/SKILL.md'
- const owner = {
+ storeState.openFiles = [{ filePath, worktreeId: 'folder:notes' }]
+ findWorkspaceFileRouteMock.mockReturnValueOnce({
worktreeId: 'folder:notes',
relativePath: 'docs/SKILL.md',
executionHostId: 'local'
- }
- findWorkspaceFileRouteMock.mockReturnValueOnce(owner)
+ })
+ openFileMock.mockImplementationOnce(() => {
+ storeState.activeFileIdByWorktree['folder:notes'] = 'owned-skill'
+ })
openDetectedFilePath(filePath, 12, null, deps)
await flushAsyncWork()
await flushDoubleRaf()
- expect(activateAndRevealWorkspace).toHaveBeenCalledTimes(1)
- expect(activateAndRevealWorkspace).toHaveBeenCalledWith('wt-1', {
+ expect(activateAndRevealWorkspace).toHaveBeenCalledWith('folder:notes', {
providesInitialSurface: true,
executionHostId: 'local'
})
expect(openFileMock).toHaveBeenCalledWith(
expect.objectContaining({
filePath,
- relativePath: filePath,
- worktreeId: 'wt-1',
- runtimeEnvironmentId: null,
- readOnly: true
+ relativePath: 'docs/SKILL.md',
+ worktreeId: 'folder:notes'
}),
{ forceContentReload: true }
)
- expect(showHintMock).toHaveBeenCalledWith({
- fileId: filePath,
- filePath,
- owner,
- runtimeEnvironmentId: null,
- position: { line: 12, column: null }
- })
- })
-
- it('opens a direct SSH link to a sibling workspace in that workspace, as before', async () => {
- vi.mocked(getConnectionId).mockReturnValue('ssh-1')
- findWorkspaceFileRouteMock.mockReturnValue({
- worktreeId: 'wt-2',
- relativePath: 'x.ts',
- executionHostId: 'ssh:ssh-1'
- })
-
- openDetectedFilePath('/other/x.ts', null, null, deps)
- await flushAsyncWork()
-
- // Why: a read-only absolute tab on a direct SSH connection would read as this computer's file.
- expect(activateAndRevealWorkspace).toHaveBeenCalledWith('wt-2', {
- providesInitialSurface: true,
- executionHostId: 'ssh:ssh-1'
- })
- expect(openFileMock).toHaveBeenCalledWith(
- expect.objectContaining({
- filePath: '/other/x.ts',
- relativePath: 'x.ts',
- worktreeId: 'wt-2'
- }),
- { forceContentReload: true }
+ expect(setPendingEditorRevealMock).toHaveBeenLastCalledWith(
+ expect.objectContaining({ fileId: 'owned-skill' })
)
- expect(openFileMock.mock.calls[0][0]).not.toHaveProperty('readOnly')
- expect(showHintMock).not.toHaveBeenCalled()
})
- it('opens a file outside every workspace here, editable and without a hint', async () => {
+ it('leaves ordinary external ownership lookup to the editor loader', async () => {
const filePath = '/external/docs/readme.md'
openDetectedFilePath(filePath, null, null, deps)
await flushAsyncWork()
+ expect(findWorkspaceFileRouteMock).not.toHaveBeenCalled()
expect(openFileMock).toHaveBeenCalledWith(
expect.objectContaining({ filePath, worktreeId: 'wt-1' }),
{ forceContentReload: true }
)
- expect(openFileMock.mock.calls[0][0]).not.toHaveProperty('readOnly')
- expect(showHintMock).not.toHaveBeenCalled()
})
it('preserves explicit column for Orca opens from :line:column links', async () => {
diff --git a/src/renderer/src/components/terminal-pane/terminal-link-handlers-test-fixtures.ts b/src/renderer/src/components/terminal-pane/terminal-link-handlers-test-fixtures.ts
index adfe35259bc..7c5901bf036 100644
--- a/src/renderer/src/components/terminal-pane/terminal-link-handlers-test-fixtures.ts
+++ b/src/renderer/src/components/terminal-pane/terminal-link-handlers-test-fixtures.ts
@@ -8,13 +8,6 @@ export type TerminalLinkStoreSettings = {
activeRuntimeEnvironmentId?: string | null
}
-export type TerminalLinkOpenFile = {
- id?: string
- filePath: string
- worktreeId: string
- readOnly?: boolean
-}
-
export type TerminalLinkStoreState = {
settings: TerminalLinkStoreSettings | undefined
setActiveWorktree: Mock
@@ -24,7 +17,7 @@ export type TerminalLinkStoreState = {
setPendingEditorReveal: Mock
setMarkdownViewMode: Mock
activeFileIdByWorktree: Record
- openFiles: TerminalLinkOpenFile[]
+ openFiles: { filePath: string; worktreeId: string }[]
groupsByWorktree: Record
unifiedTabsByWorktree: Record<
string,
@@ -42,7 +35,6 @@ export type TerminalLinkStoreState = {
}[]
>
folderWorkspaces: []
- repos?: { id: string; displayName: string }[]
}
export type TerminalLinkTestDoubles = {
diff --git a/src/renderer/src/components/terminal-pane/terminal-link-host-workspace-file.test.ts b/src/renderer/src/components/terminal-pane/terminal-link-host-workspace-file.test.ts
index ce8a9d7d30c..c2ce28b467e 100644
--- a/src/renderer/src/components/terminal-pane/terminal-link-host-workspace-file.test.ts
+++ b/src/renderer/src/components/terminal-pane/terminal-link-host-workspace-file.test.ts
@@ -1,6 +1,5 @@
import { describe, expect, it, vi } from 'vitest'
import { openDetectedFilePath } from './terminal-link-handlers'
-import type { RuntimeWorkspaceFileRoute } from '@/lib/runtime-workspace-file-route'
import { activateAndRevealWorkspace } from '@/lib/worktree-activation'
import { downloadAndOpenRemoteTerminalFile } from './terminal-remote-file-download-open'
import { createTerminalLinkTestDoubles } from './terminal-link-handlers-test-fixtures'
@@ -9,10 +8,6 @@ import {
installTerminalLinkTestEnvironment
} from './terminal-link-handlers-test-harness'
-const findWorkspaceFileRouteMock = vi.hoisted(() =>
- vi.fn<() => RuntimeWorkspaceFileRoute | null>(() => null)
-)
-const showHintMock = vi.hoisted(() => vi.fn())
const doubles = createTerminalLinkTestDoubles()
const { storeState, statMock, openFileMock, openFilePathMock, runtimeEnvironmentCallMock } = doubles
@@ -35,16 +30,6 @@ vi.mock('./terminal-remote-file-download-open', () => ({
downloadAndOpenRemoteTerminalFile: vi.fn()
}))
-// Why: this client's catalog lacks the host's other workspaces unless a spec says otherwise.
-vi.mock('@/lib/runtime-workspace-file-route', () => ({
- findWorkspaceFileRoute: findWorkspaceFileRouteMock
-}))
-
-vi.mock('./terminal-file-other-workspace-hint', () => ({
- dismissFileInOtherWorkspaceHint: vi.fn(),
- showFileInOtherWorkspaceHint: showHintMock
-}))
-
installTerminalLinkTestEnvironment(doubles)
const featureWorktree = {
@@ -98,36 +83,6 @@ describe('a paired-server link outside its own workspace', () => {
)
})
- it('opens a file in a workspace this client knows there, with no read-only view or hint', async () => {
- findWorkspaceFileRouteMock.mockReturnValue({
- worktreeId: 'wt-main',
- relativePath: 'src/index.ts',
- executionHostId: 'runtime:env-1'
- })
- hostAnswers({
- worktree: 'wt-main',
- relativePath: 'src/index.ts',
- absolutePath: '/repo/src/index.ts',
- exists: true,
- isDirectory: false
- })
-
- openDetectedFilePath('/repo/src/index.ts', 3, null, featureWorktree)
- await flushDoubleRaf()
-
- // Why: a server reads only by workspace-relative path, so its file opens in its own workspace.
- expect(activateAndRevealWorkspace).toHaveBeenCalledWith('wt-main', {
- providesInitialSurface: true,
- executionHostId: 'runtime:env-1'
- })
- expect(openFileMock).toHaveBeenCalledWith(
- expect.objectContaining({ relativePath: 'src/index.ts', worktreeId: 'wt-main' }),
- { forceContentReload: true }
- )
- expect(openFileMock.mock.calls[0][0]).not.toHaveProperty('readOnly')
- expect(showHintMock).not.toHaveBeenCalled()
- })
-
it("opens the host's spelling of the path so the tab stays a workspace file", async () => {
hostAnswers({
worktree: 'wt-main',
diff --git a/src/renderer/src/components/terminal-pane/terminal-link-open-beside-source.test.ts b/src/renderer/src/components/terminal-pane/terminal-link-open-beside-source.test.ts
index bc6d0ff54dc..1af0d9ab0d6 100644
--- a/src/renderer/src/components/terminal-pane/terminal-link-open-beside-source.test.ts
+++ b/src/renderer/src/components/terminal-pane/terminal-link-open-beside-source.test.ts
@@ -32,11 +32,6 @@ vi.mock('@/store', () => ({
vi.mock('sonner', () => ({ toast: { error: toastErrorMock } }))
-vi.mock('./terminal-file-other-workspace-hint', () => ({
- dismissFileInOtherWorkspaceHint: vi.fn(),
- showFileInOtherWorkspaceHint: vi.fn()
-}))
-
vi.mock('@/lib/language-detect', () => ({
detectLanguage: () => 'plaintext'
}))
@@ -169,9 +164,10 @@ describe('terminal file links carry their source tab', () => {
expect(toastErrorMock).toHaveBeenCalledTimes(1)
})
- it('keeps an explicit request for another workspace file in the source group', async () => {
+ it('refuses an explicit request routed to another workspace before activating it', async () => {
setPlatform('Macintosh')
seedSourceTab()
+ storeState.openFiles = [{ filePath: '/other/x.ts', worktreeId: 'wt-2' }]
findWorkspaceFileRouteMock.mockReturnValueOnce({
worktreeId: 'wt-2',
executionHostId: 'local',
@@ -181,14 +177,9 @@ describe('terminal file links carry their source tab', () => {
openDetectedFilePath('/other/x.ts', null, null, { ...sourceDeps, openInSourceGroup: true })
await flushAsyncWork()
- expect(activateAndRevealWorkspace).not.toHaveBeenCalledWith('wt-2', expect.anything())
- expect(openFileForLinkMock).toHaveBeenCalledWith(
- expect.objectContaining({ filePath: '/other/x.ts', worktreeId: 'wt-1', readOnly: true }),
- expect.anything(),
- expect.objectContaining({ targetGroupId: 'group-a' }),
- expect.any(Function)
- )
- expect(toastErrorMock).not.toHaveBeenCalled()
+ expect(activateAndRevealWorkspace).not.toHaveBeenCalled()
+ expect(openFileForLinkMock).not.toHaveBeenCalled()
+ expect(toastErrorMock).toHaveBeenCalledTimes(1)
})
it('keeps automatic HTML opens on their existing browser route', async () => {
diff --git a/src/renderer/src/components/terminal-pane/terminal-link-other-workspace-hint.sonner.test.tsx b/src/renderer/src/components/terminal-pane/terminal-link-other-workspace-hint.sonner.test.tsx
deleted file mode 100644
index dd059f19c1a..00000000000
--- a/src/renderer/src/components/terminal-pane/terminal-link-other-workspace-hint.sonner.test.tsx
+++ /dev/null
@@ -1,197 +0,0 @@
-// @vitest-environment happy-dom
-import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
-import { Toaster } from 'sonner'
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { activateAndRevealWorkspace } from '@/lib/worktree-activation'
-import { openDetectedFilePath } from './terminal-link-handlers'
-import { createTerminalLinkTestDoubles } from './terminal-link-handlers-test-fixtures'
-
-const findWorkspaceFileRouteMock = vi.hoisted(() => vi.fn())
-const doubles = createTerminalLinkTestDoubles()
-const { storeState, openFileMock, statMock } = doubles
-
-vi.mock('@/i18n/i18n', () => ({
- translate: (_key: string, fallback: string, values?: Record) =>
- fallback.replace(/\{\{(\w+)\}\}/g, (_match, name: string) => values?.[name] ?? '')
-}))
-
-vi.mock('@/store', () => ({ useAppStore: { getState: () => storeState } }))
-
-vi.mock('@/store/selectors', () => ({
- selectKnownWorktreeById: (_state: unknown, worktreeId: string) =>
- worktreeId === 'wt-b'
- ? { repoId: 'repo-b', displayName: 'feature-b', branch: '', path: '/b' }
- : null
-}))
-
-vi.mock('@/lib/language-detect', () => ({ detectLanguage: () => 'plaintext' }))
-
-vi.mock('@/lib/worktree-activation', () => ({
- activateAndRevealWorkspace: vi.fn(),
- activateAndRevealWorktree: vi.fn()
-}))
-
-vi.mock('@/lib/connection-context', () => ({ getConnectionId: vi.fn(() => null) }))
-
-vi.mock('@/lib/runtime-workspace-file-route', () => ({
- findWorkspaceFileRoute: findWorkspaceFileRouteMock
-}))
-
-const sourceDeps = { worktreeId: 'wt-a', worktreePath: '/a' }
-
-async function settle(): Promise {
- for (let i = 0; i < 5; i += 1) {
- await act(async () => {
- await vi.advanceTimersByTimeAsync(1)
- })
- }
-}
-
-beforeEach(() => {
- vi.useFakeTimers()
- vi.clearAllMocks()
- storeState.openFiles = []
- storeState.repos = [{ id: 'repo-b', displayName: 'project-b' }]
- // Records each open as the store does, so the hint sees whether the tab is read-only.
- openFileMock.mockImplementation(
- (file: { filePath: string; worktreeId: string; readOnly?: boolean }) => {
- storeState.openFiles.push({ id: file.filePath, ...file })
- }
- )
- findWorkspaceFileRouteMock.mockReturnValue(null)
- statMock.mockResolvedValue({ isDirectory: false })
- Object.defineProperty(window, 'api', {
- configurable: true,
- value: {
- shell: { openFilePath: vi.fn().mockResolvedValue(true) },
- fs: { stat: statMock, pathExists: vi.fn().mockResolvedValue(true) }
- }
- })
- render()
-})
-
-afterEach(() => {
- cleanup()
- vi.useRealTimers()
-})
-
-describe('a terminal file link clicked in workspace A', () => {
- it('opens a file inside A in A, with no hint', async () => {
- openDetectedFilePath('/a/src/main.ts', null, null, sourceDeps)
- await settle()
-
- expect(activateAndRevealWorkspace).toHaveBeenCalledTimes(1)
- expect(activateAndRevealWorkspace).toHaveBeenCalledWith('wt-a', expect.anything())
- expect(openFileMock).toHaveBeenCalledWith(
- expect.objectContaining({ relativePath: 'src/main.ts', worktreeId: 'wt-a' }),
- { forceContentReload: true }
- )
- expect(openFileMock.mock.calls[0][0]).not.toHaveProperty('readOnly')
- expect(screen.queryByText(/This file is in/)).toBeNull()
- })
-
- it('opens a file workspace B holds in A, and the hint opens it in B', async () => {
- findWorkspaceFileRouteMock.mockReturnValue({
- worktreeId: 'wt-b',
- relativePath: 'report.md',
- executionHostId: 'local'
- })
-
- openDetectedFilePath('/b/report.md', 7, null, sourceDeps)
- await settle()
-
- expect(activateAndRevealWorkspace).toHaveBeenCalledTimes(1)
- expect(activateAndRevealWorkspace).toHaveBeenCalledWith('wt-a', expect.anything())
- expect(openFileMock).toHaveBeenCalledWith(
- expect.objectContaining({
- filePath: '/b/report.md',
- relativePath: '/b/report.md',
- worktreeId: 'wt-a',
- readOnly: true
- }),
- { forceContentReload: true }
- )
- // Why the project: worktrees of different projects share names like "main".
- expect(screen.getByText('This file is in project-b · feature-b')).not.toBeNull()
- expect(screen.getByText('Opened here as read-only')).not.toBeNull()
-
- fireEvent.click(screen.getByRole('button', { name: 'Open there' }))
- await settle()
-
- expect(activateAndRevealWorkspace).toHaveBeenLastCalledWith('wt-b', {
- providesInitialSurface: true,
- executionHostId: 'local'
- })
- expect(openFileMock).toHaveBeenLastCalledWith(
- expect.objectContaining({
- filePath: '/b/report.md',
- relativePath: 'report.md',
- worktreeId: 'wt-b',
- runtimeEnvironmentId: null
- }),
- { forceContentReload: true }
- )
- expect(openFileMock.mock.lastCall?.[0]).not.toHaveProperty('readOnly')
- })
-
- it('keeps the hint of a second link clicked while the first hint shows', async () => {
- findWorkspaceFileRouteMock.mockReturnValue({
- worktreeId: 'wt-b',
- relativePath: 'report.md',
- executionHostId: 'local'
- })
-
- openDetectedFilePath('/b/report.md', null, null, sourceDeps)
- await settle()
- findWorkspaceFileRouteMock.mockReturnValue({
- worktreeId: 'wt-b',
- relativePath: 'notes.md',
- executionHostId: 'local'
- })
- openDetectedFilePath('/b/notes.md', 11, null, sourceDeps)
- await settle()
- await act(async () => {
- await vi.advanceTimersToNextFrame()
- })
- await act(async () => {
- await vi.advanceTimersByTimeAsync(300)
- })
- await act(async () => {
- await vi.advanceTimersByTimeAsync(300)
- })
-
- expect(screen.getByText('This file is in project-b · feature-b')).not.toBeNull()
- fireEvent.click(screen.getByRole('button', { name: 'Open there' }))
- await settle()
-
- expect(openFileMock).toHaveBeenLastCalledWith(
- expect.objectContaining({
- filePath: '/b/notes.md',
- relativePath: 'notes.md',
- worktreeId: 'wt-b'
- }),
- { forceContentReload: true }
- )
- })
-
- it('shows no read-only hint when the link reused an editable tab here', async () => {
- findWorkspaceFileRouteMock.mockReturnValue({
- worktreeId: 'wt-b',
- relativePath: 'report.md',
- executionHostId: 'local'
- })
- openFileMock.mockImplementationOnce(() => {
- storeState.openFiles.push({
- id: '/b/report.md',
- filePath: '/b/report.md',
- worktreeId: 'wt-a'
- })
- })
-
- openDetectedFilePath('/b/report.md', null, null, sourceDeps)
- await settle()
-
- expect(openFileMock).toHaveBeenCalledTimes(1)
- expect(screen.queryByText(/This file is in/)).toBeNull()
- })
-})
diff --git a/src/renderer/src/hooks/editor-external-watch-background-trim.test.ts b/src/renderer/src/hooks/editor-external-watch-background-trim.test.ts
index 00bb2373cde..5aeb98d0715 100644
--- a/src/renderer/src/hooks/editor-external-watch-background-trim.test.ts
+++ b/src/renderer/src/hooks/editor-external-watch-background-trim.test.ts
@@ -15,18 +15,7 @@ import {
TEST_REPO
} from '@/store/slices/worktrees-slice-test-fixtures'
-// Why: the catalog says wt1 holds /r/wt1/a.ts once it knows the owner.
-const catalog = vi.hoisted(() => ({ knowsOwner: true }))
-// Why `repos` tracks the catalog: owner lookups are cached until a route input changes.
-vi.mock('@/store', () => ({
- useAppStore: { getState: vi.fn(() => ({ repos: catalog.knowsOwner ? 'known' : 'unknown' })) }
-}))
-vi.mock('@/lib/runtime-workspace-file-route', () => ({
- findWorkspaceFileRoute: (_state: unknown, _hostId: string, absolutePath: string) =>
- catalog.knowsOwner && absolutePath === '/r/wt1/a.ts'
- ? { worktreeId: 'wt1', relativePath: 'a.ts', executionHostId: 'local' }
- : null
-}))
+vi.mock('@/store', () => ({ useAppStore: { getState: vi.fn() } }))
type Repo = EditorExternalWatchTargetState['repos'][number]
type Worktree = EditorExternalWatchTargetState['worktreesByRepo'][string][number]
@@ -120,38 +109,6 @@ describe('background workspace watch trim', () => {
expect(watchedIds(state)).toEqual(['wt0', 'wt2', 'wt3', 'wt4'])
})
- it("watches a read-only view of another workspace's file with that workspace", () => {
- const state = makeState({
- worktrees,
- openFiles: [
- tab('wt0', {
- filePath: '/r/wt1/a.ts',
- relativePath: '/r/wt1/a.ts',
- readOnly: true
- })
- ],
- activeWorktreeId: 'wt0'
- })
- expect(watchedIds(state)).toEqual(['wt1'])
- })
-
- it("re-targets a view's watch once the catalog learns its owner", () => {
- const state = makeState({
- worktrees,
- openFiles: [
- tab('wt0', { filePath: '/r/wt1/a.ts', relativePath: '/r/wt1/a.ts', readOnly: true })
- ],
- activeWorktreeId: 'wt0'
- })
- catalog.knowsOwner = false
- try {
- expect(watchedIds(state)).toEqual(['wt0'])
- } finally {
- catalog.knowsOwner = true
- }
- expect(watchedIds(state)).toEqual(['wt1'])
- })
-
it('does not let a deleted workspace hold a warm slot', () => {
const state = makeState({
worktrees,
diff --git a/src/renderer/src/hooks/editor-external-watch-disk-verification.ts b/src/renderer/src/hooks/editor-external-watch-disk-verification.ts
index 2476007f54b..4904736fc6d 100644
--- a/src/renderer/src/hooks/editor-external-watch-disk-verification.ts
+++ b/src/renderer/src/hooks/editor-external-watch-disk-verification.ts
@@ -318,7 +318,6 @@ export function scheduleSelfWriteAwareEditorExternalReload(
}
function hasCleanExternalReloadTarget(notification: EditorExternalWatchNotification): boolean {
- const state = useAppStore.getState()
- const matching = getOpenFilesForExternalFileChange(state.openFiles, notification, state)
+ const matching = getOpenFilesForExternalFileChange(useAppStore.getState().openFiles, notification)
return matching.some((file) => !file.isDirty)
}
diff --git a/src/renderer/src/hooks/editor-external-watch-event-reconciliation.ts b/src/renderer/src/hooks/editor-external-watch-event-reconciliation.ts
index 571bc4240d0..721e64d7ba9 100644
--- a/src/renderer/src/hooks/editor-external-watch-event-reconciliation.ts
+++ b/src/renderer/src/hooks/editor-external-watch-event-reconciliation.ts
@@ -1,7 +1,5 @@
import { useAppStore } from '@/store'
-import { openFileOwnerWorktreeId } from '@/lib/open-file-owner-scope'
import type { OpenFile } from '@/store/slices/editor'
-import type { AppState } from '@/store/types'
import { basename } from '@/lib/path'
import { canAutoSaveOpenFile } from '@/components/editor/editor-autosave'
import { indexEditorExternalWatchBatchPaths } from '@/components/editor/editor-external-watch-path-index'
@@ -98,10 +96,8 @@ export function buildEditorExternalWatchEventHandler(
const reconcileTarget = (payload: FsChangedPayload, target: EditorExternalWatchTarget): void => {
// Why: one batch index keeps local WSL alias normalization out of event×tab loops.
- const stateAtStart = useAppStore.getState()
- const openFilesAtStart = stateAtStart.openFiles
+ const openFilesAtStart = useAppStore.getState().openFiles
const batchPaths = indexEditorExternalWatchBatchPaths(payload, openFilesAtStart, {
- catalog: stateAtStart,
worktreeId: target.worktreeId,
worktreePath: target.worktreePath,
runtimeEnvironmentId: target.runtimeEnvironmentId,
@@ -129,7 +125,7 @@ export function buildEditorExternalWatchEventHandler(
const deletedOpenEditorIds = deletedOpenEditors.map(({ file }) => file.id)
const hasPairedCreate =
deletedOpenEditorIds.length > 0 &&
- hasRenameCorrelatedCreate(payload, target.worktreeId, deletedOpenEditorIds, stateAtStart)
+ hasRenameCorrelatedCreate(payload, target.worktreeId, deletedOpenEditorIds, openFilesAtStart)
if (deletedOpenEditorIds.length > 0) {
if (hasPairedCreate) {
const setExternalMutation = useAppStore.getState().setExternalMutation
@@ -169,7 +165,7 @@ export function buildEditorExternalWatchEventHandler(
const state = useAppStore.getState()
for (const file of state.openFiles) {
if (
- openFileOwnerWorktreeId(file, state) === target.worktreeId &&
+ file.worktreeId === target.worktreeId &&
getOpenFileRuntimeOwner(file) === target.runtimeEnvironmentId &&
(file.mode === 'edit' || file.mode === 'markdown-preview') &&
(file.externalMutation === 'deleted' || file.externalMutation === 'renamed') &&
@@ -266,16 +262,16 @@ function hasRenameCorrelatedCreate(
payload: FsChangedPayload,
worktreeId: string,
deletedOpenEditorIds: string[],
- state: AppState
+ openFiles: OpenFile[]
): boolean {
if (deletedOpenEditorIds.length === 0) {
return false
}
const deletedIdSet = new Set(deletedOpenEditorIds)
const deletedBasenames = new Set()
- for (const file of state.openFiles) {
+ for (const file of openFiles) {
if (
- openFileOwnerWorktreeId(file, state) !== worktreeId ||
+ file.worktreeId !== worktreeId ||
(file.mode !== 'edit' && file.mode !== 'markdown-preview') ||
!deletedIdSet.has(file.id)
) {
diff --git a/src/renderer/src/hooks/editor-external-watch-other-workspace-view.test.ts b/src/renderer/src/hooks/editor-external-watch-other-workspace-view.test.ts
deleted file mode 100644
index e18efacab52..00000000000
--- a/src/renderer/src/hooks/editor-external-watch-other-workspace-view.test.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import type { OpenFile } from '@/store/slices/editor'
-import { useAppStore } from '@/store'
-import { createTestStore } from '@/store/slices/store-test-helpers'
-import { buildEditorExternalWatchEventHandler } from './editor-external-watch-event-reconciliation'
-import { indexEditorExternalWatchBatchPaths } from '@/components/editor/editor-external-watch-path-index'
-
-vi.mock('@/store', () => ({ useAppStore: { getState: vi.fn() } }))
-
-// Why: the catalog says workspace wt-b (root /b) holds /b/report.md.
-vi.mock('@/lib/runtime-workspace-file-route', () => ({
- findWorkspaceFileRoute: (_state: unknown, _hostId: string, absolutePath: string) =>
- absolutePath === '/b/report.md'
- ? { worktreeId: 'wt-b', relativePath: 'report.md', executionHostId: 'local' }
- : null
-}))
-
-const setExternalMutation = vi.fn()
-const ownerTarget = {
- worktreeId: 'wt-b',
- worktreePath: '/b',
- connectionId: undefined,
- runtimeEnvironmentId: null
-}
-
-/** A read-only view in workspace A of B's file, as a session restore draws it: nothing extra stored. */
-function restoredView(extra: Partial = {}): OpenFile {
- return {
- id: 'view',
- worktreeId: 'wt-a',
- filePath: '/b/report.md',
- relativePath: '/b/report.md',
- language: 'markdown',
- mode: 'edit',
- isDirty: false,
- readOnly: true,
- ...extra
- }
-}
-
-function useOpenFiles(openFiles: OpenFile[]): void {
- vi.mocked(useAppStore.getState).mockReturnValue({
- ...createTestStore().getState(),
- openFiles,
- setExternalMutation
- })
-}
-
-beforeEach(() => {
- vi.clearAllMocks()
- vi.useFakeTimers()
-})
-
-afterEach(() => {
- vi.useRealTimers()
-})
-
-describe("a read-only view of another workspace's file", () => {
- it('matches the owning workspace watch after a restore', () => {
- const view = restoredView()
- useOpenFiles([view])
- const index = indexEditorExternalWatchBatchPaths(
- { worktreePath: '/b', events: [{ kind: 'update', absolutePath: '/b/report.md' }] },
- [view],
- { ...ownerTarget, catalog: useAppStore.getState() }
- )
-
- expect(index.matchingOpenFiles(index.changes[0])).toEqual([view])
- })
-
- it('clears its deleted mark when the owner recreates the file', () => {
- useOpenFiles([restoredView({ externalMutation: 'deleted' })])
- const { handleFsChanged, dispose } = buildEditorExternalWatchEventHandler((path) =>
- path === '/b' ? ownerTarget : undefined
- )
-
- handleFsChanged({
- worktreePath: '/b',
- events: [{ kind: 'create', absolutePath: '/b/report.md' }]
- })
-
- expect(setExternalMutation).toHaveBeenCalledWith('view', null)
- dispose()
- })
-
- it('is marked deleted when the owner deletes the file', () => {
- useOpenFiles([restoredView()])
- const { handleFsChanged, dispose } = buildEditorExternalWatchEventHandler((path) =>
- path === '/b' ? ownerTarget : undefined
- )
-
- handleFsChanged({
- worktreePath: '/b',
- events: [{ kind: 'delete', absolutePath: '/b/report.md' }]
- })
- vi.runAllTimers()
-
- expect(setExternalMutation).toHaveBeenCalledWith('view', 'deleted')
- dispose()
- })
-})
diff --git a/src/renderer/src/hooks/editor-external-watch-overflow.test.ts b/src/renderer/src/hooks/editor-external-watch-overflow.test.ts
index 193a704e48c..12e58286510 100644
--- a/src/renderer/src/hooks/editor-external-watch-overflow.test.ts
+++ b/src/renderer/src/hooks/editor-external-watch-overflow.test.ts
@@ -18,13 +18,6 @@ vi.mock('@/runtime/runtime-path-existence-batch', () => ({
vi.mock('@/lib/file-owner-runtime-target', () => ({
getRuntimeTargetForFileOwner: () => ({ kind: 'local' })
}))
-// Why: the catalog says wt-1 (root /repo, this test's watch target) holds docs/report.md.
-vi.mock('@/lib/runtime-workspace-file-route', () => ({
- findWorkspaceFileRoute: (_state: unknown, _hostId: string, absolutePath: string) =>
- absolutePath === '/repo/docs/report.md'
- ? { worktreeId: 'wt-1', relativePath: 'docs/report.md', executionHostId: 'local' }
- : null
-}))
vi.mock('./editor-external-watch-disk-verification', () => ({
scheduleDebouncedEditorExternalReload: harness.scheduleReload,
scheduleEditorSelfMoveEchoVerification: harness.compareDirty
@@ -133,22 +126,6 @@ describe('reconcileEditorTabsAfterOverflow', () => {
expect(harness.scheduleReload).not.toHaveBeenCalled()
})
- it("re-reads another workspace's read-only view of the watched workspace's file", async () => {
- setTabs(
- tab('view', {
- worktreeId: 'wt-other',
- filePath: '/repo/docs/report.md',
- relativePath: '/repo/docs/report.md',
- readOnly: true
- })
- )
- harness.runtimePathsExist.mockResolvedValue([{ exists: true }])
- reconcileEditorTabsAfterOverflow(target, true)
- await vi.waitFor(() => expect(harness.scheduleReload).toHaveBeenCalledTimes(1))
-
- expect(harness.scheduleReload).toHaveBeenCalledWith(notification('docs/report.md'))
- })
-
it('a storm overflow reloads clean tabs without probing or dropping combined bodies', () => {
setTabs(
tab('a.md', { externalMutation: 'deleted' }),
diff --git a/src/renderer/src/hooks/editor-external-watch-overflow.ts b/src/renderer/src/hooks/editor-external-watch-overflow.ts
index 1f60fbc6923..7d544805af7 100644
--- a/src/renderer/src/hooks/editor-external-watch-overflow.ts
+++ b/src/renderer/src/hooks/editor-external-watch-overflow.ts
@@ -1,5 +1,4 @@
import { useAppStore } from '@/store'
-import { openFileOwnerScope } from '@/lib/open-file-owner-scope'
import type { OpenFile } from '@/store/slices/editor'
import type { EditorExternalWatchBatchPathIndex } from '@/components/editor/editor-external-watch-path-index'
import { isExternalReloadableEditorTab } from '@/components/editor/editor-autosave'
@@ -89,9 +88,8 @@ export function reconcileEditorTabsAfterOverflow(target: OverflowTarget, resync:
const state = useAppStore.getState()
const probes: EditTabProbe[] = []
for (const file of state.openFiles) {
- const ownerScope = openFileOwnerScope(file, state)
if (
- ownerScope.worktreeId !== target.worktreeId ||
+ file.worktreeId !== target.worktreeId ||
getOpenFileRuntimeOwner(file) !== (target.runtimeEnvironmentId ?? null) ||
!isExternalReloadableEditorTab(file)
) {
@@ -116,7 +114,7 @@ export function reconcileEditorTabsAfterOverflow(target: OverflowTarget, resync:
const notification: EditorExternalWatchNotification = {
worktreeId: target.worktreeId,
worktreePath: target.worktreePath,
- relativePath: ownerScope.relativePath,
+ relativePath: file.relativePath,
runtimeEnvironmentId: target.runtimeEnvironmentId ?? null,
...getLocalWindowsWslAliasOption(target)
}
diff --git a/src/renderer/src/hooks/editor-external-watch-targets.ts b/src/renderer/src/hooks/editor-external-watch-targets.ts
index f8b12cbf232..7f36f0ec7b2 100644
--- a/src/renderer/src/hooks/editor-external-watch-targets.ts
+++ b/src/renderer/src/hooks/editor-external-watch-targets.ts
@@ -1,5 +1,4 @@
-import { useAppStore, type AppState } from '@/store'
-import { openFileOwnerWorktreeId } from '@/lib/open-file-owner-scope'
+import type { AppState } from '@/store'
import type { OpenFile } from '@/store/slices/editor'
import { findWorktreeById } from '@/store/slices/worktree-helpers'
import { isVirtualEditorFile } from '@/store/slices/editor/tabs/editor-tab-content-type'
@@ -58,7 +57,6 @@ type WatchedTargetsSnapshot = {
unwatchedTargetsKey: string
}
-let cachedOwnersKey = ''
let cachedOpenFiles: AppState['openFiles'] | null = null
let cachedWorktreesByRepo: AppState['worktreesByRepo'] | null = null
let cachedRepos: AppState['repos'] | null = null
@@ -151,19 +149,7 @@ export function selectEditorExternalWatchTargets(
): WatchedTargetsSnapshot {
const runtimeEnvironmentId = state.settings?.activeRuntimeEnvironmentId?.trim() || undefined
const activeWorkspaceExecutionHostId = state.activeWorkspaceExecutionHostId ?? null
- // Why the store: this selector's narrowed state lacks the catalog the owner lookup reads, and
- // the owners join the cache key because catalog fields outside it can move them.
- const catalog = useAppStore.getState()
- const watchWorktreeIds = new Map()
- for (const file of state.openFiles) {
- const owner = openFileOwnerWorktreeId(file, catalog)
- if (owner !== file.worktreeId) {
- watchWorktreeIds.set(file.id, owner)
- }
- }
- const ownersKey = [...watchWorktreeIds].join('|')
if (
- cachedOwnersKey === ownersKey &&
cachedOpenFiles === state.openFiles &&
cachedWorktreesByRepo === selectWorktreesByRepo(state) &&
cachedRepos === state.repos &&
@@ -201,11 +187,7 @@ export function selectEditorExternalWatchTargets(
const consumers = watchEligibleIds.has(file.worktreeId)
? consumersByWorktreeId
: unwatchedConsumersByWorktreeId
- addWatchConsumer(
- consumers,
- watchWorktreeIds.get(file.id) ?? file.worktreeId,
- getOpenFileWatchConsumer(file)
- )
+ addWatchConsumer(consumers, file.worktreeId, getOpenFileWatchConsumer(file))
}
const activeWorktreeId = state.activeWorktreeId
const activeWorktree = activeWorktreeId
@@ -256,7 +238,6 @@ export function selectEditorExternalWatchTargets(
const nextTargets = sortedEntries.map(([, target]) => target)
const targetsKey = sortedEntries.map(([key]) => key).join('|')
const unwatchedTargetsKey = [...unwatchedTargetKeys].sort().join('|')
- cachedOwnersKey = ownersKey
cachedOpenFiles = state.openFiles
cachedWorktreesByRepo = selectWorktreesByRepo(state)
cachedRepos = state.repos
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json
index a8f00e84d84..bb835618fae 100644
--- a/src/renderer/src/i18n/locales/en.json
+++ b/src/renderer/src/i18n/locales/en.json
@@ -3687,11 +3687,7 @@
"downloadOpenWithDefaultApp": "Download & open with default app",
"downloadOpenFailed": "Failed to download '{{value0}}'.",
"openInThisGroup": "Open in this group",
- "openInThisGroupRefused": "Couldn't open {{value0}} in this group",
- "fileInOtherWorkspace": "This file is in {{value0}}",
- "fileInAnotherWorkspace": "This file is in another workspace",
- "openInOwningWorkspace": "Open there",
- "openedHereReadOnly": "Opened here as read-only"
+ "openInThisGroupRefused": "Couldn't open {{value0}} in this group"
},
"TerminalSessionStateSaveFailureDialog": {
"6bee0c8f17": "Open Disk Space Analyzer",
@@ -16276,7 +16272,6 @@
"6c4f1a8d2e": "Check details are unavailable."
},
"EditorPanelHeader": {
- "readOnlyBadge": "Read-only",
"fb8331694e": "Open Preview to the Side",
"4157f3cbf3": "Open Markdown Preview",
"269ce4842b": "Copy Relative Path",
diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json
index e83e26e2875..97f5c99a51c 100644
--- a/src/renderer/src/i18n/locales/es.json
+++ b/src/renderer/src/i18n/locales/es.json
@@ -3565,11 +3565,7 @@
"downloadOpenWithDefaultApp": "Descargar y abrir con la aplicación predeterminada",
"downloadOpenFailed": "No se pudo descargar '{{value0}}'.",
"openInThisGroup": "Abrir en este grupo",
- "openInThisGroupRefused": "No se pudo abrir {{value0}} en este grupo",
- "fileInOtherWorkspace": "Este archivo está en {{value0}}",
- "fileInAnotherWorkspace": "Este archivo está en otro espacio de trabajo",
- "openInOwningWorkspace": "Abrir allí",
- "openedHereReadOnly": "Abierto aquí en modo de solo lectura"
+ "openInThisGroupRefused": "No se pudo abrir {{value0}} en este grupo"
},
"TerminalQuickCommandsSubmenu": {
"3ccc7981bb": "Anfitrión no disponible",
@@ -15960,7 +15956,6 @@
"6c4f1a8d2e": "Los detalles del check no están disponibles."
},
"EditorPanelHeader": {
- "readOnlyBadge": "Solo lectura",
"fb8331694e": "Abrir vista previa al lado",
"4157f3cbf3": "Abrir vista previa de Markdown",
"269ce4842b": "Copiar ruta relativa",
diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json
index c3533c6dc35..e3f33dc5ca5 100644
--- a/src/renderer/src/i18n/locales/fr.json
+++ b/src/renderer/src/i18n/locales/fr.json
@@ -3553,11 +3553,7 @@
"downloadOpenWithDefaultApp": "Téléchargez et ouvrez avec l'application par défaut",
"downloadOpenFailed": "Échec du téléchargement de « {{value0}} ».",
"openInThisGroup": "Ouvrir dans ce groupe",
- "openInThisGroupRefused": "Impossible d'ouvrir {{value0}} dans ce groupe",
- "fileInOtherWorkspace": "Ce fichier se trouve dans {{value0}}",
- "fileInAnotherWorkspace": "Ce fichier se trouve dans un autre espace de travail",
- "openInOwningWorkspace": "Ouvrir là-bas",
- "openedHereReadOnly": "Ouvert ici en lecture seule"
+ "openInThisGroupRefused": "Impossible d'ouvrir {{value0}} dans ce groupe"
},
"TerminalSessionStateSaveFailureDialog": {
"6bee0c8f17": "Ouvrir l'Analyseur d'espace disque",
@@ -16104,7 +16100,6 @@
"6c4f1a8d2e": "Les détails de la vérification sont indisponibles."
},
"EditorPanelHeader": {
- "readOnlyBadge": "Lecture seule",
"fb8331694e": "Ouvrir l'aperçu sur le côté",
"4157f3cbf3": "Ouvrir l'aperçu Markdown",
"269ce4842b": "Copier le chemin relatif",
diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json
index b0fa73c3241..5d8e241faf0 100644
--- a/src/renderer/src/i18n/locales/ja.json
+++ b/src/renderer/src/i18n/locales/ja.json
@@ -3547,11 +3547,7 @@
"downloadOpenWithDefaultApp": "ダウンロードしてデフォルトのアプリで開く",
"downloadOpenFailed": "「{{value0}}」のダウンロードに失敗しました。",
"openInThisGroup": "このグループで開く",
- "openInThisGroupRefused": "{{value0}} をこのグループで開けませんでした",
- "fileInOtherWorkspace": "このファイルは {{value0}} にあります",
- "fileInAnotherWorkspace": "このファイルは別のワークスペースにあります",
- "openInOwningWorkspace": "そこで開く",
- "openedHereReadOnly": "ここでは読み取り専用で開いています"
+ "openInThisGroupRefused": "{{value0}} をこのグループで開けませんでした"
},
"TerminalQuickCommandsSubmenu": {
"3ccc7981bb": "ホストが利用不可",
@@ -15944,7 +15940,6 @@
"6c4f1a8d2e": "チェック詳細を利用できません。"
},
"EditorPanelHeader": {
- "readOnlyBadge": "読み取り専用",
"fb8331694e": "プレビューを横に開く",
"4157f3cbf3": "Markdown プレビューを開く",
"269ce4842b": "相対パスをコピー",
diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json
index 63e440c6080..12aabd61e15 100644
--- a/src/renderer/src/i18n/locales/ko.json
+++ b/src/renderer/src/i18n/locales/ko.json
@@ -3547,11 +3547,7 @@
"downloadOpenWithDefaultApp": "기본 앱 다운로드 및 열기",
"downloadOpenFailed": "'{{value0}}'을(를) 다운로드하지 못했습니다.",
"openInThisGroup": "이 그룹에서 열기",
- "openInThisGroupRefused": "이 그룹에서 {{value0}}을(를) 열 수 없습니다",
- "fileInOtherWorkspace": "이 파일은 {{value0}}에 있습니다",
- "fileInAnotherWorkspace": "이 파일은 다른 워크스페이스에 있습니다",
- "openInOwningWorkspace": "거기서 열기",
- "openedHereReadOnly": "여기서는 읽기 전용으로 열렸습니다"
+ "openInThisGroupRefused": "이 그룹에서 {{value0}}을(를) 열 수 없습니다"
},
"TerminalQuickCommandsSubmenu": {
"3ccc7981bb": "호스트를 사용할 수 없음",
@@ -15944,7 +15940,6 @@
"6c4f1a8d2e": "체크 세부 정보를 사용할 수 없습니다."
},
"EditorPanelHeader": {
- "readOnlyBadge": "읽기 전용",
"fb8331694e": "측면으로 미리보기 열기",
"4157f3cbf3": "Markdown 미리보기 열기",
"269ce4842b": "상대 경로 복사",
diff --git a/src/renderer/src/i18n/locales/pt.json b/src/renderer/src/i18n/locales/pt.json
index 669514f31ab..6d95c0d78cd 100644
--- a/src/renderer/src/i18n/locales/pt.json
+++ b/src/renderer/src/i18n/locales/pt.json
@@ -12206,7 +12206,6 @@
"759463a221": "Não é possível abrir o diretório: {{value0}}"
},
"EditorPanelHeader": {
- "readOnlyBadge": "Somente leitura",
"fb8331694e": "Abrir Pré-visualização ao Lado",
"4157f3cbf3": "Abrir Pré-visualização de Markdown",
"269ce4842b": "Copiar Caminho Relativo",
@@ -15522,11 +15521,7 @@
"downloadOpenWithDefaultApp": "Baixar e abrir com o aplicativo padrão",
"downloadOpenFailed": "Falha ao baixar '{{value0}}'.",
"openInThisGroup": "Abrir neste grupo",
- "openInThisGroupRefused": "Não foi possível abrir {{value0}} neste grupo",
- "fileInOtherWorkspace": "Este arquivo está em {{value0}}",
- "fileInAnotherWorkspace": "Este arquivo está em outro espaço de trabalho",
- "openInOwningWorkspace": "Abrir lá",
- "openedHereReadOnly": "Aberto aqui como somente leitura"
+ "openInThisGroupRefused": "Não foi possível abrir {{value0}} neste grupo"
},
"MobileDriverOverlay": {
"c6460cf584": "Retomar",
diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json
index 2cedcf90f86..9f887e277b6 100644
--- a/src/renderer/src/i18n/locales/zh.json
+++ b/src/renderer/src/i18n/locales/zh.json
@@ -3547,11 +3547,7 @@
"downloadOpenWithDefaultApp": "下载并使用默认应用程序打开",
"downloadOpenFailed": "无法下载“{{value0}}”。",
"openInThisGroup": "在此分组中打开",
- "openInThisGroupRefused": "无法在此分组中打开 {{value0}}",
- "fileInOtherWorkspace": "此文件位于 {{value0}}",
- "fileInAnotherWorkspace": "此文件位于另一个工作区",
- "openInOwningWorkspace": "在那里打开",
- "openedHereReadOnly": "已在此处以只读方式打开"
+ "openInThisGroupRefused": "无法在此分组中打开 {{value0}}"
},
"TerminalQuickCommandsSubmenu": {
"3ccc7981bb": "主机不可用",
@@ -15944,7 +15940,6 @@
"6c4f1a8d2e": "检查详情不可用。"
},
"EditorPanelHeader": {
- "readOnlyBadge": "只读",
"fb8331694e": "打开侧面预览",
"4157f3cbf3": "打开 Markdown 预览",
"269ce4842b": "复制相对路径",
diff --git a/src/renderer/src/lib/open-file-owner-scope.ts b/src/renderer/src/lib/open-file-owner-scope.ts
deleted file mode 100644
index ff68c313c19..00000000000
--- a/src/renderer/src/lib/open-file-owner-scope.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import type { OpenFile } from '@/store/slices/editor'
-import type { AppState } from '@/store/types'
-import { LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host'
-import { isFloatingWorkspaceId } from '../../../shared/floating-workspace-worktree'
-import { findWorkspaceFileRoute } from './runtime-workspace-file-route'
-import { editorBufferRouteEpochOf } from '@/store/slices/editor/buffers/editor-buffer-identity-cache'
-
-type OwnerScopedFile = Pick<
- OpenFile,
- | 'worktreeId'
- | 'filePath'
- | 'relativePath'
- | 'readOnly'
- | 'liveTail'
- | 'runtimeEnvironmentId'
- | 'terminalArtifactGrantId'
- | 'externalSshTargetId'
->
-
-type OwnerScope = { worktreeId: string; relativePath: string }
-const ownerScopeCache = new WeakMap