Skip to content

terminal: main applies where each new terminal goes (core C1 of 7) - #26151

Closed
Jinwoo-H wants to merge 10 commits into
mainfrom
topo-c1-placement-applies
Closed

Jinwoo-H wants to merge 10 commits into
mainfrom
topo-c1-placement-applies

Conversation

@Jinwoo-H

@Jinwoo-H Jinwoo-H commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​323 $\color{#cf222e}{\Huge{\mathbf{−}}}$​25 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​298
Prod 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​119 $\color{#cf222e}{\Huge{\mathbf{−}}}$​48 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​71

ELI5

When the app starts a new terminal, the window already tells the main process where it belongs: a new tab (with its title, color and so on), or a split next to a given pane (with direction, size and the full tree after the split). Until now main only logged that and kept guessing: it made a tab called "Terminal N" and stuck every new split pane to the right of the whole tab. Now main uses what the window told it.

This is core step C1 of 7 in the terminal layout refactor. The core steps are reviewed as separate stacked PRs and merged together as one unit. Base is #26078 (B1-5, the window side); it is retargeted to main when that merges.

What Changed

What users see: nothing. The window's own save still overwrites main's copy a moment later with the same tab and the same tree, so what you see and what gets saved don't change. (The parity check below confirms this.) The only time main's copy shows is when the app is killed between the terminal starting and the window's next save. In that case a restart now brings back the real tab title and the real split shape, not "Terminal N" and a split on the right. That's the "nested before/after splits restore the wrong shape" case.

How it works, in applyPtyBinding, for a pane main doesn't know yet:

  • New tab: today's minted row, with the creation fields the window sent written over it (title, custom title, color, createdAt, startup folder, shell, quick-command label, agent, view mode). Fields the window left out keep their minted values. pendingActivationSpawn: true, sort order and ids are unchanged.
  • Split: if the window's proposed tree holds exactly the tab's current panes plus the new one, main takes that tree as is. That's the only way to describe "split before" or "split around a group of panes". If not, main splits the named parent pane with the given direction and ratio.
  • Root: no change. An empty layout already gets the pane as its root.
  • Fallback: if a spawn has no placement (older clients, reattach, relay), or its placement doesn't fit the tab as it is now (the tab is missing, the parent isn't in the tab, the tab already exists for a new-tab request), main does exactly what it does today: mint the tab and graft the pane on the right. This isn't a second path kept alive. It's what happens when the input is missing, and B1-7 removes it later.

The decision is the existing terminalPanePlacementAgreement check: placement is applied only when it returns agrees, so the binding.placement span attribute now reads as "applied" vs "fell back". Since the write now depends on that check, I removed the report-only try/catch around it (and its check_threw value). Main's own headless split reuses the same split-at-pane function (splitLayoutLeaf) instead of its private copy.

Why

Later core steps need main's tree to match the window's. C4 applies geometry from main, and C6 needs a local split to cause no rebuild after the reply. A parent pane plus a direction can't describe a "before" split or a split around a subtree, so main takes the window's tree, but only after checking it holds exactly the expected panes. Otherwise it falls back to splitting the parent. I chose not to add a new resolver: the existing agreement check already sorted placements into "fits" and "doesn't fit".

Wire compatibility: every placement field stays optional. Old clients send none and keep today's behavior. A malformed field is dropped on parse, and a malformed placement as a whole is ignored.

Linked Issue

Part of the terminal layout refactor (STA-9417 core). No separate issue.

Visual Proof

N/A: nothing changes on screen. The window's save makes the saved state identical to main's.

Testing

  • Parity: ORCA_BACKGROUND_LAUNCH=1 pnpm run test:e2e:terminal-layout-parity -- --base origin/main --head HEAD passed with no differences (base 385fe8ab4ee, all 9 scenarios on both builds).
  • New terminal-pane-placement-apply.test.ts (21 cases):
    • golden: the placed row equals the minted row plus only the placement fields
    • an undefined field keeps the minted value
    • split at the parent with direction and ratio
    • a nested "before" split's proposed tree kept verbatim, and a subtree split kept
    • a proposed tree that is missing a pane, lacks the new one, adds a foreign one or repeats one falls back to the parent split
    • parent not in the tab: today's graft
    • tab missing: today's mint
    • a pane already present ignores placement
    • same revision bump as the graft
    • root
    • malformed placements fall back
  • pty-binding-placement-inertness.test.ts:
    • placements that match today's guess or fall back write the same memory and saved state (local, SSH and folder workspaces)
    • a placed split and row reach the saved ssh: copy
    • a closed (tombstoned) tab is still refused with placement
  • pnpm tc and pnpm run check:code-quality:changed pass. All src/main and src/shared unit tests pass, apart from 45 browser, daemon and fish-shell tests that fail the same way on the base. The test(terminal): pin layout invariants the mirror refactor must keep #26073 gate tests also pass on this branch.
  • Platforms: macOS locally. No platform-specific code.

Review

Agent skill upstream boundary

  • Not applicable.

Notes

  • SSH: placement applies in whichever copy the spawn binds to (ssh: for SSH spawns). Tested.
  • Folder workspaces: covered by the fixture scenarios.
  • Mobile and main-runtime spawns: unchanged. Main's own split now lands at the right pane at bind time, and its later exact-direction commit rebuilds the same tree.
  • Performance: one extra pass over a small tree, per new pane.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • N/A screenshots (no visual change)
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote considered
  • Typecheck, unit tests, changed-code gate pass locally

…eport-only

The window now says where each fresh terminal goes: the first pane of a tab
sends new-tab with the tab's creation fields, a split sends the parent leaf,
direction, ratio and the tree after the split, background launches send the
same, and a Codex restart that rebuilds a rootless layout sends root. A
reattach sends nothing. Main still only records whether placement names the
tab its binding write picks; its 'absent' value now counts only new leaves
without placement, so it reads the fallback mint and graft directly.

Extends the shared placement type with optional row, ratio and proposedRoot,
each dropped alone if malformed, so older and newer peers keep working.
…he tab lacks

The adopted tab is created without launchAgent, so a row naming one would
diverge from the renderer's tab once main applies placement.
…order

wrapInSplit now places the new pane first itself, so the pane-created
handler's proposedRoot sees the real post-split tree instead of the order
before the subtree split moved it.
Core step C1 of 7. When a new terminal's binding names a tab main does not
have yet, main builds the row from the creation fields the window sent; when
it names a split whose parent is in the tab, main takes the window's tree
(holding exactly today's leaves plus the new one) or splits at the parent with
the given direction and ratio. Spawns without placement, or whose placement
doesn't fit the tab's current shape, keep today's mint and root graft.

The headless split reuses the same split-at-leaf op, and the agreement span
now reports exactly when placement was applied.
…'s row

Main stamped the spawn's cwd on every tab it built from placement. Once
main's copy is authoritative that would add startupCwd to rows the window
never gave one.
@Jinwoo-H
Jinwoo-H changed the base branch from topo-b1-5-renderer-placement to main October 7, 2026 18:46
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 15 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c438ccbe-6e52-4754-8d13-b67c9fb466c6
📥 Commits

Reviewing files that changed from the base of the PR and between 740a09d and e87d8ec.

📒 Files selected for processing (7)
  • src/main/persistence/loading-store/pty-binding-persistence.ts
  • src/main/persistence/loading-store/pty-binding-placement-inertness.test.ts
  • src/main/persistence/loading-store/pty-binding-session-update.ts
  • src/main/persistence/terminal-topology/terminal-pane-placement-agreement.ts
  • src/main/persistence/terminal-topology/terminal-pane-placement-apply.test.ts
  • src/main/persistence/terminal-topology/terminal-pane-placement-apply.ts
  • src/main/runtime/headless-terminal-split-layout.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4705e385-05bf-4ee1-888f-238b136c788c
📥 Commits

Reviewing files that changed from the base of the PR and between b1b5590 and 740a09d.

📒 Files selected for processing (3)
  • src/main/persistence/loading-store/pty-binding-placement-inertness.test.ts
  • src/main/persistence/terminal-topology/terminal-pane-placement-agreement.ts
  • src/main/runtime/headless-terminal-split-layout.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/persistence/loading-store/pty-binding-placement-inertness.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

PTY binding persistence now applies placement details when they agree with the current session shape. New-tab row fields and split layouts are applied through placement helpers. Headless terminal layout updates also use the shared split insertion helper. The agreement check no longer converts thrown errors into a check_threw verdict.

Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to 740a0

A split started during another layout change can restore an older pane arrangement. The pane sessions remain intact, but the layout may need to be arranged again.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.73% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 37 functions across 35 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: main now applies placement information for each new terminal. The core C1 scope is also stated.
Description check ✅ Passed The description is detailed and covers the user impact, implementation, rationale, testing, compatibility, platforms, and checklist items. The linked issue section does not provide a concrete issue nu…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Jinwoo-H

Jinwoo-H commented Oct 8, 2026

Copy link
Copy Markdown
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.

@Jinwoo-H Jinwoo-H closed this Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant