Skip to content

Improve Shelf book switch performance#246

Merged
onevcat merged 12 commits intomainfrom
perf/shelf-jank-fixes
Apr 29, 2026
Merged

Improve Shelf book switch performance#246
onevcat merged 12 commits intomainfrom
perf/shelf-jank-fixes

Conversation

@onevcat
Copy link
Copy Markdown
Owner

@onevcat onevcat commented Apr 29, 2026

Summary

This PR keeps the successful Shelf jank fixes from the investigation branch:

  • limits shortcut-hint invalidation to bare Command/Control modifier presses
  • disables sidebar key forwarding while Shelf is active, removing the KeyPressModifier invalidation path during book switching
  • renders Shelf spines from a single ForEach, eliminating matched-geometry churn between left/right spine stacks
  • removes the open-book opacity transition, which showed a small hitch improvement without noticeable UX regression
  • adds/keeps signposts and investigation notes for future Shelf performance work

Why

Book switching in Shelf mode was hitching heavily, especially when switching via keyboard shortcuts. Trace analysis showed a large SwiftUI invalidation storm from shortcut/key-press environment modifiers, followed by layout churn from the split spine rendering model.

The retained changes brought the most useful trace and UX improvements while avoiding the later experiments that introduced visible artifacts or degraded interaction.

Validation

  • make build-app 2>&1 | xcsift -f toon
  • Compared Instruments traces through run15. The retained path removed EnvironmentWriter: KeyPressModifier from the Shelf switching trace, eliminated Layout: MatchedFrame, and reduced select-window hitch cost versus the earlier baseline.

onevcat added 11 commits April 28, 2026 17:11
Fast book-switch profiling on the Shelf showed `RepositorySectionView.body`
running ~7400 times/sec under SwiftUI observation tracking — `openTabCount`
inside the body subscribed every section to the entire
`WorktreeTerminalManager.states` dictionary, which churns on any terminal
activity. Combined with `ShelfView.body` rebuilding `worktreeRowSections`
per call, a 25s fast-switching trace produced one Severe Hang (2.02s) plus
11 multi-hundred-ms Hangs.

Changes:
- RepositorySectionView: extract the tab-count badge into a dedicated leaf
  view (`RepoHeaderTabCountBadge`) so the parent body never reads
  `terminalManager`. Body invocations -92% in the rerun trace.
- ShelfBook.orderedShelfBooks: replace `Dictionary(uniqueKeysWithValues:)`
  with `repositories[id:]` and route worktree ordering through
  `orderedWorktrees(in:)` to skip per-repo `WorktreeRowSections` model and
  Set construction.
- ShelfView: drop the redundant TCA action animation
  (`store.send(..., animation:)`) on book open paths — the view-level
  `.animation(_:value: openBookID)` already drives the spine flow.
- SupaLogger: add an `OSSignposter` plus `interval`,
  `beginInterval`/`endInterval` (token form for `inout`-bound paths), and
  `event` helpers — kept always-on since signposts are ~zero-cost when no
  Instruments session is attached.
- Instrument hot paths: `WorktreeTerminalState.focusSelectedTab`,
  `syncFocus`, `applySurfaceActivity`; `ShelfOpenBookView.onAppear` /
  `onChange(selectedTabId)`; `RepositoriesFeature.selectWorktree` /
  `selectRepository` reducer cases; `ShelfView` book-click events.

Verified via `make check`, `make build-app`, `make test` (928 passed).
After the perf push that landed in 0fe682c, several diagnostic signposts
proved valuable enough to keep around — they cost nothing when no
Instruments session is attached but make future regressions much easier
to localize on the Points of Interest timeline.

What this commit adds:

- SupaLogger: signposter is now created with the well-known
  `"PointsOfInterest"` category so events surface in Apple's stock
  Points of Interest instrument without any custom subsystem filter
  (signpost names already carry origin granularity).
- ShelfView.body / ShelfSpineView.body event markers — sanity-check
  body invocation cadence across animation transitions.
- ShelfOpenBookView.onDisappear event — pairs with the existing
  onAppear interval so any future change to subtree mount/unmount
  cadence shows as a count delta.
- GhosttyTerminalView.makeNSView / updateNSView intervals plus a
  dismantleNSView event override — counts and times the
  NSViewRepresentable lifecycle, the layer where book-switch teardown
  cost is most directly observable.

Behavioural deltas: none — this is pure observability.
Closes the loop on the perf push that landed in 0fe682c / c9b852e.
Captures the full investigation narrative — symptoms, hypotheses, the
P0 wins (sidebar observability storm), the Phase 2 / animation
experiments that did not pan out, methodology learnings around xctrace
and Animation Hitches signpost filtering, and the long-term
observability hooks that are now permanently in place.

Intended as a starting reference if Shelf jank ever resurfaces.
@onevcat onevcat marked this pull request as ready for review April 29, 2026 02:44
@onevcat onevcat merged commit 0a0a556 into main Apr 29, 2026
1 check passed
@onevcat onevcat deleted the perf/shelf-jank-fixes branch April 29, 2026 02:55
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