fix(remote): a desktop no longer serves other runtimes its tabs on its servers' files (#33424) (#33752)

* fix(remote): a desktop ignores its own workspaces echoed back by a server it serves (#33424)

When two desktops pair to each other, each server republishes the tabs it
mirrors from its own servers. Desktop A then received B's mirror of A's own
workspace under the real workspace id and grafted it onto its local copy as
B-hosted tabs, so opening those files asked B (selector_not_found) and closing
them closed B's tabs.

A session-tabs frame from a paired server no longer writes a workspace this
app lists as its own (a stamped local or direct SSH row) unless that server
also lists a copy. Other servers' workspaces stay open to every runtime.

* fix(remote): retire echoes already taken and replay frames a twin's late row releases

An echo fenced out of this app's own workspace now applies as that server's
tombstone, so rows it left before the fence (restored sessions, or frames that
arrived before this app's own row listed) are retired without closing anything.
A held frame replays once the server's own copy of the workspace lists, so a
same-id twin that publishes before its row loads still mirrors. The e2e waits
on a later frame from the same stream instead of a fixed delay.

* fix(remote): serve a desktop's tabs on its servers' files only to its phone, not to other runtimes

Redesign of the #33424 fix at the source. A desktop publishes every editor tab in
its window, including ones on files of its paired servers; its phone uses those
through the desktop's relay. Served to another runtime, such a tab routes the file
to the desktop, and in a pairing cycle it is that runtime's own workspace coming
back as the desktop's tab: file reads fail with selector_not_found and closing it
closes the desktop's real tab.

Publishers now stamp those rows with hostRuntimeEnvironmentId (window, layout-owner
main projection and the windowless projection alike). projectSessionTabsForClient
strips the stamp for every caller and drops the stamped rows for runtime-scope
callers, keeping groups, layout and the active tab consistent. Phones and local
callers see what they saw before. The receiver-side fence is removed.

* refactor(remote): keep the file-host stamp helper outside the old-client boundary; test an all-server snapshot
This commit is contained in:
Neil
2026-10-25 20:28:07 -07:00
committed by GitHub
parent 5d6a110706
commit ddcedee37c
10 changed files with 367 additions and 8 deletions
@@ -61,6 +61,8 @@ export type RuntimeMobileSessionMarkdownTab = {
documentVersion: string
color?: string | null
isPinned?: boolean
/** The paired server the file lives on. Its publisher sets it; runtimes strip it before serving. */
hostRuntimeEnvironmentId?: string
}
export type RuntimeMobileSessionFileTab = {
@@ -76,6 +78,8 @@ export type RuntimeMobileSessionFileTab = {
color?: string | null
isPinned?: boolean
isActive: boolean
/** The paired server the file lives on. Its publisher sets it; runtimes strip it before serving. */
hostRuntimeEnvironmentId?: string
}
export type RuntimeMobileSessionBrowserTab = {
+9
View File
@@ -0,0 +1,9 @@
import type { RuntimeMobileSessionFileTab } from './runtime-mobile-session-tab-contracts'
/** Names the paired server a row's file lives on, for runtimes to keep from other runtimes (#33424). */
export function editorRowHost(file: {
runtimeEnvironmentId?: string | null
}): Pick<RuntimeMobileSessionFileTab, 'hostRuntimeEnvironmentId'> {
const hostRuntimeEnvironmentId = file.runtimeEnvironmentId?.trim()
return hostRuntimeEnvironmentId ? { hostRuntimeEnvironmentId } : {}
}
@@ -5,6 +5,7 @@ import type {
RuntimeMobileSessionMarkdownTab
} from '../../runtime-mobile-session-tab-contracts'
import type { LayoutEditorView } from '../layout-editor-view'
import { editorRowHost } from '../../session-tab-file-host'
import type { DesktopLayoutView } from '../workspace-layout-beside'
import { editorFileKey, editorTabFile } from '../workspace-layout-editor-tab-file'
import type { LayoutContentTab, LayoutEditorFile, WorkspaceLayout } from '../workspace-layout-model'
@@ -54,7 +55,8 @@ export function editorTabRow(tab: LayoutContentTab, scope: EditorRowScope): Edit
filePath: file.filePath,
relativePath: file.relativePath,
isDirty: hasDraft(scope, file),
...tabRowProps(tab, scope)
...tabRowProps(tab, scope),
...editorRowHost(file)
}
if (file.language === 'markdown') {
return {
@@ -106,7 +108,8 @@ function markdownPreviewRow(
sourceFilePath: sourceFile.filePath,
sourceRelativePath: sourceFile.relativePath,
documentVersion: `file:${sourceId}`,
...tabRowProps(tab, scope)
...tabRowProps(tab, scope),
...editorRowHost(preview)
}
}
@@ -135,7 +138,8 @@ export function diffTabRow(
...(diffSource === 'staged' || diffSource === 'unstaged' ? { diffSource } : {}),
// The model holds no unsaved edit of a diff's modified side; the window's row it lists wins.
isDirty: false,
...tabRowProps(tab, scope)
...tabRowProps(tab, scope),
...editorRowHost(file)
}
}
@@ -159,6 +163,7 @@ export function reviewTabRow(
mode: 'edit',
// The model holds no unsaved edit of a review; the window's row it lists wins.
isDirty: false,
...tabRowProps(tab, scope)
...tabRowProps(tab, scope),
...editorRowHost(file)
}
}