native macOS codings agent orchestrator
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf(shelf): isolate book switch animation

+57 -11
+57 -11
supacode/Features/Shelf/Views/ShelfView.swift
··· 44 44 45 45 HStack(spacing: 0) { 46 46 if let openIndex { 47 - spineStack(books: Array(books[0...openIndex]), openIndex: openIndex, baseOffset: 0) 47 + spineStack( 48 + books: Array(books[0...openIndex]), 49 + openIndex: openIndex, 50 + baseOffset: 0, 51 + animationValue: openBookID 52 + ) 48 53 openBookArea(for: books[openIndex], state: state) 49 54 .transition(.opacity) 55 + .transaction { transaction in 56 + transaction.animation = nil 57 + } 50 58 let rightStart = openIndex + 1 51 59 if rightStart < books.count { 52 60 spineStack( 53 61 books: Array(books[rightStart..<books.count]), 54 62 openIndex: openIndex, 55 - baseOffset: rightStart 63 + baseOffset: rightStart, 64 + animationValue: openBookID 56 65 ) 57 66 } 58 67 } else { 59 - spineStack(books: books, openIndex: nil, baseOffset: 0) 68 + spineStack(books: books, openIndex: nil, baseOffset: 0, animationValue: openBookID) 60 69 emptyOpenArea() 61 70 } 62 71 } 63 72 .frame(maxWidth: .infinity, maxHeight: .infinity) 64 73 .background(Color(nsColor: .windowBackgroundColor).opacity(surfaceBackgroundOpacity)) 65 - // Animate on every openBookID change — covers both Shelf-originated 66 - // book switches (which also set their own TCA animation) and 67 - // left-nav-originated switches, so the spine flow is consistent 68 - // regardless of entry point. 69 - .animation(.easeInOut(duration: 0.2), value: openBookID) 70 74 } 71 75 72 76 /// `baseOffset` is the index of `books.first` within the full ordered 73 77 /// list, so we can reconstruct each spine's global index and compute 74 78 /// its distance to `openIndex` without re-scanning the full list. 75 79 @ViewBuilder 76 - private func spineStack(books: [ShelfBook], openIndex: Int?, baseOffset: Int) -> some View { 80 + private func spineStack( 81 + books: [ShelfBook], 82 + openIndex: Int?, 83 + baseOffset: Int, 84 + animationValue: Worktree.ID? 85 + ) -> some View { 77 86 HStack(spacing: 0) { 78 87 ForEach(Array(books.enumerated()), id: \.element.id) { localIndex, book in 79 88 let globalIndex = baseOffset + localIndex 80 89 let distance = openIndex.map { abs(globalIndex - $0) } 81 90 let open = globalIndex == openIndex 82 - ShelfSpineView( 91 + ShelfSpineContainer( 83 92 book: book, 84 93 isOpen: open, 85 94 distanceFromOpen: distance, 86 - terminalState: terminalManager.stateIfExists(for: book.id), 95 + terminalManager: terminalManager, 87 96 onOpenBook: { openBook(book, selectingTab: nil) }, 88 97 onSelectTab: { tabID in openBook(book, selectingTab: tabID) }, 89 98 onNewTab: { ··· 105 114 .matchedGeometryEffect(id: book.id, in: spineNamespace) 106 115 } 107 116 } 117 + // Keep the visual spine-flow animation scoped to the lightweight 118 + // spine layer. The terminal content area is intentionally outside 119 + // this transaction so Ghostty/AppKit representables do not inherit a 120 + // book-switch animation. 121 + .animation(.easeInOut(duration: 0.2), value: animationValue) 108 122 } 109 123 110 124 /// Dispatch the open-book action only when `book` isn't already the open ··· 229 243 } 230 244 } 231 245 } 246 + 247 + private struct ShelfSpineContainer: View { 248 + let book: ShelfBook 249 + let isOpen: Bool 250 + let distanceFromOpen: Int? 251 + let terminalManager: WorktreeTerminalManager 252 + let onOpenBook: () -> Void 253 + let onSelectTab: (TerminalTabID) -> Void 254 + let onNewTab: () -> Void 255 + let onSplitVertical: (() -> Void)? 256 + let onSplitHorizontal: (() -> Void)? 257 + let closeMenuTitle: String 258 + let onCloseBook: () -> Void 259 + let onOpenRepositorySettings: () -> Void 260 + 261 + var body: some View { 262 + ShelfSpineView( 263 + book: book, 264 + isOpen: isOpen, 265 + distanceFromOpen: distanceFromOpen, 266 + terminalState: terminalManager.stateIfExists(for: book.id), 267 + onOpenBook: onOpenBook, 268 + onSelectTab: onSelectTab, 269 + onNewTab: onNewTab, 270 + onSplitVertical: onSplitVertical, 271 + onSplitHorizontal: onSplitHorizontal, 272 + closeMenuTitle: closeMenuTitle, 273 + onCloseBook: onCloseBook, 274 + onOpenRepositorySettings: onOpenRepositorySettings 275 + ) 276 + } 277 + }