mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
Write no false preview flag on a new or promoted tab (dark, layout-owner build only) (#30041)
Switch-on builds only; switch-off behaviour is unchanged. Main stores a tab's preview flag only while it is true. A non-preview editor open created its tab-bar entry with `isPreview: false`, and making a preview permanent by an edit, by "keep open" or by pinning wrote the same, so the window's next save changed that tab in main's model (the save-divergence check's `tab <id> changed: isPreview` after a CLI file open). New tabs now carry the flag only when true, and those writers drop it, by the rule E2-4 gave activation, now in one place (tab-preview-flag). Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
This commit is contained in:
@@ -10,6 +10,12 @@ import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
|
||||
import { applyLayoutCommand } from '../../../../shared/workspace-layout/workspace-layout-commands'
|
||||
import type { LayoutCommand } from '../../../../shared/workspace-layout/workspace-layout-command-types'
|
||||
import type { WorkspaceLayoutModel } from '../../../../shared/workspace-layout/workspace-layout-model'
|
||||
import { saveWorkspaceLayout } from '../../../../shared/workspace-layout/workspace-layout-save'
|
||||
import { asLoaded } from '../../../../shared/workspace-layout/workspace-layout-command.test-fixture'
|
||||
import { buildPersistedUnifiedTabSessionData } from '@/lib/workspace-session-unified-tabs'
|
||||
import { loadWorkspaceLayoutWithoutReport } from '../../../../shared/workspace-layout/workspace-layout-load'
|
||||
import { nameBasedLoadContext } from '../../../../shared/workspace-layout/workspace-layout-minted-ids'
|
||||
import type { WorkspaceSessionState } from '../../../../shared/workspace-session-state-types'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
layoutOwnerIsMain: true,
|
||||
@@ -123,6 +129,23 @@ function mainView() {
|
||||
}))
|
||||
}
|
||||
|
||||
/** Main's tabs by id as loaded from a save, as main's save-divergence check reads them. */
|
||||
function loadedTabs(session: WorkspaceSessionState) {
|
||||
const { layout } = loadWorkspaceLayoutWithoutReport(
|
||||
LOCAL_EXECUTION_HOST_ID,
|
||||
session,
|
||||
nameBasedLoadContext()
|
||||
)
|
||||
return Object.fromEntries((layout.workspaces[WT]?.tabs ?? []).map((tab) => [tab.id, tab]))
|
||||
}
|
||||
|
||||
/** Main's tabs, then the same after the window's tab-bar save lands on main's session. */
|
||||
function tabsBeforeAndAfterWindowSave() {
|
||||
const mains = saveWorkspaceLayout(asLoaded(main))
|
||||
const windows = { ...mains, ...buildPersistedUnifiedTabSessionData(store.getState()) }
|
||||
return [loadedTabs(mains), loadedTabs(windows)]
|
||||
}
|
||||
|
||||
function openFile(path: string, options?: { preview?: boolean; targetGroupId?: string }): string {
|
||||
return store
|
||||
.getState()
|
||||
@@ -298,6 +321,42 @@ describe('editor tabs in a layout-owner build', () => {
|
||||
).toBe(command!.type === 'openEditorTab' ? command.tabId : undefined)
|
||||
})
|
||||
|
||||
it('saves the tabs as main holds them, with no preview flag main does not store', () => {
|
||||
openFile('/a.ts')
|
||||
openFile('/b.ts', { targetGroupId: 'g2' })
|
||||
applySent()
|
||||
let [was, now] = tabsBeforeAndAfterWindowSave()
|
||||
expect(now).toStrictEqual(was)
|
||||
|
||||
const c = openFile('/c.ts', { preview: true })
|
||||
store.getState().markFileDirty(c, true)
|
||||
const d = openFile('/d.ts', { preview: true, targetGroupId: 'g2' })
|
||||
store.getState().makePreviewFilePermanent(d)
|
||||
applySent()
|
||||
;[was, now] = tabsBeforeAndAfterWindowSave()
|
||||
expect(now).toStrictEqual(was)
|
||||
})
|
||||
|
||||
it('pins a preview tab without writing a preview flag', () => {
|
||||
const a = openFile('/a.ts', { preview: true })
|
||||
store.getState().pinTab(tabIn('g1', a))
|
||||
|
||||
const [tab] = store.getState().unifiedTabsByWorktree[WT]!
|
||||
expect(tab?.isPinned).toBe(true)
|
||||
expect(tab).not.toHaveProperty('isPreview')
|
||||
})
|
||||
|
||||
it('keeps writing the preview flag as false with the switch off', () => {
|
||||
mocks.layoutOwnerIsMain = false
|
||||
const a = openFile('/a.ts')
|
||||
const b = openFile('/b.ts', { preview: true })
|
||||
store.getState().pinTab(tabIn('g1', b))
|
||||
|
||||
const tabs = store.getState().unifiedTabsByWorktree[WT]!
|
||||
expect(tabs.find((tab) => tab.entityId === a)).toHaveProperty('isPreview', false)
|
||||
expect(tabs.find((tab) => tab.entityId === b)).toHaveProperty('isPreview', false)
|
||||
})
|
||||
|
||||
it("sends an edit-mode file's record exactly as the window's session save keeps it", () => {
|
||||
const file = {
|
||||
id: 'f1',
|
||||
|
||||
@@ -14,6 +14,7 @@ import { buildEditorActiveResult } from '../tabs/editor-open-target-group'
|
||||
import { openWorkspaceEditorItem } from '../tabs/workspace-editor-item'
|
||||
import type { Tab } from '../../../../../../shared/tab-types'
|
||||
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../../../shared/layout-owner-switch'
|
||||
import { withPreviewEnded } from '../../tabs/tab-preview-flag'
|
||||
import {
|
||||
previewEditorTabsOf,
|
||||
recordPromotedEditorTabsInLayoutOwner
|
||||
@@ -188,7 +189,7 @@ export function createMarkdownPreviewActions(
|
||||
return tab
|
||||
}
|
||||
changed = true
|
||||
return { ...tab, isPreview: false }
|
||||
return withPreviewEnded(tab)
|
||||
})
|
||||
}
|
||||
return changed ? { openFiles, unifiedTabsByWorktree } : s
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { EditorSlice } from '../types/editor-slice'
|
||||
import { isEditorTabContentType } from '../tabs/editor-tab-content-type'
|
||||
import type { Tab } from '../../../../../../shared/tab-types'
|
||||
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../../../shared/layout-owner-switch'
|
||||
import { withPreviewEnded } from '../../tabs/tab-preview-flag'
|
||||
import {
|
||||
previewEditorTabsOf,
|
||||
recordPromotedEditorTabsInLayoutOwner
|
||||
@@ -97,7 +98,7 @@ export function createOpenFileMutations(
|
||||
worktreeId,
|
||||
tabs.map((tab) =>
|
||||
tab.entityId === fileId && isEditorTabContentType(tab.contentType)
|
||||
? { ...tab, isPreview: false }
|
||||
? withPreviewEnded(tab)
|
||||
: tab
|
||||
)
|
||||
])
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Switch point: main stores a tab's preview flag only while it is true, so a layout-owner build
|
||||
// writes no `isPreview: false` either; a save carrying one would change the tab in main's model.
|
||||
|
||||
import type { Tab } from '../../../../../shared/tab-types'
|
||||
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../../shared/layout-owner-switch'
|
||||
|
||||
/** A new tab's preview flag. */
|
||||
export function createdTabPreviewFlag(isPreview: boolean | undefined): Pick<Tab, 'isPreview'> {
|
||||
if (!ORCA_LAYOUT_OWNER_IS_MAIN) {
|
||||
return { isPreview }
|
||||
}
|
||||
return isPreview ? { isPreview: true } : {}
|
||||
}
|
||||
|
||||
/** The tab with its preview ended. */
|
||||
export function withPreviewEnded(tab: Tab): Tab {
|
||||
if (!ORCA_LAYOUT_OWNER_IS_MAIN) {
|
||||
return { ...tab, isPreview: false }
|
||||
}
|
||||
const { isPreview: _ended, ...rest } = tab
|
||||
return rest
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { resolveUnifiedTabCreatePlacement } from './tabs-create-placement'
|
||||
import { emptySplitGroup, insertSplitGroup } from './tabs-group-actions'
|
||||
import { folderWorkspaceToWorktree } from '../../../../../shared/folder-workspace-worktree'
|
||||
import { createdTabPreviewFlag } from './tab-preview-flag'
|
||||
|
||||
export function createTabsCreateActions(
|
||||
set: TabsSliceSet,
|
||||
@@ -115,7 +116,7 @@ export function createTabsCreateActions(
|
||||
createdAt,
|
||||
// Why: creating an active tab is a focus event; Cmd+J recency reads lastFocusedAt.
|
||||
...(recordsFocus ? { lastFocusedAt: createdAt } : {}),
|
||||
isPreview: init?.isPreview,
|
||||
...createdTabPreviewFlag(init?.isPreview),
|
||||
isPinned: init?.isPinned
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ export function createTabsCreateActions(
|
||||
createdAt,
|
||||
// Why: creating an active tab is a focus event; Cmd+J recency reads lastFocusedAt.
|
||||
...(shouldActivate ? { lastFocusedAt: createdAt } : {}),
|
||||
isPreview: init?.isPreview,
|
||||
...createdTabPreviewFlag(init?.isPreview),
|
||||
isPinned: init?.isPinned
|
||||
}
|
||||
const newGroup: TabGroup = {
|
||||
|
||||
@@ -8,17 +8,14 @@ import {
|
||||
import type { Tab } from '../../../../../shared/tab-types'
|
||||
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../../shared/layout-owner-switch'
|
||||
import { recordPromotedEditorTabsInLayoutOwner } from '../editor/tabs/editor-tab-layout-owner'
|
||||
import { withPreviewEnded } from './tab-preview-flag'
|
||||
|
||||
/** Activation ends a preview. Switch point: main stores no `isPreview: false`, so neither does this. */
|
||||
/** Activation ends a preview unless it preserves it. */
|
||||
function withPreviewOnActivation(tab: Tab, preservePreview: boolean): Tab {
|
||||
if (!ORCA_LAYOUT_OWNER_IS_MAIN) {
|
||||
return { ...tab, isPreview: preservePreview ? tab.isPreview : false }
|
||||
}
|
||||
if (preservePreview) {
|
||||
return tab
|
||||
}
|
||||
const { isPreview: _ended, ...rest } = tab
|
||||
return rest
|
||||
return preservePreview ? tab : withPreviewEnded(tab)
|
||||
}
|
||||
|
||||
export function createTabsFocusActions(
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from './tabs-host-mirroring'
|
||||
import { sendTabChangeToLayoutOwner } from '../../terminals/tab-change-through-layout-owner'
|
||||
import { recordTabOrderInLayoutOwner } from './tab-order-layout-owner'
|
||||
import { withPreviewEnded } from './tab-preview-flag'
|
||||
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../../shared/layout-owner-switch'
|
||||
|
||||
function oppositeViewMode(mode: 'terminal' | 'chat'): 'terminal' | 'chat' {
|
||||
@@ -216,7 +217,7 @@ export function createTabsLabelActions(
|
||||
}
|
||||
const { tab, worktreeId } = found
|
||||
const tabs = (state.unifiedTabsByWorktree[worktreeId] ?? []).map((candidate) =>
|
||||
candidate.id === tabId ? { ...candidate, isPinned: true, isPreview: false } : candidate
|
||||
candidate.id === tabId ? withPreviewEnded({ ...candidate, isPinned: true }) : candidate
|
||||
)
|
||||
const groups = state.groupsByWorktree[worktreeId] ?? []
|
||||
const group = groups.find((candidate) => candidate.id === tab.groupId)
|
||||
|
||||
Reference in New Issue
Block a user