fix(runtime): collapse mirrored Markdown aliases with shared backing identity (#31984)

* fix(runtime): collapse mirrored editor tab variants that differ only by environment

A snapshot that already lists one file under several per-environment host tab ids
(editor:<worktree>:<env>:<path> or the bare path) fanned out into one mirrored tab
per variant. Keep one minted variant per (worktree, mode, path) when building
mirrored editor tabs; tabs with opaque ids (deliberate splits) are untouched.
Protects receivers from older peers that still republish mirrored files and from
snapshots that are already polluted.

* fix(runtime): match encoded editor tab ids and keep the navigation target when collapsing variants

Host tab ids come from buildOwnedEditorFileId, which URL-encodes the worktree,
environment and path, so the raw-path predicate never matched a real variant.
Each candidate is now checked by rebuilding it with buildOwnedEditorFileId, and
the tab a pending focus/follow intent targets wins its bucket ahead of an active
one. Tests build their ids the same way.

* fix(runtime): preserve editor identity while collapsing aliases

* test(runtime): keep headless mirror regression across project boundaries

---------

Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
Dave Choi
2026-10-23 19:21:14 -07:00
committed by GitHub
co-authored by Neil
parent c91a40c43d
commit 60c58ea7e2
4 changed files with 450 additions and 4 deletions
@@ -1,3 +1,4 @@
import type { RuntimeMobileSessionMarkdownTab } from '../../../shared/runtime-types'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { posix as pathPosix } from 'node:path'
import type { BrowserPage, BrowserWorkspace } from '../../../shared/browser-workspace-types'
@@ -5,6 +6,9 @@ import type { Tab } from '../../../shared/tab-types'
import type { OpenFile } from '../store/slices/editor'
import { buildOwnedEditorFileId } from '../store/slices/editor/file-ids/editor-file-ids'
import { applyWebSessionTabsSnapshot } from './web-session-tabs-sync/snapshot-api'
import { recordWebSessionFocusIntent } from './web-session-focus-intent'
import { buildMobileSessionTabSnapshots } from './sync-runtime-graph/mobile-session-snapshots'
import { makeState as makePublisherState } from './sync-runtime-graph-test-harness'
import type { WebSessionTabsSyncState } from './web-session-tabs-sync/state'
import {
ENV,
@@ -553,3 +557,299 @@ describe('applyWebSessionTabsSnapshot', () => {
})
})
})
describe('applyWebSessionTabsSnapshot host-invariant editor dedup', () => {
beforeEach(resetWebSessionTabsSyncTestState)
const variant = (id: string, isActive: boolean): RuntimeMobileSessionMarkdownTab => ({
type: 'markdown',
id,
title: 'README.md',
filePath: '/repo/README.md',
relativePath: 'README.md',
language: 'markdown',
mode: 'edit',
isDirty: false,
isActive,
sourceFileId: README_ID,
sourceFilePath: '/repo/README.md',
sourceRelativePath: 'README.md',
documentVersion: 'file:/repo/README.md'
})
const envVariantId = (env: string) => buildOwnedEditorFileId('/repo/README.md', WT, env)
function stateWithVariant(id: string, retained: OpenFile): WebSessionTabsSyncState {
return makeState({
activeFileId: retained.id,
activeFileIdByWorktree: { [WT]: retained.id },
activeTabType: 'editor',
activeTabTypeByWorktree: { [WT]: 'editor' },
activeGroupIdByWorktree: { [WT]: 'host-group-1' },
openFiles: [retained],
editorDrafts: { [retained.id]: '# unsaved client edit' },
unifiedTabsByWorktree: {
[WT]: [
{
id,
entityId: retained.id,
worktreeId: WT,
groupId: 'host-group-1',
contentType: 'editor',
label: 'README.md',
customLabel: null,
color: null,
sortOrder: 0,
createdAt: NOW - 1,
isPreview: false
}
]
},
groupsByWorktree: {
[WT]: [
{
id: 'host-group-1',
worktreeId: WT,
activeTabId: id,
tabOrder: [id],
recentTabIds: [id]
}
]
}
})
}
const ownedFile: OpenFile = {
id: README_ID,
filePath: '/repo/README.md',
relativePath: 'README.md',
worktreeId: WT,
language: 'markdown',
mode: 'edit',
isDirty: true,
runtimeEnvironmentId: ENV,
mirroredFromRuntimeSession: true
}
it('mirrors one tab when a snapshot lists the same file under several environment variants', () => {
const ids = [
envVariantId('env-a'),
envVariantId('env-b'),
envVariantId('env-c'),
buildOwnedEditorFileId('/repo/README.md', WT, null),
'/repo/README.md'
]
const published = buildMobileSessionTabSnapshots(
makePublisherState({
openFiles: [ownedFile],
unifiedTabsByWorktree: {
[WT]: ids.flatMap((id) => stateWithVariant(id, ownedFile).unifiedTabsByWorktree[WT] ?? [])
}
})
)[0]
if (!published) {
throw new Error('Desktop did not publish its editor tabs')
}
expect(published.tabs).toHaveLength(5)
const tabs = published.tabs.filter((tab) => tab.type === 'markdown')
expect(tabs).toHaveLength(5)
const patch = applyWebSessionTabsSnapshot(
makeState(),
makeSnapshot(tabs, { activeTabId: null, activeTabType: null }),
ENV,
NOW
)
expect(patch.openFiles).toHaveLength(1)
expect(patch.unifiedTabsByWorktree?.[WT]).toHaveLength(1)
expect(patch.unifiedTabsByWorktree?.[WT]?.[0]?.id).toBe(envVariantId('env-a'))
})
it('keeps the variant a follow navigation targets over an active one', () => {
const patch = applyWebSessionTabsSnapshot(
makeState(),
makeSnapshot([variant(envVariantId('env-a'), true), variant(envVariantId('env-b'), false)], {
activeTabId: envVariantId('env-b'),
activeTabType: 'markdown',
navigationIntent: 'follow'
}),
ENV,
NOW
)
expect(patch.unifiedTabsByWorktree?.[WT]).toHaveLength(1)
expect(patch.unifiedTabsByWorktree?.[WT]?.[0]?.id).toBe(envVariantId('env-b'))
})
it('leaves split tabs with opaque ids alone', () => {
const patch = applyWebSessionTabsSnapshot(
makeState(),
makeSnapshot([variant(envVariantId('env-a'), false), variant('split-tab-7f3a', false)], {
activeTabId: null,
activeTabType: null
}),
ENV,
NOW
)
expect(patch.unifiedTabsByWorktree?.[WT]).toHaveLength(2)
})
it('keeps the visible local variant when a status snapshot marks another active', () => {
const visibleId = envVariantId('env-b')
const state = stateWithVariant(visibleId, ownedFile)
const patch = applyWebSessionTabsSnapshot(
state,
makeSnapshot([variant(envVariantId('env-a'), true), variant(visibleId, false)], {
activeTabId: envVariantId('env-a'),
activeTabType: 'markdown'
}),
ENV,
NOW
)
expect(patch.unifiedTabsByWorktree?.[WT]?.map((tab) => tab.id)).toEqual([visibleId])
expect((patch.groupsByWorktree ?? state.groupsByWorktree)[WT]?.[0]?.activeTabId).toBe(visibleId)
expect((patch.openFiles ?? state.openFiles)[0]).toMatchObject({
id: ownedFile.id,
isDirty: true
})
expect(patch.editorDrafts ?? state.editorDrafts).toEqual(state.editorDrafts)
})
it('retains a pending relay draft identity even when navigation selects another variant', () => {
const retained: OpenFile = {
...ownedFile,
id: '/repo/README.md',
runtimeEnvironmentId: null,
pendingOwnerMigration: true
}
const state = stateWithVariant(envVariantId('env-b'), retained)
const selectedId = envVariantId('env-a')
const patch = applyWebSessionTabsSnapshot(
state,
makeSnapshot([variant(selectedId, true), variant(envVariantId('env-b'), false)], {
activeTabId: selectedId,
activeTabType: 'markdown',
navigationIntent: 'follow'
}),
ENV,
NOW
)
expect(patch.openFiles ?? state.openFiles).toEqual([retained])
expect((patch.openFiles ?? state.openFiles)[0]).toBe(retained)
expect(patch.unifiedTabsByWorktree?.[WT]).toMatchObject([
{ id: selectedId, entityId: retained.id }
])
expect(patch.unifiedTabsByWorktree?.[WT]).toHaveLength(1)
expect(patch.editorDrafts ?? state.editorDrafts).toEqual(state.editorDrafts)
})
it('honors a pending caller focus intent over the visible and host-active variants', () => {
const visibleId = envVariantId('env-b')
const requestedId = envVariantId('env-c')
const state = stateWithVariant(visibleId, ownedFile)
recordWebSessionFocusIntent({ environmentId: ENV }, WT, requestedId)
const patch = applyWebSessionTabsSnapshot(
state,
makeSnapshot([
variant(envVariantId('env-a'), true),
variant(visibleId, false),
variant(requestedId, false)
]),
ENV,
NOW
)
expect(patch.unifiedTabsByWorktree?.[WT]?.map((tab) => tab.id)).toEqual([requestedId])
expect((patch.groupsByWorktree ?? state.groupsByWorktree)[WT]?.[0]?.activeTabId).toBe(
requestedId
)
})
it.each([
{ filePath: '/workspace/notes/Read me #1%.md', worktree: 'folder:/workspace' },
{ filePath: 'C:\\workspace\\docs\\Read me.md', worktree: 'folder:C:\\workspace' },
{ filePath: '\\\\server\\share\\Read me.md', worktree: 'folder:\\\\server\\share' }
])('collapses encoded variants for the host path $filePath', ({ filePath, worktree }) => {
const tabs = ['host:a', 'host:b'].map((environment) => ({
...variant(buildOwnedEditorFileId(filePath, worktree, environment), false),
filePath,
sourceFilePath: filePath
}))
const state = makeState({ activeWorktreeId: worktree })
const snapshot = makeSnapshot(tabs, { worktree, activeTabId: null, activeTabType: null })
const patch = applyWebSessionTabsSnapshot(state, snapshot, ENV, NOW)
const nextState = { ...state, ...patch }
const replay = applyWebSessionTabsSnapshot(nextState, snapshot, ENV, NOW + 1)
expect(patch.openFiles).toMatchObject([{ filePath, worktreeId: worktree }])
expect(patch.openFiles).toHaveLength(1)
expect(patch.unifiedTabsByWorktree?.[worktree]).toHaveLength(1)
expect((replay.openFiles ?? nextState.openFiles)[0]?.id).toBe(patch.openFiles?.[0]?.id)
expect(
(replay.unifiedTabsByWorktree ?? nextState.unifiedTabsByWorktree)[worktree]
).toHaveLength(1)
})
it('keeps edit and preview views distinct while collapsing edit aliases', () => {
const preview = { ...variant(envVariantId('env-c'), false), mode: 'markdown-preview' as const }
const patch = applyWebSessionTabsSnapshot(
makeState(),
makeSnapshot([
variant(envVariantId('env-a'), false),
variant(envVariantId('env-b'), false),
preview
]),
ENV,
NOW
)
expect(patch.unifiedTabsByWorktree?.[WT]).toHaveLength(2)
expect(patch.openFiles).toMatchObject([
{ id: README_ID, mode: 'edit' },
{ mode: 'markdown-preview', markdownPreviewSourceFileId: README_ID }
])
})
it('leaves a malformed environment segment opaque', () => {
const malformedId = `editor:${encodeURIComponent(WT)}:%:${encodeURIComponent('/repo/README.md')}`
const patch = applyWebSessionTabsSnapshot(
makeState(),
makeSnapshot([variant(envVariantId('env-a'), false), variant(malformedId, false)]),
ENV,
NOW
)
expect(patch.unifiedTabsByWorktree?.[WT]?.map((tab) => tab.id)).toEqual([
envVariantId('env-a'),
malformedId
])
})
it('keeps distinct backing source identities at the same path', () => {
const first = { ...variant(envVariantId('env-a'), true), sourceFileId: 'local-file-owner' }
const second = { ...variant(envVariantId('env-b'), false), sourceFileId: 'remote-file-owner' }
const patch = applyWebSessionTabsSnapshot(makeState(), makeSnapshot([first, second]), ENV, NOW)
expect(patch.unifiedTabsByWorktree?.[WT]?.map((tab) => tab.id)).toEqual([first.id, second.id])
})
it('keeps legacy file views without a backing source identity', () => {
const tabs = ['env-a', 'env-b'].map((environment) => ({
type: 'file' as const,
id: envVariantId(environment),
title: 'README.md',
filePath: '/repo/README.md',
relativePath: 'README.md',
language: 'plaintext',
isDirty: false,
isActive: false
}))
const patch = applyWebSessionTabsSnapshot(makeState(), makeSnapshot(tabs), ENV, NOW)
expect(patch.unifiedTabsByWorktree?.[WT]?.map((tab) => tab.id)).toEqual(
tabs.map((tab) => tab.id)
)
})
})
@@ -1,4 +1,5 @@
import { buildWebSessionExistingTabIndex } from '../web-session-existing-tab-index'
import { resolveWebSessionVisibleTabId } from '../web-session-focus-intent'
import { chooseTargetGroupId, buildHostGroupIdByTabId } from './tab-group-layout-tree'
import {
buildMirroredBrowserTabs,
@@ -35,7 +36,8 @@ export function prepareWebSessionTabsSnapshotBrowser(
mirroredTerminalIds,
removedTerminalIds,
reconcilesNonAgentTabs,
batchContext
batchContext,
navigationIntentTab
} = base
const targetGroupId = chooseTargetGroupId(state, snapshot)
const hostGroupIdByTabId = buildHostGroupIdByTabId(snapshot.tabGroups)
@@ -123,7 +125,8 @@ export function prepareWebSessionTabsSnapshotBrowser(
targetGroupId,
mirroredTerminalTabEntries.length + mirroredBrowserTabs.length,
now,
(fileId) => state.editorDrafts?.[fileId] !== undefined
(fileId) => state.editorDrafts?.[fileId] !== undefined,
navigationIntentTab?.id ?? resolveWebSessionVisibleTabId(state, worktreeId)
)
const mirroredAgentTabs = buildMirroredAgentTabs(
snapshot,
@@ -11,6 +11,7 @@ import type { ReadyEditorSurface, MirroredEditorTab } from './state'
import type { WebSessionExistingTabIndex } from '../web-session-existing-tab-index'
import { isReadyEditorTab } from './terminal-surfaces'
import { createMirroredEditorFileResolver } from './mirrored-editor-file-identity'
import { buildOwnedEditorFileId } from '../../store/slices/editor/file-ids/editor-file-ids'
export function buildTerminalUnifiedTab(
tab: TerminalTab,
@@ -100,6 +101,71 @@ export function buildEditorUnifiedTab(
}
}
// Older peers can echo one file under IDs minted by several environments.
function mintedEnvironmentSegment(tab: ReadyEditorSurface, worktreeId: string): string | null {
if (
tab.type !== 'markdown' ||
typeof tab.sourceFileId !== 'string' ||
!tab.sourceFileId ||
tab.sourceFileId === tab.sourceFilePath
) {
return null
}
if (tab.id === tab.filePath) {
return ''
}
const prefix = `editor:${encodeURIComponent(worktreeId)}:`
const suffix = `:${encodeURIComponent(tab.filePath)}`
if (
!tab.id.startsWith(prefix) ||
!tab.id.endsWith(suffix) ||
tab.id.length <= prefix.length + suffix.length
) {
return null
}
const segment = tab.id.slice(prefix.length, tab.id.length - suffix.length)
let runtimeKey: string
try {
runtimeKey = decodeURIComponent(segment)
} catch {
return null
}
return buildOwnedEditorFileId(tab.filePath, worktreeId, runtimeKey) === tab.id ? segment : null
}
function dedupeReadyEditorTabsBySource(
tabs: readonly ReadyEditorSurface[],
worktreeId: string,
preferredHostTabId: string | null
): ReadyEditorSurface[] {
const bucketOf = (tab: ReadyEditorSurface): string =>
JSON.stringify([
worktreeId,
tab.type === 'markdown' ? tab.mode : 'edit',
tab.filePath,
tab.type === 'markdown' ? tab.sourceFileId : null
])
const rank = (tab: ReadyEditorSurface): number =>
tab.id === preferredHostTabId ? 2 : tab.isActive ? 1 : 0
const winnerByBucket = new Map<string, { segment: string; rank: number }>()
for (const tab of tabs) {
const segment = mintedEnvironmentSegment(tab, worktreeId)
if (segment === null) {
continue
}
const bucket = bucketOf(tab)
const current = winnerByBucket.get(bucket)
const tabRank = rank(tab)
if (!current || tabRank > current.rank) {
winnerByBucket.set(bucket, { segment, rank: tabRank })
}
}
return tabs.filter((tab) => {
const segment = mintedEnvironmentSegment(tab, worktreeId)
return segment === null || segment === winnerByBucket.get(bucketOf(tab))?.segment
})
}
export function buildMirroredEditorTabs(
snapshot: RuntimeMobileSessionTabsResult,
environmentId: string,
@@ -109,7 +175,8 @@ export function buildMirroredEditorTabs(
fallbackGroupId: string,
sortOffset: number,
now: number,
hasLocalDraft: (fileId: string) => boolean
hasLocalDraft: (fileId: string) => boolean,
preferredHostTabId: string | null = null
): MirroredEditorTab[] {
const readyTabs = snapshot.tabs.filter(isReadyEditorTab)
if (readyTabs.length === 0) {
@@ -120,7 +187,12 @@ export function buildMirroredEditorTabs(
for (const tab of readyTabs) {
resolveFile(tab, existingTabIndex.getEditorUnifiedTabByHostId(tab.id))
}
return readyTabs.map((tab, index) => {
const distinctTabs = dedupeReadyEditorTabsBySource(
readyTabs,
snapshot.worktree,
preferredHostTabId
)
return distinctTabs.map((tab, index) => {
const identity = resolveFile(tab, existingTabIndex.getEditorUnifiedTabByHostId(tab.id))
const { fileId, existingFile, sourceFileId } = identity
const existingUnifiedTab = existingTabIndex.getEditorUnifiedTab(fileId, tab.id)
@@ -0,0 +1,71 @@
import { describe, expect, it, vi } from 'vitest'
import { buildHeadlessMobileSessionEditorTabs } from '../../src/main/runtime/mobile-session-editor-projection'
import type { WorkspaceSessionState } from '../../src/shared/workspace-session-state-types'
import { buildOwnedEditorFileId } from '../../src/renderer/src/store/slices/editor/file-ids/editor-file-ids'
import { applyWebSessionTabsSnapshot } from '../../src/renderer/src/runtime/web-session-tabs-sync/snapshot-api'
import {
ENV,
NOW,
WT,
makeSnapshot,
makeState
} from '../../src/renderer/src/runtime/web-session-tabs-sync-test-harness'
const host = vi.hoisted(() => ({ layoutOwnerIsMain: true }))
vi.mock('../../src/shared/layout-owner-switch', () => ({
get ORCA_LAYOUT_OWNER_IS_MAIN() {
return host.layoutOwnerIsMain
}
}))
describe('headless editor identity in an older desktop mirror', () => {
it('keeps two owners when the producer represents both source identities by the path', () => {
const filePath = '/repo/README.md'
const owners = [null, 'other-host']
const session: WorkspaceSessionState = {
activeRepoId: null,
activeWorktreeId: WT,
activeTabId: null,
tabsByWorktree: {},
terminalLayoutsByTabId: {},
openFilesByWorktree: {
[WT]: owners.map((owner) => ({
filePath,
relativePath: 'README.md',
worktreeId: WT,
language: 'markdown',
runtimeEnvironmentId: owner,
...(owner ? { dirtyDraftContent: 'remote draft' } : {})
}))
},
unifiedTabs: {
[WT]: owners.map((owner, index) => ({
id: buildOwnedEditorFileId(filePath, WT, owner),
entityId: buildOwnedEditorFileId(filePath, WT, owner),
groupId: 'host-group-1',
worktreeId: WT,
contentType: 'editor',
label: 'README.md',
customLabel: null,
color: null,
sortOrder: index,
createdAt: NOW,
isPreview: false
}))
}
}
const tabs = buildHeadlessMobileSessionEditorTabs(WT, session)
expect(tabs).toHaveLength(2)
expect(tabs.map((tab) => tab.id)).toEqual(
owners.map((owner) => buildOwnedEditorFileId(filePath, WT, owner))
)
expect(tabs.map((tab) => tab.isDirty)).toEqual([false, true])
host.layoutOwnerIsMain = false
const patch = applyWebSessionTabsSnapshot(makeState(), makeSnapshot(tabs), ENV, NOW)
expect(patch.unifiedTabsByWorktree?.[WT]?.map((tab) => tab.id)).toEqual(
tabs.map((tab) => tab.id)
)
})
})