Omit unsupported combined diff tabs from mobile session snapshots (#6520)

Combined diff tabs (such as branch, commit, or uncommitted changes)
use display labels as relative paths and require the desktop-only
combined renderer. Excluding these tabs from the mobile snapshots
prevents mobile clients from incorrectly attempting to read them
via files.read.
This commit is contained in:
Jinjing
2026-06-27 15:56:21 -07:00
committed by GitHub
parent 32542a8e93
commit 14f0096077
2 changed files with 107 additions and 5 deletions
@@ -725,6 +725,85 @@ describe('buildMobileSessionTabSnapshots', () => {
expect(tab).not.toHaveProperty('diffSource')
})
it.each([
['combined-branch', 'wt-1::all-diffs::branch::main', 'Branch Changes (main)'],
['combined-commit', 'wt-1::all-diffs::commit::abc123', 'Commit abc123'],
['combined-all', 'wt-1::all-diffs::uncommitted', 'All Changes'],
['combined-uncommitted', 'wt-1::all-diffs::uncommitted::unstaged', 'Changes']
] as const)('omits unsupported %s diff tabs from mobile file snapshots', (source, id, label) => {
const state = makeState({
browserTabsByWorktree: {},
tabBarOrderByWorktree: { 'wt-1': [id] },
activeFileId: id,
activeFileIdByWorktree: { 'wt-1': id },
activeTabType: 'editor',
activeTabTypeByWorktree: { 'wt-1': 'editor' },
openFiles: [
{
id,
filePath: '/repo',
relativePath: label,
worktreeId: 'wt-1',
language: 'plaintext',
mode: 'diff',
diffSource: source,
isDirty: false
}
]
})
const snapshot = buildMobileSessionTabSnapshots(state)[0]
expect(snapshot?.tabs).toEqual([])
expect(snapshot?.activeTabId).toBeNull()
expect(snapshot?.activeTabType).toBeNull()
})
it('does not recover unsupported combined diff tabs through split-group fallback', () => {
const combinedId = 'wt-1::all-diffs::branch::main'
const state = makeState({
activeGroupIdByWorktree: { 'wt-1': 'group-right' },
groupsByWorktree: {
'wt-1': [
{
id: 'group-right',
activeTabId: 'combined-tab-right',
tabOrder: [],
recentTabIds: []
}
]
} as unknown as AppState['groupsByWorktree'],
unifiedTabsByWorktree: {
'wt-1': [
{
id: 'combined-tab-right',
groupId: 'group-right',
contentType: 'diff',
entityId: combinedId,
title: 'Branch Changes (main)'
}
]
} as unknown as AppState['unifiedTabsByWorktree'],
openFiles: [
{
id: combinedId,
filePath: '/repo',
relativePath: 'Branch Changes (main)',
worktreeId: 'wt-1',
language: 'plaintext',
mode: 'diff',
diffSource: 'combined-branch',
isDirty: false
}
]
})
const snapshot = buildMobileSessionTabSnapshots(state)[0]
expect(snapshot?.tabs).toEqual([])
expect(snapshot?.tabGroups).toBeUndefined()
})
it('publishes a missing non-markdown editor with its unified tab id and split group', () => {
const fileId = '/repo/src/app.ts'
const state = makeState({
+28 -5
View File
@@ -700,7 +700,11 @@ export function buildMobileSessionTabSnapshots(
const unifiedTabByIdForWorktree = new Map(
(state.unifiedTabsByWorktree[worktreeId] ?? []).map((tab) => [tab.id, tab])
)
const editorIds = openFileIndexes.idsByWorktree.get(worktreeId) ?? []
const openFilesForWorktree = openFileIndexes.byWorktreeAndId.get(worktreeId)
const editorIds = (openFileIndexes.idsByWorktree.get(worktreeId) ?? []).filter((fileId) => {
const file = openFilesForWorktree?.get(fileId)
return file ? isMobilePublishableOpenFile(file) : false
})
const publishableTerminalIds = [...terminalTabByIdForWorktree.values()]
.filter((terminal) => !isWebOnlyMirroredTerminalTab(state, terminal))
.map((terminal) => terminal.id)
@@ -732,8 +736,8 @@ export function buildMobileSessionTabSnapshots(
)
)
} else if (item.type === 'editor') {
const file = openFileIndexes.byWorktreeAndId.get(worktreeId)?.get(item.id)
if (!file) {
const file = openFilesForWorktree?.get(item.id)
if (!file || !isMobilePublishableOpenFile(file)) {
continue
}
const markdown = buildMobileMarkdownTab(
@@ -774,7 +778,6 @@ export function buildMobileSessionTabSnapshots(
// Why: split-group projection can miss plain editor files during hydration.
// Publish the missing file so paired mobile/web clients still mirror it.
const fallbackEditorTabs: FallbackEditorTabTarget[] = []
const openFilesForWorktree = openFileIndexes.byWorktreeAndId.get(worktreeId)
if (openFilesForWorktree) {
const unifiedEditorTabs = getEditorUnifiedTabsForWorktree(state, worktreeId)
const unifiedEditorFileIds = new Set(unifiedEditorTabs.map((tab) => tab.entityId))
@@ -783,7 +786,7 @@ export function buildMobileSessionTabSnapshots(
continue
}
const file = openFilesForWorktree.get(unifiedTab.entityId)
if (!file) {
if (!file || !isMobilePublishableOpenFile(file)) {
continue
}
const markdown = buildMobileMarkdownTab(
@@ -802,6 +805,9 @@ export function buildMobileSessionTabSnapshots(
emittedEditorTabIds.add(unifiedTab.id)
}
for (const file of openFilesForWorktree.values()) {
if (!isMobilePublishableOpenFile(file)) {
continue
}
if (emittedEditorFileIds.has(file.id)) {
continue
}
@@ -1435,6 +1441,23 @@ function isMobileFileDiffSource(
return diffSource === 'staged' || diffSource === 'unstaged'
}
function isMobileUnsupportedCombinedDiffSource(
diffSource: AppState['openFiles'][number]['diffSource']
): boolean {
return (
diffSource === 'combined-all' ||
diffSource === 'combined-uncommitted' ||
diffSource === 'combined-branch' ||
diffSource === 'combined-commit'
)
}
function isMobilePublishableOpenFile(file: AppState['openFiles'][number]): boolean {
// Why: combined diff tabs use display labels as relative paths and require
// the desktop combined renderer; mobile would otherwise try files.read.
return !isMobileUnsupportedCombinedDiffSource(file.diffSource)
}
function buildMobileBrowserTab(
state: AppState,
workspace: NonNullable<AppState['browserTabsByWorktree'][string]>[number],