mirror of
https://github.com/stablyai/orca.git
synced 2026-09-27 16:02:35 +00:00
fix(mobile): keep split terminal placement stable
This commit is contained in:
@@ -305,6 +305,20 @@ describe('optimistic placement of a created tab', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('paints after the active split parent, matching headed host placement', async () => {
|
||||
const scope = createScope(clientReturning(terminalCreateResponse()))
|
||||
scope.activeSessionTabId = 'existing-tab::left'
|
||||
await createLegacyTerminal(scope)
|
||||
|
||||
expect(
|
||||
tabIdsAfterCreate(scope, [
|
||||
{ id: 'existing-tab::left', parentTabId: 'existing-tab' },
|
||||
{ id: 'existing-tab::right', parentTabId: 'existing-tab' },
|
||||
{ id: 'trailing-tab' }
|
||||
])
|
||||
).toEqual(['existing-tab::left', 'existing-tab::right', 'terminal-tab-1', 'trailing-tab'])
|
||||
})
|
||||
|
||||
it('sends the same anchor it paints with', async () => {
|
||||
const scope = createScope(clientReturning(terminalCreateResponse()))
|
||||
await createLegacyTerminal(scope)
|
||||
|
||||
@@ -303,6 +303,64 @@ describe('OrcaRuntimeService', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('places a runtime-owned terminal after every leaf in the anchored split parent', async () => {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
runtime.setPtyController({
|
||||
spawn: vi.fn().mockResolvedValue({ id: 'pty-created-after-split' }),
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess: async () => null
|
||||
})
|
||||
runtime.syncWindowGraph(0, {
|
||||
tabs: [],
|
||||
leaves: [],
|
||||
mobileSessionTabs: [
|
||||
{
|
||||
worktree: TEST_WORKTREE_ID,
|
||||
publicationEpoch: 'headless:split-placement',
|
||||
snapshotVersion: 1,
|
||||
activeGroupId: 'group-1',
|
||||
activeTabId: 'split::left',
|
||||
activeTabType: 'terminal',
|
||||
tabs: [
|
||||
{
|
||||
type: 'terminal',
|
||||
id: 'split::left',
|
||||
parentTabId: 'split',
|
||||
leafId: 'left',
|
||||
title: 'Split',
|
||||
isActive: true
|
||||
},
|
||||
{
|
||||
type: 'terminal',
|
||||
id: 'split::right',
|
||||
parentTabId: 'split',
|
||||
leafId: 'right',
|
||||
title: 'Split',
|
||||
isActive: false
|
||||
},
|
||||
{
|
||||
type: 'terminal',
|
||||
id: 'trailing::leaf',
|
||||
parentTabId: 'trailing',
|
||||
leafId: 'leaf',
|
||||
title: 'Trailing',
|
||||
isActive: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const created = await runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, {
|
||||
afterTabId: 'split::left'
|
||||
})
|
||||
|
||||
expect(
|
||||
(await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)).tabs.map((tab) => tab.id)
|
||||
).toEqual(['split::left', 'split::right', created.tab.id, 'trailing::leaf'])
|
||||
})
|
||||
|
||||
it('leases renderer publication for a paired create and preserves host-owned inventory', async () => {
|
||||
const leafId = '91919191-9191-4919-8919-919191919191'
|
||||
const spawn = vi.fn()
|
||||
|
||||
@@ -10,6 +10,20 @@ describe('placeCreatedSessionTab', () => {
|
||||
).toEqual(['a', 'new', 'b', 'c'])
|
||||
})
|
||||
|
||||
it('inserts after all leaves of a split parent', () => {
|
||||
expect(
|
||||
placeCreatedSessionTab(
|
||||
[
|
||||
{ id: 'split::left', parentTabId: 'split' },
|
||||
{ id: 'split::right', parentTabId: 'split' },
|
||||
{ id: 'trailing' }
|
||||
],
|
||||
{ id: 'new' },
|
||||
'split::left'
|
||||
).map((tab) => tab.id)
|
||||
).toEqual(['split::left', 'split::right', 'new', 'trailing'])
|
||||
})
|
||||
|
||||
it('appends when the anchor is the last tab', () => {
|
||||
expect(
|
||||
placeCreatedSessionTab([{ id: 'a' }, { id: 'b' }], created, 'b').map((t) => t.id)
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
/**
|
||||
* Where a newly created session tab lands in a tab list.
|
||||
*
|
||||
* Shared because a client that optimistically paints a created tab and the host that publishes
|
||||
* the authoritative snapshot must agree. When they disagreed — the client appending while the
|
||||
* host spliced after `afterTabId` — the new tab painted at the end and then visibly jumped to its
|
||||
* real slot as soon as the host frame landed.
|
||||
*/
|
||||
export function placeCreatedSessionTab<T extends { id: string }>(
|
||||
/** Places a created tab after the anchor's top-level terminal group, or appends when unanchored. */
|
||||
export function placeCreatedSessionTab<T extends { id: string; parentTabId?: string }>(
|
||||
tabs: readonly T[],
|
||||
created: T,
|
||||
afterTabId: string | null | undefined
|
||||
@@ -17,6 +10,16 @@ export function placeCreatedSessionTab<T extends { id: string }>(
|
||||
next.push(created)
|
||||
return next
|
||||
}
|
||||
next.splice(anchor + 1, 0, created)
|
||||
let insertAfter = anchor
|
||||
const anchorParentTabId = next[anchor].parentTabId
|
||||
if (anchorParentTabId) {
|
||||
while (
|
||||
insertAfter + 1 < next.length &&
|
||||
next[insertAfter + 1].parentTabId === anchorParentTabId
|
||||
) {
|
||||
insertAfter += 1
|
||||
}
|
||||
}
|
||||
next.splice(insertAfter + 1, 0, created)
|
||||
return next
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user