From ca5867345d88084d60c7c4ff91465d5bcbf73b82 Mon Sep 17 00:00:00 2001 From: onevcat Date: Wed, 29 Apr 2026 22:59:25 +0900 Subject: [PATCH] Fix Shelf empty state fallback --- .../Features/Shelf/Models/ShelfBook.swift | 11 ++++++++ supacode/Features/Shelf/Views/ShelfView.swift | 9 ++++--- supacodeTests/ShelfBookOrderingTests.swift | 27 +++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/supacode/Features/Shelf/Models/ShelfBook.swift b/supacode/Features/Shelf/Models/ShelfBook.swift index 672fdab3..79a2d991 100644 --- a/supacode/Features/Shelf/Models/ShelfBook.swift +++ b/supacode/Features/Shelf/Models/ShelfBook.swift @@ -115,4 +115,15 @@ extension RepositoriesFeature.State { var openShelfBookID: Worktree.ID? { selectedTerminalWorktree?.id } + + /// The rendered book matching the current Shelf selection, if any. + /// + /// `openShelfBookID` can briefly point at a worktree/folder that has + /// just been retired from `openedWorktreeIDs` after its last tab closes. + /// Views should use this lookup rather than assuming a non-nil open ID + /// means an open book is still present in `orderedShelfBooks()`. + func openShelfBook(in books: [ShelfBook]) -> ShelfBook? { + guard let openShelfBookID else { return nil } + return books.first(where: { $0.id == openShelfBookID }) + } } diff --git a/supacode/Features/Shelf/Views/ShelfView.swift b/supacode/Features/Shelf/Views/ShelfView.swift index a8f4b50b..1aa07110 100644 --- a/supacode/Features/Shelf/Views/ShelfView.swift +++ b/supacode/Features/Shelf/Views/ShelfView.swift @@ -32,9 +32,10 @@ struct ShelfView: View { let _ = shelfLogger.event("ShelfView.body") let state = store.state let books = state.orderedShelfBooks(customTitles: state.repositoryCustomTitles) - let openBookID = state.openShelfBookID - let openIndex = openBookID.flatMap { id in - books.firstIndex(where: { $0.id == id }) + let openBook = state.openShelfBook(in: books) + let openBookID = openBook?.id + let openIndex = openBook.flatMap { book in + books.firstIndex(where: { $0.id == book.id }) } HStack(spacing: 0) { @@ -44,7 +45,7 @@ struct ShelfView: View { openBookArea(for: book, state: state) } } - if openBookID == nil { + if openBook == nil { emptyOpenArea() } } diff --git a/supacodeTests/ShelfBookOrderingTests.swift b/supacodeTests/ShelfBookOrderingTests.swift index a1e919a0..9212eecb 100644 --- a/supacodeTests/ShelfBookOrderingTests.swift +++ b/supacodeTests/ShelfBookOrderingTests.swift @@ -182,6 +182,33 @@ struct ShelfBookOrderingTests { #expect(state.openShelfBookID == worktree.id) } + @Test func openShelfBookReturnsNilWhenSelectedBookIsNoLongerRendered() { + let rootURL = URL(fileURLWithPath: "/tmp/repo") + let worktree = Worktree( + id: "/tmp/repo", + name: "main", + detail: "", + workingDirectory: rootURL, + repositoryRootURL: rootURL + ) + let repository = Repository( + id: rootURL.path(percentEncoded: false), + rootURL: rootURL, + name: "repo", + worktrees: IdentifiedArray(uniqueElements: [worktree]) + ) + var state = RepositoriesFeature.State(repositories: [repository]) + state.repositoryRoots = [rootURL] + state.repositoryOrderIDs = [repository.id] + state.selection = .worktree(worktree.id) + state.openedWorktreeIDs = [] + + let books = state.orderedShelfBooks() + #expect(books.isEmpty) + #expect(state.openShelfBookID == worktree.id) + #expect(state.openShelfBook(in: books) == nil) + } + @Test func openShelfBookIDResolvesPlainFolderViaRepositoryID() { let rootURL = URL(fileURLWithPath: "/tmp/plain") let repository = Repository( -- 2.51.2