fix(app): reuse file tabs opened from nested-project Changes

Changes paths are relative to the current worktree. Resolve file actions
from that root, then give file tabs and chat attachments the existing
workspace-relative target for files inside the project. Keep absolute
targets for changed files outside it so both paths remain addressable.

The contributor's root-path fix opened inside-project files with absolute
targets, which differed from Files and created duplicate retained tabs.
A failing-first browser journey covers navigation between both views.

Co-authored-by: 3ae3ae <ogyshjsm@naver.com>
This commit is contained in:
paseo-bot[bot]
2026-11-02 13:41:29 +00:00
co-authored by 3ae3ae
parent cacd97d663
commit 1211cceaa1
2 changed files with 46 additions and 4 deletions
@@ -8,6 +8,7 @@ import { daemonWsRoutePattern } from "../support/helpers/daemon-port";
import { getServerId } from "../support/helpers/server-id";
import { connectSeedClient } from "../support/helpers/seed-client";
import { createTempGitRepo } from "../support/helpers/workspace";
import { openFileExplorer, openFileFromExplorer } from "../support/helpers/file-explorer";
import { openChangesPanel, waitForWorkspaceTabsVisible } from "../support/helpers/workspace-tabs";
interface DirtyWorkspace {
@@ -506,6 +507,44 @@ for (const { workspaceSubdirectory, filePath, content, visibleContent } of [
});
}
test("Changes and Files reuse a retained file tab in a subfolder project", async ({ page }) => {
const workspace = await createWorkspaceWithMountedTabDiff({ workspaceSubdirectory: "src" });
await openWorkspaceChanges(page, workspace);
await openChangedFile(page, "src/use-mounted-tab-set.ts");
await editAndSaveChangedFile(page, workspace);
await openChangesPanel(page);
await openFileExplorer(page);
await openFileFromExplorer(page, "use-mounted-tab-set.ts");
await expect(page.getByTestId(/^workspace-tab-file_/).filter({ visible: true })).toHaveCount(1);
await expect(page.getByTestId("workspace-tab-file_use-mounted-tab-set.ts")).toHaveAttribute(
"aria-selected",
"true",
);
await expect(visibleFileEditor(page)).toContainText("// retained file tab");
});
async function openChangedFile(page: Page, filePath: string): Promise<void> {
const panel = page.getByTestId("working-diff-panel").filter({ visible: true });
await diffHeaderForPath(panel, filePath)
.getByTestId(/^diff-file-\d+-toggle$/)
.click({ button: "right" });
await page.getByRole("menuitem", { name: "Open file", exact: true }).click();
await expect(visibleFileEditor(page)).toContainText("interface DeriveRenderMountedTabIdsInput");
}
function visibleFileEditor(page: Page): Locator {
return page.getByTestId("workspace-file-pane").filter({ visible: true }).locator(".cm-content");
}
async function editAndSaveChangedFile(page: Page, workspace: DirtyWorkspace): Promise<void> {
const content = "// retained file tab\n" + AFTER;
await visibleFileEditor(page).fill(content);
await expect
.poll(() => readFile(path.join(workspace.repoPath, "src/use-mounted-tab-set.ts"), "utf8"))
.toBe(content);
}
test("canvas file headers select without toggling for context menu and long press", async ({
page,
}) => {
+7 -4
View File
@@ -56,7 +56,7 @@ import * as Clipboard from "expo-clipboard";
import { useFileDownload } from "@/hooks/use-file-download";
import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon";
import { buildAbsoluteExplorerPath } from "@/utils/explorer-paths";
import { isWorkspaceRootPath } from "@/workspace/file-open";
import { resolveWorkspaceFilePaths } from "@/workspace/file-open";
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
import { GitActionsSplitButton } from "@/git/actions-split-button";
import type { GitActions } from "@/git/policy";
@@ -1637,14 +1637,17 @@ export function ChangesSurface({
[appSettings.monoFontFamily, codeFontSize, effectiveLayout, wrapLines],
);
const fileRoot = resolveChangesFileRoot(status, cwd);
const isRootWorkspace = isWorkspaceRootPath({ path: fileRoot, workspaceRoot: cwd });
const resolveFilePath = useCallback(
(path: string) => buildAbsoluteExplorerPath({ workspaceRoot: fileRoot, entryPath: path }),
[fileRoot],
);
const resolveFileTargetPath = useCallback(
(path: string) => (isRootWorkspace ? path : resolveFilePath(path)),
[isRootWorkspace, resolveFilePath],
(path: string) => {
const absolutePath = resolveFilePath(path);
const resolved = resolveWorkspaceFilePaths({ path: absolutePath, workspaceRoot: cwd });
return resolved?.relativePath ?? absolutePath;
},
[cwd, resolveFilePath],
);
const handleOpenFile = useCallback(
(path: string) => onOpenFile?.(resolveFileTargetPath(path)),