@@ -12441,6 +12441,13 @@ private struct TabItemView: View, Equatable {
1244112441 @State private var workspaceObservationGeneration: UInt64 = 0
1244212442 @State private var isHovering = false
1244312443 @State private var rowHeight: CGFloat = 1
12444+ // Cached results of the expensive bonsplit tree walk + branch/dir/PR snapshot.
12445+ // Updated only by the debounced publisher, onAppear, and settings changes —
12446+ // NOT by the immediate publisher (title keystrokes). This prevents the tree
12447+ // walk from running on every keystroke in single-panel workspaces.
12448+ @State private var cachedOrderedPanelIds: [UUID]? = nil
12449+ @State private var cachedBranchDirectoryLines: [VerticalBranchDirectoryLine] = []
12450+ @State private var cachedPullRequestRows: [PullRequestDisplay] = []
1244412451
1244512452 var isMultiSelected: Bool {
1244612453 selectedTabIds.contains(tab.id)
@@ -12681,9 +12688,11 @@ private struct TabItemView: View, Equatable {
1268112688 let latestNotificationSubtitle = latestNotificationText
1268212689 let effectiveSubtitle = latestNotificationSubtitle
1268312690 let detailVisibility = visibleAuxiliaryDetails
12684- let orderedPanelIds: [UUID]? = (detailVisibility.showsBranchDirectory || detailVisibility.showsPullRequests)
12685- ? tab.sidebarOrderedPanelIds()
12686- : nil
12691+ // Read from cache — updated only by the debounced publisher, onAppear,
12692+ // and settings changes. Title-keystroke re-renders skip this tree walk.
12693+ let orderedPanelIds: [UUID]? = cachedOrderedPanelIds
12694+ let branchDirectoryLines: [VerticalBranchDirectoryLine] = cachedBranchDirectoryLines
12695+ let pullRequestRows: [PullRequestDisplay] = cachedPullRequestRows
1268712696 let compactGitBranchSummaryText: String? = {
1268812697 guard detailVisibility.showsBranchDirectory,
1268912698 !sidebarBranchVerticalLayout,
@@ -12705,19 +12714,7 @@ private struct TabItemView: View, Equatable {
1270512714 gitSummary: compactGitBranchSummaryText,
1270612715 directorySummary: compactDirectorySummaryText
1270712716 )
12708- let branchDirectoryLines: [VerticalBranchDirectoryLine] = {
12709- guard detailVisibility.showsBranchDirectory,
12710- sidebarBranchVerticalLayout,
12711- let orderedPanelIds else {
12712- return []
12713- }
12714- return verticalBranchDirectoryLines(orderedPanelIds: orderedPanelIds)
12715- }()
1271612717 let branchLinesContainBranch = sidebarShowGitBranch && branchDirectoryLines.contains { $0.branch != nil }
12717- let pullRequestRows: [PullRequestDisplay] = {
12718- guard detailVisibility.showsPullRequests, let orderedPanelIds else { return [] }
12719- return pullRequestDisplays(orderedPanelIds: orderedPanelIds)
12720- }()
1272112718
1272212719 VStack(alignment: .leading, spacing: 4) {
1272312720 HStack(spacing: 8) {
@@ -13056,6 +13053,10 @@ private struct TabItemView: View, Equatable {
1305613053 "desc=\"\(debugCommandPaletteTextPreview(description))\""
1305713054 )
1305813055#endif
13056+ // Refresh expensive caches (tree walk, branch/dir/PR) before
13057+ // signalling a redraw. The immediate publisher intentionally does
13058+ // NOT call this so that title keystrokes skip the tree walk.
13059+ recomputeSidebarDetailCache()
1305913060 workspaceObservationGeneration &+= 1
1306013061 }
1306113062 .onDrag {
@@ -13098,6 +13099,15 @@ private struct TabItemView: View, Equatable {
1309813099 .accessibilityAction(named: Text(moveDownActionText)) {
1309913100 moveBy(1)
1310013101 }
13102+ .onAppear {
13103+ // Prime the cache so branch/dir/PR rows appear immediately,
13104+ // before the first debounced publisher fires.
13105+ recomputeSidebarDetailCache()
13106+ }
13107+ .onChange(of: visibleAuxiliaryDetails) { _ in
13108+ // Toggling branch/PR columns changes which data we need to cache.
13109+ recomputeSidebarDetailCache()
13110+ }
1310113111 .contextMenu { workspaceContextMenu }
1310213112 }
1310313113
@@ -13669,6 +13679,25 @@ private struct TabItemView: View, Equatable {
1366913679 let directory: String?
1367013680 }
1367113681
13682+ /// Recomputes the expensive sidebar detail caches (bonsplit tree walk,
13683+ /// branch/directory lines, PR snapshot) and writes them into @State.
13684+ /// Must be called only from the debounced publisher, onAppear, and
13685+ /// settings-change handlers — never from the immediate (title) publisher.
13686+ private func recomputeSidebarDetailCache() {
13687+ let detail = visibleAuxiliaryDetails
13688+ let needsDetail = detail.showsBranchDirectory || detail.showsPullRequests
13689+ let ids: [UUID]? = needsDetail ? tab.sidebarOrderedPanelIds() : nil
13690+ cachedOrderedPanelIds = ids
13691+ cachedBranchDirectoryLines = {
13692+ guard detail.showsBranchDirectory, sidebarBranchVerticalLayout, let ids else { return [] }
13693+ return verticalBranchDirectoryLines(orderedPanelIds: ids)
13694+ }()
13695+ cachedPullRequestRows = {
13696+ guard detail.showsPullRequests, let ids else { return [] }
13697+ return pullRequestDisplays(orderedPanelIds: ids)
13698+ }()
13699+ }
13700+
1367213701 private func verticalBranchDirectoryLines(orderedPanelIds: [UUID]) -> [VerticalBranchDirectoryLine] {
1367313702 let entries = tab.sidebarBranchDirectoryEntriesInDisplayOrder(orderedPanelIds: orderedPanelIds)
1367413703 let home = SidebarPathFormatter.homeDirectoryPath
0 commit comments