Repository navigation
Conversation
… into topo-c3-mirror-apply
Core step C3 (plan B2-4). After hydration the window subscribes to main's topology push, pulls every slice, and applies them before reconnect. The apply replaces only main-owned topology; presentation, pendingActivationSpawn, the live ptyId and ptyIdsByTabId stay. Unchanged rows and layouts keep their identity and an apply never echoes back as a session save. Flips the STA-9417 gate tests.
5 tasks done
…-mirror-apply # Conflicts: # src/renderer/src/lib/setup-split-activation-single-owner.test.ts
…e in main's layout Waking a slept split after a relaunch reattaches each pane to a process the sleep stopped. The host answers absent, and main retired the pane's surface (exit retirement and the persisted owner retirement) before the fresh spawn re-minted it: the tab was rebuilt from main's copy with the split reordered, and with the wake hint gone the agent resumed into an extra tab. Main's copy did this before the mirror too; the window's saves hid it. The absence is now a 'replaced' stop: the fresh spawn takes the pane, so main keeps the pane and its binding until that spawn's bind swaps it, and only the tab row lets go of the gone process, as the window's hydration does.
… store call The stable-owner absent path called retirePtyBinding through an optional method only because some test stores lacked it; those stores now supply it. The keeps-pane fixture builds a checked WorkspaceSessionState instead of a cast.
10 of 12 tasks
Contributor
Author
|
Closing: the layout rework is being redesigned so the runtime holds the only copy of the layout and every window/client is a view. This branch is kept for reference. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
Main keeps the saved copy of every worktree's terminal tabs and panes, and since C2 it announces that copy whenever it changes. With this PR the window starts from main's copy and keeps following it. Before, the window only read the saved file at startup, and after that it kept its own copy.
This is core step C3 of 7 in the terminal layout refactor. The core steps are reviewed as separate stacked PRs and merged together as one unit, so
mainnever gets a half-finished version. This PR is stacked on C2 (#26279). C1 (#26151) is a sibling on another base.What Changed
What users will see once the whole core lands: the window always shows main's terminal layout. A split or tab that main creates (setup script split, CLI split or create, a move) shows up in the window even if the separate "reveal" message never arrives. That fixes STA-9417: a worktree's setup terminal showing up in two tabs, one of them frozen, after the first time you open the worktree.
What C3 alone does (it is not a complete user state; C4 to C7 finish it, see the parity notes):
Mirror apply. A new store action,
applyTerminalTopologySlice, takes one worktree's layout from main and replaces only what main owns:launchAgent,defaultTitle,shellOverride,startupCwd,forceHostRuntime,quickCommandLabel,createdAt);Everything the window owns stays the same: title, custom title, color, pin, order, view mode,
generation,agentLaunchPane,pendingActivationSpawn, active/expanded pane, buffers, andptyIdsByTabId(which terminals are running). An existing tab'sptyIdalso stays, because in the window it means "the terminal this tab is attached to right now", and it is null at startup until reconnect. A tab main created seeds itsptyIdfrom main, the same way a revealed tab does.Tabs come and go with main. A new tab is added to the active tab group without being focused. A removed tab is taken out of its tab group, matched by
entityIdorid. Editor and browser tabs are not touched. Tabs aruntime:server publishes under the same worktree id are left alone.Unchanged means untouched. Rows and layouts that didn't change keep the same object, so an identical update changes nothing but the sequence number, and React and the pane reconciler don't re-run.
No echo. The apply writes the per-worktree
publishSeqin the same store update. The session writer treats any update that changes that map as main's own state and doesn't save it back. A later local edit still saves normally.Order. Main's
publishSeqdecides which update is newer: an update older than the last one applied is ignored.Startup. Right after the window hydrates the saved session, it subscribes to the push, then pulls every layout and applies it, and only then reconnects terminals. Doing it after hydration keeps the older startup read from overwriting a newer update. If the pull fails, the window logs it and keeps following pushes. The startup abort stops the subscription.
Types. Preload now declares the pull, the push, and the optional
publishSeqon thepty:spawn,pty:moveLeafToNewTaband close-surface replies (C4 to C6 read it). Web clients get empty no-ops, because they follow their host's session tabs instead.To stay under the file length limit,
listRuntimeSessionHostIdsForStartupmoved, unchanged, intostartup/startup-runtime-session-hosts.ts.Why
STA-9417 happens because the window builds terminal layout from whatever messages reach it, and when one is missed, the next sweep guesses wrong. The fix is one source of truth: main owns the layout and the window shows it. Main's projection and push already exist (B2-1, C2). This PR adds the reader with as little new state as possible:
terminalTopologySeqByWorktree), which does two jobs: it orders updates, and it tells the session writer that an update came from main;ensureGroup,pickNextActiveTab,sanitizeRecentTabIds,dedupeTabOrder);Alternatives I rejected:
reconcileWorktreeTabModelfor tab groups. It also sweeps orphan tabs and writesptyIdsByTabId, which the mirror must not touch.ptyId. That would put a terminal that died before the restart back on the tab ahead of reconnect, and undo the window's clear on exit.Linked Issue
STA-9417 (fixed by the core as a unit). Prerequisites before the core merges: the SSH fixes #26088 and #26098. Each worktree shows only its home partition's copy, and those PRs make that copy the right one.
Visual Proof
N/A. This is a stacked core step. The visible fix (no duplicate setup tab) shows only once C4 to C7 land, and the parity check below records what C3 changes on its own.
Testing
pnpm tcpasses.pnpm run check:code-quality:changedpasses.terminal-topology-mirror.test.ts,terminal-topology-follow.test.ts):pendingActivationSpawn,agentLaunchPane,generationand the liveptyId.ptyIdsByTabIdkeeps the same object;entityIdjoin works for removal, and an existing entry with a different id isn't duplicated. A removed tab leavesunifiedTabsand its group, and focus moves to the most recent remaining tab. Editor tabs are untouched;runtime:tab under the same worktree id is untouched;publishSeqis ignored;reconnectPersistedTerminals). A push that raced ahead of an older pull wins, a lower push after the pull is ignored, and abort unsubscribes. A failed pull still follows pushes.it.failscases insetup-split-activation-single-owner.test.ts(the reveal never arrives, and 8 create-then-activate cycles) are plainitnow. The test pushes main's slice, which holds the setup split, before the first activation.src/main/persistence/terminal-topology,src/renderer/src/store/terminals, session-writer and web test passes, apart from two web socket timeouts that also fail on the base. I ran the full unit suite too: every failing file outside those is either on C2's recorded base failure list or environmental (bun runtime,node:sqlite, lone-surrogate strings, socket timeouts). The one renderer file I spot-checked,terminal-shutdown-layout-capture, fails the same way on C2's head.ORCA_BACKGROUND_LAUNCH=1 pnpm run test:e2e:terminal-layout-parity -- --base origin/main --head HEAD(base80d45095d2f) FAILS, as expected for a lone core step. I declared no differences, because none of them is the named fix: the tool has no STA-9417 scenario yet (C7 adds it). Each difference:drag-out-to-tabbreaks on head (2 panes stay visible). Main's push of the move reaches the window before themoveLeafToNewTabreply. The window's own detach code then can't find the leaf it is moving, so it closes the new tab. Goes away with C5, which drops the window-side detach writes and keeps only main's move, plus C4, which removes the pane by reconciling.split-right-downandcli-split: the window's stored tree takes main's grafted shape (root vertical graft instead of the real nested or horizontal split). The panes on screen and the saved session are unchanged. Goes away with C1 (placement carries the proposed tree) and C4 (the reconciler applies main's geometry).startupCwd: <repo>appears on tab rows in every scenario, in the window and in the save. Main's spawn binding stamps the spawn cwd onto rows it creates (spawn-commit-persist.ts:63), and today the window's save overwrites it. This one does not go away on its own: after C7 main's row is authoritative. It is functionally neutral (it is the default cwd), but C1 or C7 should decide whether main stampsstartupCwdonly when the spawn asked for an explicit cwd (open question).restart-restoreandfolder-workspacewere rerun separately (--grep), after the drag-out failure stopped the serial run. Both journeys pass, and their only difference is Add Shift+Enter to insert newline in terminal #3.Review
Agent skill upstream boundary
Notes
Known gaps C3 leaves to later core steps (all inside the merge unit):
setLayout, C5sleepLeaf/wakeLeaf).importPeerTopology). The degraded startup path doesn't follow pushes.Other areas:
ssh:home copy. Mobile and web: unchanged, since they don't subscribe.Checklist