diff --git a/apps/desktop/electron/desktop-profile.test.ts b/apps/desktop/electron/desktop-profile.test.ts index af606fd637..4d6603eb27 100644 --- a/apps/desktop/electron/desktop-profile.test.ts +++ b/apps/desktop/electron/desktop-profile.test.ts @@ -8,6 +8,7 @@ import { test } from 'vitest' import { createDesktopProfilePreferences, resolveDesktopConnectionRequest, + resolveDesktopWindowLaunch, resolveDesktopWindowRoute } from './desktop-profile' import { WindowConnectionRouteRegistry } from './window-connection-route' @@ -66,6 +67,15 @@ test('explicit routes are strict and never fall back to a different source windo connectionId: 'remote-a', profile: 'work' }) + // Only an explicit route pins the window's New-session default; an inherited + // or fallback route seeds boot alone. + assert.deepEqual(resolveDesktopWindowLaunch(explicit, routes.get(1), fallback), { ...explicit, profileWindow: true }) + assert.deepEqual(resolveDesktopWindowLaunch(undefined, routes.get(1), fallback), { + connectionId: 'remote-a', + profile: 'work', + profileWindow: false + }) + assert.deepEqual(resolveDesktopWindowLaunch(undefined, null, fallback), { ...fallback, profileWindow: false }) assert.deepEqual(routes.get(1), { connectionId: 'remote-a', profile: 'work', registryScoped: true }) assert.throws(() => resolveDesktopWindowRoute({ profile: 'work' }, routes.get(1), fallback)) assert.throws(() => resolveDesktopWindowRoute({ connectionId: null, profile: '../work' }, routes.get(1), fallback)) diff --git a/apps/desktop/electron/desktop-profile.ts b/apps/desktop/electron/desktop-profile.ts index 08a97c0a9a..65c242280d 100644 --- a/apps/desktop/electron/desktop-profile.ts +++ b/apps/desktop/electron/desktop-profile.ts @@ -52,6 +52,21 @@ export function resolveDesktopWindowRoute( : { ...fallback } } +/** A peer window's launch route plus whether the renderer asked for that + * route explicitly ("Open profile in new window"). Only an explicit route is + * the window's New-session default; an inherited one seeds boot alone. */ +export interface DesktopWindowLaunch extends DesktopProfileRoute { + profileWindow: boolean +} + +export function resolveDesktopWindowLaunch( + explicit: unknown, + source: WindowConnectionRoute | null, + fallback: DesktopProfileRoute +): DesktopWindowLaunch { + return { ...resolveDesktopWindowRoute(explicit, source, fallback), profileWindow: explicit !== undefined } +} + // A profile-less boot/reconnect belongs to its sender. An explicit profile // keeps the legacy route, even if the sender serves the same name remotely. export function resolveDesktopConnectionRequest( diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index 292f032356..fd83994d2e 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -171,7 +171,7 @@ import { DESKTOP_PROFILE_NAME_RE, type DesktopProfileRoute, resolveDesktopConnectionRequest, - resolveDesktopWindowRoute + resolveDesktopWindowLaunch } from './desktop-profile' import { resolveDesktopRemoteRoute, v1SshTerminalPoolKey } from './desktop-remote-route' import { @@ -13569,7 +13569,7 @@ function createInstanceWindow( options?: DesktopProfileRoute, source: BrowserWindow | null = BrowserWindow.getFocusedWindow() || mainWindow ) { - const route = resolveDesktopWindowRoute( + const route = resolveDesktopWindowLaunch( options, source && !source.isDestroyed() ? windowConnectionRoutes.get(source.webContents.id) : null, { connectionId: null, profile: primaryProfileKey() } @@ -13633,7 +13633,6 @@ function createInstanceWindow( win, buildInstanceWindowUrl({ ...route, - profileWindow: options !== undefined, devServer: DEV_SERVER, rendererIndexPath: DEV_SERVER ? undefined : resolveRendererIndex() }), diff --git a/apps/desktop/electron/session-windows.ts b/apps/desktop/electron/session-windows.ts index 669aa1efc8..ff66425a1d 100644 --- a/apps/desktop/electron/session-windows.ts +++ b/apps/desktop/electron/session-windows.ts @@ -5,6 +5,8 @@ import { pathToFileURL } from 'node:url' +import type { DesktopWindowLaunch } from './desktop-profile' + // Secondary windows open at the minimum usable size — a compact side panel for // subagent watch / cmd-click session pop-out, not a second full desktop. const SESSION_WINDOW_MIN_WIDTH = 420 @@ -87,7 +89,12 @@ function buildSessionWindowUrl(sessionId: string, { devServer, profile, renderer // separate marker lets the renderer distinguish a peer from the one primary // app window: app-launch source restoration belongs to the primary only, while // a peer keeps the already-running backend it joined during boot. -function buildInstanceWindowUrl({ connectionId, devServer, profile, profileWindow, rendererIndexPath }: any = {}) { +interface InstanceWindowUrlOptions extends Partial { + devServer?: string + rendererIndexPath?: string +} + +function buildInstanceWindowUrl({ connectionId, devServer, profile, profileWindow, rendererIndexPath }: InstanceWindowUrlOptions = {}) { const query = `?peer=1${profile ? `&profile=${encodeURIComponent(profile)}&connectionId=${encodeURIComponent(connectionId ?? '')}${profileWindow ? '&profileWindow=1' : ''}` : ''}` if (devServer) {