Terminal and layout performance optimization - #826
Merged
Conversation
Drop the per-selection and whole-tree `.id`s that tore down and rebuilt the pane hosting chain on every worktree switch and split/close; identity now lives per leaf, so a topology change re-identifies only the branch it changed. Cache each surface's scroll wrapper and reuse it across remounts, so a switch reparents the live surface instead of building a new wrapper at zero size, which made the renderer discard the frames painted for the old bounds.
Agent TUIs rewrite their title several times a second. That report now lands on the content's observable TabChrome and reaches the layout only as one discrete titleCommitted at teardown, so a title storm no longer invalidates the detail view or the sidebar, or pays a per-action reducer log. The strip and window title read the live value off the chrome (TabTitle.resolved); persistence pulls it into the snapshot (TabTitle.stored). Also gate the per-action state-diff logging behind SUPACODE_LOG_ACTIONS, and skip the no-op per-keystroke bell and per-prompt pwd writes.
sbertix
marked this pull request as ready for review
August 23, 2026 23:25
sbertix
enabled auto-merge (squash)
August 23, 2026 23:25
Hibernation was a pure 5-minute wall-clock timer, so flipping back to a worktree you were just in paid a full surface rebuild and zmx replay. Keep the most recently selected worktrees' visible tabs live regardless of idle time (capped at liveWorktreeLimit), so switching among them never rewakes; the clock still hibernates worktrees outside that window, and stacked background tabs as before. Under memory pressure, drop the recency budget to the current selection and hibernate every hidden tab immediately, so the live set never grows unbounded.
Closing a pane ran ghostty_surface_free synchronously in the reducer, before the split tree collapsed: it joins the surface's search, renderer, and IO threads and tears down the Metal renderer, all on the click. Detach the surface synchronously (unregister it, clear the callback wiring, hide the view so the "[Process exited]" overlay cannot flash while the layout collapses) and free it off the reducer turn on the main queue, retaining the Ghostty app and the callback bridge until the free lands. Reap after the tree collapses so the collapse is the turn's state mutation. Deferring the free keeps the surface's background threads alive briefly after closeSurface returns, so an action one emits can reach the app callback after the surface is freed. Drop a background-thread action whose surface was unregistered (closing or freed) via a live-surface registry cleared synchronously in closeSurface, so a late action lands on nothing instead of freed memory.
sbertix
force-pushed
the
sbertix/terminal-layout-perf
branch
from
August 23, 2026 23:45
982337f to
877b7ff
Compare
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.
Summary
This is a set of performance optimizations on the terminal and layout hot paths. Some address slowdowns from the pane/tab/content layout rework (#786), others are longer-standing, but the theme is the same: cut the work done on the interactive path so switching, typing, and reclaiming stay smooth.
This is an initial, minimal subset of the performance work @sasha-id shared from his fork (see #814). His refactor is genuinely impressive. Rather than port his code wholesale, I re-implemented each fix from the root cause up: same diagnosis, independently written, kept to a minimal reviewable subset so every slice lands with its own tests and rationale. Full credit to him for surfacing the problems and for the original implementation that pointed the way: https://github.com/sasha-id/supacode
Slices, one commit each:
Switch worktrees by reparenting the terminal surface, not rebuilding it. View identity was tied to selection and topology, so every switch tore down and rebuilt the live surface at zero size (the visible flash). The surface's scroll wrapper is now built once and reparented across remounts, and a per-leaf
.idreplaces the whole-tree structural identity.Keep per-tab title churn off the root store. An agent TUI rewrites its title several times a second, and each report was dispatched as a reducer action through the root store, fanning out invalidation. Reported titles now land on the content's observable chrome and reach the layout only as a single discrete commit at teardown.
Keep recently-used worktrees live and reclaim under memory pressure. Hidden worktrees hibernated on a wall-clock grace timer alone. A small LRU recency floor now keeps the most recently used worktrees' front surfaces live, and a memory-pressure ceiling reclaims aggressively when the system asks.
Cut split and close latency (incoming). Closing a pane runs libghostty's surface teardown synchronously on the main thread before the layout collapses:
ghostty_surface_freejoins the surface's search, renderer, and IO threads and tears down the Metal renderer, all on the click. This slice defers the free off the turn so the collapsed layout paints first, reaps after the tree collapses instead of before, and hides the surface at detach so the[Process exited]overlay never flashes during the collapse.Type of change
How was this tested?
make checkpasses (format + lint)make testpassesChecklist