mirror of
https://github.com/stablyai/orca.git
synced 2026-10-29 16:17:51 +00:00
fix(ssh): reconcile the tab model after a remote snapshot rebinds its panes
Closes the second defect with the same user-visible symptom as the reported one, and unlike the partition bug it needs no partition at all — a fresh client, a reset profile, or tabs created from another client will do. A remote snapshot is terminal-only: RemoteWorkspaceSession has no unifiedTabs or tabGroups field to carry, so replacing tabsByWorktree leaves tabs that no group's tabOrder names. The tab bar renders from tabOrder, so those tabs exist and are invisible. Placement is the whole fix. I first put the reconcile beside the hydrate and concluded from a red convergence test that it caused a repair loop. That was wrong: it was DELETING the tabs, and the test was failing at its precondition, which I never read. Hydration nulls ptyId, so before reconnect rebinds the panes the records own no PTY, the orphan sweep calls them dead and wipes them. Running after finalizeHydratedTerminals means reconnect has already rebound them. No wire change. Teaching the snapshot to carry the group model remains the durable answer for preserving real order and grouping, but it is capability-negotiated and old hosts would not send it, so this is needed anyway. 720 renderer-hook tests pass, including 'converges the active-terminal repair instead of re-running it forever' with zero churn and zero extra passes.
This commit is contained in:
@@ -130,22 +130,6 @@ export async function applyDirectSshRemoteWorkspaceSnapshot({
|
||||
replaceWorkspaceKeys
|
||||
})
|
||||
currentStore.hydrateTabsSession(merged, { replaceWorkspaceKeys })
|
||||
// KNOWN DEFECT, deliberately not repaired here (STA-3077 follow-up).
|
||||
//
|
||||
// A remote snapshot is terminal-only: `RemoteWorkspaceSession` has no `unifiedTabs` or
|
||||
// `tabGroups` field to carry, so replacing `tabsByWorktree` leaves the tab model inconsistent
|
||||
// in BOTH directions — remote tabs that no group's `tabOrder` names, and group entries naming
|
||||
// a local tab whose record the replace removed. The tab bar renders from `tabOrder`, so those
|
||||
// tabs exist and are invisible. Same user-visible symptom as the partition-upgrade bug, but
|
||||
// reachable with no partition at all: a fresh client, a reset profile, or tabs created from
|
||||
// another client. The load-time partition hoist does NOT cover it.
|
||||
//
|
||||
// `reconcileWorktreeTabModel` already adopts reconnectable records and prunes unrenderable
|
||||
// ones, and calling it here is the obvious fix — but doing so breaks
|
||||
// "converges the active-terminal repair instead of re-running it forever", i.e. it reopens a
|
||||
// repair loop. Any fix has to converge; the durable answer is probably to teach the snapshot
|
||||
// to carry the group model, which is a capability-negotiated wire change.
|
||||
//
|
||||
// Why: direct SSH snapshots project terminal state only; global editor/browser hydration would reset unrelated hosts.
|
||||
currentStore.markRemoteWorkspaceHydrated(authority.targetId)
|
||||
currentStore.setRemoteWorkspaceSyncStatus(authority.targetId, {
|
||||
@@ -179,6 +163,15 @@ export async function applyDirectSshRemoteWorkspaceSnapshot({
|
||||
}
|
||||
if (isArrivalCurrent(authority.targetId, arrival) && isPreparationTokenCurrent(token)) {
|
||||
finalizeHydratedTerminals(authority)
|
||||
// Why here and not beside the hydrate above: a snapshot is terminal-only —
|
||||
// `RemoteWorkspaceSession` has no `unifiedTabs`/`tabGroups` to carry — so replacing
|
||||
// `tabsByWorktree` leaves tabs that no group's `tabOrder` names, and the tab bar renders
|
||||
// from `tabOrder`. Those tabs exist and are invisible. Reconciling earlier does NOT work:
|
||||
// hydration nulls `ptyId`, so before reconnect rebinds the panes the records own no PTY,
|
||||
// the orphan sweep classifies them as dead and DELETES them rather than adopting them.
|
||||
for (const worktreeId of replaceWorkspaceKeys) {
|
||||
store.getState().reconcileWorktreeTabModel(worktreeId)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
snapshotWriteSuppressUntil = Date.now() + REMOTE_WORKSPACE_SNAPSHOT_WRITE_SUPPRESS_MS
|
||||
|
||||
@@ -117,6 +117,10 @@ function appState(overrides: Record<string, unknown> = {}): AppState {
|
||||
defaultTerminalTabsAppliedByWorktreeId: {},
|
||||
hydrateWorkspaceSession: vi.fn(),
|
||||
hydrateTabsSession: vi.fn(),
|
||||
reconcileWorktreeTabModel: vi.fn(() => ({
|
||||
renderableTabCount: 0,
|
||||
activeRenderableTabId: null
|
||||
})),
|
||||
hydrateEditorSession: vi.fn(),
|
||||
hydrateBrowserSession: vi.fn(),
|
||||
markRemoteWorkspaceHydrated: vi.fn(),
|
||||
|
||||
Reference in New Issue
Block a user