diff --git a/src/platform/remote.test.ts b/src/platform/remote.test.ts index 137e3a713..74596d647 100644 --- a/src/platform/remote.test.ts +++ b/src/platform/remote.test.ts @@ -1,5 +1,8 @@ import { Channel } from "@tauri-apps/api/core"; -import { beforeAll, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, describe, expect, it } from "vitest"; +import type { ProjectInfo, Settings } from "./ipc"; +import { useProject } from "./project"; +import { disableRemote, enableRemote, useRemote } from "./remote"; import { encodeArgs, remoteRouting } from "./remote"; describe("remote invoke", () => { @@ -42,3 +45,48 @@ describe("remote invoke", () => { expect(encodeArgs(undefined).json).toBe("{}"); }); }); + +describe("remote session lifetime", () => { + const session = { + target: { kind: "wsl" as const, distro: "Ubuntu" }, + root: "/home/me/app", + host: "Ubuntu", + os: "linux", + commands: ["vcs_roots"], + }; + const project = { + name: "app", + root: "/home/me/app", + trusted: true, + isGit: true, + settings: {}, + } as unknown as ProjectInfo; + + afterEach(() => { + disableRemote(); + useProject.setState({ project: null, settings: null, opening: false, openingPath: null, lightEdit: false }); + }); + + it("keeps routing when a window with no project of its own updates the settings only", () => { + // The comparison window: it rides its parent's session and keeps just the settings + // (restoring the project would overwrite the main window's editor session). + enableRemote(session); + useProject.setState({ settings: { editor: { fontSize: 12 } } as unknown as Settings }); + expect(useRemote.getState().session).not.toBeNull(); + }); + + it("ends routing when the window's project leaves the session's root", () => { + enableRemote(session); + useProject.setState({ project }); + // Close Project (or a local project opened over the remote one) — the session goes. + useProject.setState({ project: null }); + expect(useRemote.getState().session).toBeNull(); + }); + + it("keeps routing while the project still sits at the session's root", () => { + enableRemote(session); + useProject.setState({ project }); + useProject.setState({ settings: { editor: { fontSize: 14 } } as unknown as Settings }); + expect(useRemote.getState().session).not.toBeNull(); + }); +}); diff --git a/src/platform/remote.ts b/src/platform/remote.ts index 504fb1211..fb7ef224b 100644 --- a/src/platform/remote.ts +++ b/src/platform/remote.ts @@ -338,7 +338,15 @@ export function forgetRemote(target: RemoteTarget, path: string) { } // Any way of leaving the remote project (Close Project, opening a local one) ends remote routing. -useProject.subscribe((s) => { +useProject.subscribe((s, prev) => { const session = useRemote.getState().session; - if (session && !s.opening && s.project?.root !== session.root) disableRemote(); + if (!session || s.opening) return; + // Only a window whose project was the session's root can leave it. Auxiliary windows + // (comparison, deployment) hold no project of their own — they ride their parent's + // session, and a settings-only update (the comparison window keeps just the settings) + // must not drop the routing: its next commands would run here instead of on the host, + // where no project is open (`open_project` / `light_edit_open` / `close_project` end + // the session themselves, see `route`). + if (prev.project?.root !== session.root || s.project?.root === session.root) return; + disableRemote(); });