Files
orca/src/shared/session-tab-placement.ts
T
Brennan BensonandMerge Sim 5287c5cdbc fix(mobile): stop a created tab from jumping when the host snapshot lands (#20069)
* fix(mobile): stop a created tab from jumping when the host snapshot lands

Creating a tab from the mobile session strip painted the new tab at the end
of the strip and then visibly jumped it to a different slot a beat later.

The client asked the host to insert the tab after the active tab, but then
predicted a different placement for its own optimistic paint:

    afterTabId: activeSessionTabId ?? undefined   // host: splice(insertAfter + 1)
    ...
    return [...prev, { ...created, isActive: true }]   // client: append

Two independent placements that disagree, so the optimistic frame is wrong by
construction and the tab snaps to its real slot on the next published snapshot.

The disagreement dates to 57a70d2ac0 ("Fix mobile session tab authority"),
which introduced afterTabId and left the append in place. Before that the
client used terminal.create with no anchor, so both sides appended and agreed.

Rather than teach the client to re-derive the host's rule, both sides now call
one shared placeCreatedSessionTab, and the client captures a single afterTabId
for the request and the paint so they cannot drift apart again.

The host change is a pure refactor onto the shared helper; the 1260-test
runtime characterization suite is unchanged. The mobile route-parity hash pin
moves once because handleCreateTerminal's body changed - it is the only one of
the 12 extracted functions that differs.

* fix(mobile): keep split terminal placement stable

* fix(mobile): negotiate split tab placement

* fix(e2e): run worktree first-paint probe on a mapped window

* test(mobile): type tab placement updater

* test(mobile): model current host in create recordings

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-16 22:42:40 -07:00

30 lines
949 B
TypeScript

export type SessionTabPlacementOptions = {
afterParentGroup?: boolean
}
/** Places a created tab after the anchor, or after its parent group when enabled. */
export function placeCreatedSessionTab<T extends { id: string; parentTabId?: string }>(
tabs: readonly T[],
created: T,
afterTabId: string | null | undefined,
options: SessionTabPlacementOptions = {}
): T[] {
const next = tabs.filter((tab) => tab.id !== created.id)
const anchor = afterTabId ? next.findIndex((tab) => tab.id === afterTabId) : -1
if (anchor < 0) {
next.push(created)
return next
}
let insertAfter = anchor
const anchorParentTabId = next[anchor].parentTabId
if (options.afterParentGroup && anchorParentTabId) {
for (let index = anchor + 1; index < next.length; index += 1) {
if (next[index].parentTabId === anchorParentTabId) {
insertAfter = index
}
}
}
next.splice(insertAfter + 1, 0, created)
return next
}