Skip to content

Terminal and layout performance optimization - #826

Merged
sbertix merged 4 commits into
mainfrom
sbertix/terminal-layout-perf
Aug 23, 2026
Merged

Terminal and layout performance optimization#826
sbertix merged 4 commits into
mainfrom
sbertix/terminal-layout-perf

Conversation

@sbertix

@sbertix sbertix commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

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:

  1. 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 .id replaces the whole-tree structural identity.

  2. 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.

  3. 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.

  4. 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_free joins 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

  • Other: performance (no linked issue; opened by a maintainer with write access)

How was this tested?

  • make check passes (format + lint)
  • make test passes
  • I built and ran the app to confirm the change works

Checklist

  • I am the author of this work and accountable for it; no commit is authored or co-authored by an AI agent.
  • I have read the Contributing guide and the Code of Conduct.

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
sbertix marked this pull request as ready for review August 23, 2026 23:25
@sbertix
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
sbertix force-pushed the sbertix/terminal-layout-perf branch from 982337f to 877b7ff Compare August 23, 2026 23:45
@sbertix
sbertix merged commit 9f6a1c6 into main Aug 23, 2026
3 checks passed
@sbertix
sbertix deleted the sbertix/terminal-layout-perf branch August 23, 2026 23:56
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