mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
* fix: preserve renderer browser publication during client-hosted page updates * refactor: drop the now-dead publicationEpoch selection argument applyBrowserSessionTabSelection took a publicationEpoch and wrote it over the epoch the spread snapshot already carried. Its only production caller now passes snapshot.publicationEpoch, so the parameter is a no-op whose only remaining power is to reintroduce the epoch rotation this PR fixes. Remove it, and collapse the repeated prototype-cast boilerplate in the new reconciliation test into one helper. No behavior change. * fix: keep reconcile from publishing a browser row twice The retention filter partitioned existing rows by placement kind, so its disjointness from the live build relied on a non-local invariant: that the page registry only ever stores client placements and that server tabs are empty while no offscreen backend exists. Drop ids the live build already published instead, so a duplicate row is impossible by construction rather than by coincidence. * fix: stop the browser reconcile republishing on a pure reordering headlessBrowserTabsUnchanged compares by array index, so rebuilding the live list renderer-first read an interleaved snapshot as changed and republished with a bumped version and rebuilt tab groups for no semantic change - the same churn this branch exists to remove. Key the live set by id and emit it in the order the snapshot already had. Keying also makes uniqueness unconditional rather than resting on the page registry only ever storing client placements.
124 lines
4.3 KiB
TypeScript
124 lines
4.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import type { RuntimeMobileSessionTabsSnapshot } from '../../shared/runtime-types'
|
|
import { applyBrowserSessionTabSelection } from './browser-session-tab-selection-snapshot'
|
|
|
|
function makeSnapshot(): RuntimeMobileSessionTabsSnapshot {
|
|
return {
|
|
worktree: 'wt-1',
|
|
publicationEpoch: 'headless:before',
|
|
snapshotVersion: 4,
|
|
activeGroupId: 'group-left',
|
|
activeTabId: 'terminal-tab',
|
|
activeTabType: 'terminal',
|
|
tabs: [
|
|
{
|
|
type: 'terminal',
|
|
id: 'terminal-tab',
|
|
parentTabId: 'terminal-tab',
|
|
leafId: 'leaf-1',
|
|
title: 'Terminal',
|
|
isActive: true
|
|
},
|
|
{
|
|
type: 'browser',
|
|
id: 'page-new',
|
|
browserWorkspaceId: 'page-new',
|
|
browserPageId: 'page-new',
|
|
title: 'New',
|
|
url: 'about:blank',
|
|
loading: false,
|
|
canGoBack: false,
|
|
canGoForward: false,
|
|
isActive: false
|
|
}
|
|
] as RuntimeMobileSessionTabsSnapshot['tabs'],
|
|
tabGroups: [
|
|
{ id: 'group-left', tabOrder: ['terminal-tab', 'page-new'], activeTabId: 'terminal-tab' },
|
|
{ id: 'group-right', tabOrder: [], activeTabId: null }
|
|
] as RuntimeMobileSessionTabsSnapshot['tabGroups']
|
|
}
|
|
}
|
|
|
|
function select(overrides: { focusesHost: boolean; targetGroupId?: string }) {
|
|
return applyBrowserSessionTabSelection({
|
|
snapshot: makeSnapshot(),
|
|
tabId: 'page-new',
|
|
focusesHost: overrides.focusesHost,
|
|
...(overrides.targetGroupId ? { targetGroupId: overrides.targetGroupId } : {})
|
|
})
|
|
}
|
|
|
|
describe('applyBrowserSessionTabSelection', () => {
|
|
it('moves the shared active tab when the request reaches the host', () => {
|
|
const { snapshot } = select({ focusesHost: true })
|
|
|
|
expect(snapshot.activeTabId).toBe('page-new')
|
|
expect(snapshot.activeTabType).toBe('browser')
|
|
expect(snapshot.tabs.find((tab) => tab.id === 'page-new')?.isActive).toBe(true)
|
|
})
|
|
|
|
it('leaves every shared selection alone for a caller-addressed create', () => {
|
|
const before = makeSnapshot()
|
|
const { snapshot } = select({ focusesHost: false })
|
|
|
|
expect(snapshot.activeTabId).toBe(before.activeTabId)
|
|
expect(snapshot.activeTabType).toBe(before.activeTabType)
|
|
expect(snapshot.activeGroupId).toBe(before.activeGroupId)
|
|
expect(snapshot.tabs.find((tab) => tab.id === 'page-new')?.isActive).toBe(false)
|
|
expect(snapshot.tabGroups?.find((group) => group.id === 'group-left')?.activeTabId).toBe(
|
|
'terminal-tab'
|
|
)
|
|
})
|
|
|
|
it('places the tab in the requested group whether or not the host follows', () => {
|
|
for (const focusesHost of [true, false]) {
|
|
const { snapshot, placedInTargetGroup } = select({
|
|
focusesHost,
|
|
targetGroupId: 'group-right'
|
|
})
|
|
|
|
expect(placedInTargetGroup).toBe(true)
|
|
expect(snapshot.tabGroups?.find((group) => group.id === 'group-right')?.tabOrder).toEqual([
|
|
'page-new'
|
|
])
|
|
expect(snapshot.tabGroups?.find((group) => group.id === 'group-left')?.tabOrder).toEqual([
|
|
'terminal-tab'
|
|
])
|
|
}
|
|
})
|
|
|
|
it('claims the target group only for a host-following create', () => {
|
|
expect(select({ focusesHost: true, targetGroupId: 'group-right' }).snapshot).toMatchObject({
|
|
activeGroupId: 'group-right',
|
|
tabGroups: expect.arrayContaining([
|
|
expect.objectContaining({ id: 'group-right', activeTabId: 'page-new' })
|
|
])
|
|
})
|
|
expect(select({ focusesHost: false, targetGroupId: 'group-right' }).snapshot).toMatchObject({
|
|
activeGroupId: 'group-left',
|
|
tabGroups: expect.arrayContaining([
|
|
expect.objectContaining({ id: 'group-right', activeTabId: null })
|
|
])
|
|
})
|
|
})
|
|
|
|
it('reports an unknown group as unplaced so nothing is persisted for it', () => {
|
|
const { placedInTargetGroup, snapshot } = select({
|
|
focusesHost: true,
|
|
targetGroupId: 'group-missing'
|
|
})
|
|
|
|
expect(placedInTargetGroup).toBe(false)
|
|
expect(snapshot.activeGroupId).toBe('group-left')
|
|
})
|
|
|
|
// Rotating the epoch here retires the renderer's own publication client-side.
|
|
it('keeps the publication epoch and advances the version either way', () => {
|
|
for (const focusesHost of [true, false]) {
|
|
const { snapshot } = select({ focusesHost })
|
|
expect(snapshot.publicationEpoch).toBe('headless:before')
|
|
expect(snapshot.snapshotVersion).toBe(5)
|
|
}
|
|
})
|
|
})
|