Skip to content

perf: cut tab/workspace switch latency and background churn - #108

Merged
vaayne merged 4 commits into
mainfrom
perf/switching-latency
Jul 31, 2026
Merged

perf: cut tab/workspace switch latency and background churn#108
vaayne merged 4 commits into
mainfrom
perf/switching-latency

Conversation

@vaayne

@vaayne vaayne commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Problem

Switching tabs and workspaces felt sluggish. Measured against the live app: raw tmux select-window takes ~18ms, but Mori took 110–160ms before the tmux window actually flipped — and workspace switches paid seconds when the surface cache thrashed.

Root causes & fixes

Switch latency

  • selectWindow/selectPane issued the tmux command in a main-actor Task, which queued behind the selection-triggered UI re-render (~60–100ms per switch, confirmed by sample). Now Task.detached, fired before any state mutation.
  • selectWorktree serialized terminal attach behind git statuslist-sessions → full tmux rescan. When the last poll (≤5s) saw the session alive, attach now happens immediately; the reconcile chain still runs behind it. (Known accepted race documented inline: a session dying inside the poll window gets recreated by the surface's new-session -A without the mori pane environment.)
  • Terminal surface cache held only 3 surfaces — cycling through >3 workspaces destroyed and respawned a login shell + tmux attach on every switch. Raised to 10.

Background churn

  • scanAll ran 1 + sessions + windows subprocess invocations every 5s poll (dozens of SSH round trips on remote endpoints). Now a single tmux list-panes -a with session/window context per row; TmuxParser.parseScan regroups the tree. tmux guarantees ≥1 window per session and ≥1 pane per window, so nothing is lost; window currentPath keeps list-windows semantics (resolved from the active pane).
  • The sidebar's agent working icon used a SwiftUI repeatForever animation → re-rendered the hosting view every frame while any agent was working (~12% of the main thread at idle, sampled). Now a repeating CABasicAnimation on the layer (render server).
  • TerminalTabsBarView rebuilt every tab NSView on any runtimeWindows change (every poll). Now snapshots last-rendered windows + selection and skips no-op rebuilds.

Verification

  • mise run test — all packages pass (incl. 4 new parseScan tests; 288 tmux assertions)
  • Release builds of both products (Mori, mori CLI)
  • CI=1 scripts/bundle.sh + app launch check
  • scanFormat verified against a live tmux server
  • Latency measured via mori focus IPC + tmux active-window polling; sample Mori profiles before/after

Changelogs (en + zh-Hans) updated.

vaayne added 4 commits July 31, 2026 21:11
Three sources of switching lag, measured against the live app (raw tmux
select-window is ~18ms; Mori took 110-160ms end to end before the tmux
window actually flipped):

- selectWindow/selectPane issued the tmux command in a main-actor Task,
  which queued behind the selection-triggered UI re-render (~60-100ms).
  The command is now Task.detached and fired before any state mutation.
- selectWorktree serialized terminal attach behind a git status, a
  list-sessions round trip, and a full tmux rescan. When the last poll
  (<=5s) saw the session alive, attach now happens immediately and the
  reconcile chain runs behind it.
- The terminal surface cache held only 3 surfaces, so cycling through
  more than three workspaces destroyed and respawned a login shell +
  tmux attach on every switch. Raised to 10.
scanAll ran 1 + sessions + windows subprocess invocations every 5s poll
(dozens of SSH round trips for remote endpoints). A single list-panes -a
row per pane carries session and window context; parseScan groups rows
back into the session tree. tmux guarantees every session has >=1 window
and every window >=1 pane, so grouping loses no nodes. Window currentPath
keeps list-windows semantics by resolving from the active pane.
The sidebar's agent working icon used a SwiftUI repeatForever opacity
animation, which re-rendered the hosting view's display list every frame
for as long as any agent was working (~12% of the main thread at idle,
sampled). It now pulses via a repeating CABasicAnimation on the layer,
which runs on the render server.

TerminalTabsBarView's observation fires for any runtimeWindows change
(every poll); it tore down and recreated every tab NSView each time. It
now snapshots what it last rendered and skips rebuilds when the strip's
windows and selection are unchanged.
@vaayne
vaayne merged commit 6a18aa8 into main Jul 31, 2026
5 checks passed
@vaayne
vaayne deleted the perf/switching-latency branch July 31, 2026 13:28
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