perf: cut tab/workspace switch latency and background churn - #108
Merged
Conversation
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.
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.
Problem
Switching tabs and workspaces felt sluggish. Measured against the live app: raw
tmux select-windowtakes ~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/selectPaneissued the tmux command in a main-actorTask, which queued behind the selection-triggered UI re-render (~60–100ms per switch, confirmed bysample). NowTask.detached, fired before any state mutation.selectWorktreeserialized terminal attach behindgit status→list-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'snew-session -Awithout the mori pane environment.)Background churn
scanAllran 1 + sessions + windows subprocess invocations every 5s poll (dozens of SSH round trips on remote endpoints). Now a singletmux list-panes -awith session/window context per row;TmuxParser.parseScanregroups the tree. tmux guarantees ≥1 window per session and ≥1 pane per window, so nothing is lost; windowcurrentPathkeepslist-windowssemantics (resolved from the active pane).repeatForeveranimation → re-rendered the hosting view every frame while any agent was working (~12% of the main thread at idle, sampled). Now a repeatingCABasicAnimationon the layer (render server).TerminalTabsBarViewrebuilt every tab NSView on anyruntimeWindowschange (every poll). Now snapshots last-rendered windows + selection and skips no-op rebuilds.Verification
mise run test— all packages pass (incl. 4 newparseScantests; 288 tmux assertions)Mori,moriCLI)CI=1 scripts/bundle.sh+ app launch checkscanFormatverified against a live tmux servermori focusIPC + tmux active-window polling;sample Moriprofiles before/afterChangelogs (en + zh-Hans) updated.