From 30decf6d9b6b95277cbd8b8a7d9e23d9ce5413ca Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Mon, 13 Apr 2026 17:35:37 -0500 Subject: fix: show empty state for bare git repos instead of an error git.sr.ht returns "internal system error" from the references query and "reference not found" from the log/readme queries when a repo has no commits. Broaden isEmptyRepositoryError to cover missingReference, unknownRevision, noRows, notFound, and those two message strings, then apply the same silent-empty treatment to loadReferences and loadArtifacts (which previously surfaced any error directly to the user). Fixes: https://todo.sr.ht/~ccleberg/hutch/60 --- Hutch/Views/Tickets/TicketListViewModel.swift | 34 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'Hutch/Views/Tickets') diff --git a/Hutch/Views/Tickets/TicketListViewModel.swift b/Hutch/Views/Tickets/TicketListViewModel.swift index e23cc52..0255798 100644 --- a/Hutch/Views/Tickets/TicketListViewModel.swift +++ b/Hutch/Views/Tickets/TicketListViewModel.swift @@ -99,13 +99,13 @@ final class TicketListViewModel { var filter: TicketFilter = .open { didSet { persistFilterState() - updateFilteredTickets() + resetPaginationAndUpdateFilters() } } var selectedLabelIDs: Set = [] { didSet { persistFilterState() - updateFilteredTickets() + resetPaginationAndUpdateFilters() } } var searchText = "" { @@ -283,6 +283,13 @@ final class TicketListViewModel { } } + private func resetPaginationAndUpdateFilters() { + // Reset pagination when filters change since the cursor is tied to the unfiltered dataset + cursor = nil + hasMore = true + updateFilteredTickets() + } + // MARK: - Public API func loadTickets() async { @@ -305,19 +312,32 @@ final class TicketListViewModel { } func loadMoreIfNeeded(currentItem: TicketSummary) async { - guard let last = tickets.last, - last.id == currentItem.id, - hasMore, - !isLoadingMore else { + // Check if currentItem is in the filtered list and close to the end + guard hasMore, !isLoadingMore else { return } + + guard let index = filteredTickets.firstIndex(where: { $0.id == currentItem.id }) else { return } + let itemsFromEnd = filteredTickets.count - index - 1 + guard itemsFromEnd < 5 else { return } + isLoadingMore = true do { let page = try await fetchPage(cursor: cursor) - tickets.append(contentsOf: page.results) + + // Deduplicate: only add tickets that don't already exist + let existingIDs = Set(tickets.map(\.id)) + let newTickets = page.results.filter { !existingIDs.contains($0.id) } + + // If we got back the same tickets, the API cursor pagination isn't working + if newTickets.isEmpty && !page.results.isEmpty { + hasMore = false + } + cursor = page.cursor + tickets.append(contentsOf: newTickets) hasMore = page.cursor != nil reconcileSelectionWithLoadedTickets() } catch { -- cgit v1.2.3