diff options
| author | Christian Cleberg <[email protected]> | 2026-04-13 13:35:03 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-13 13:35:03 -0500 |
| commit | 0b84ecaf7ba0fb2cd3f3867da8dc732450b55b50 (patch) | |
| tree | c2d92448661d9e6ca76bd7c69e9d51131305214e /Hutch/Views/Builds | |
| parent | 06b24715cd244475ded5482e926721a57278a3cf (diff) | |
| download | hutch-0b84ecaf7ba0fb2cd3f3867da8dc732450b55b50.tar.gz hutch-0b84ecaf7ba0fb2cd3f3867da8dc732450b55b50.tar.bz2 hutch-0b84ecaf7ba0fb2cd3f3867da8dc732450b55b50.zip | |
perf: cache filtered collections and add equatable row views
Convert filteredJobs, filteredPastes, and filteredTickets from computed
properties to stored properties updated via didSet on their inputs. Each
update function guards with an equality check before assigning, so
@Observable skips the notification when the filtered result hasn't
changed — preventing list re-renders on auto-refresh when no visible
data has actually changed.
Add Equatable conformance to BuildRowView, PasteRowView, TicketRowView,
and SelectableTicketRow, and apply .equatable() at each ForEach call
site. When a list does re-render, SwiftUI now skips body evaluation for
rows whose model value is identical to the previous pass.
Implements: https://todo.sr.ht/~ccleberg/hutch/57
Diffstat (limited to 'Hutch/Views/Builds')
| -rw-r--r-- | Hutch/Views/Builds/BuildListView.swift | 1 | ||||
| -rw-r--r-- | Hutch/Views/Builds/BuildListViewModel.swift | 35 | ||||
| -rw-r--r-- | Hutch/Views/Builds/BuildRowView.swift | 2 |
3 files changed, 29 insertions, 9 deletions
diff --git a/Hutch/Views/Builds/BuildListView.swift b/Hutch/Views/Builds/BuildListView.swift index 6f3f934..d525918 100644 --- a/Hutch/Views/Builds/BuildListView.swift +++ b/Hutch/Views/Builds/BuildListView.swift @@ -137,6 +137,7 @@ struct BuildListView: View { ForEach(viewModel.filteredJobs) { job in NavigationLink(value: job) { BuildRowView(job: job) + .equatable() } .contextMenu { Button { diff --git a/Hutch/Views/Builds/BuildListViewModel.swift b/Hutch/Views/Builds/BuildListViewModel.swift index c6f8e49..643bacd 100644 --- a/Hutch/Views/Builds/BuildListViewModel.swift +++ b/Hutch/Views/Builds/BuildListViewModel.swift @@ -46,18 +46,33 @@ enum AutoRefreshInterval: Int, CaseIterable, Sendable { final class BuildListViewModel { private static let searchHistoryScopeID = "builds" - private(set) var jobs: [JobSummary] = [] + private(set) var jobs: [JobSummary] = [] { + didSet { updateFilteredJobs() } + } private(set) var recentSearches: [ScopedSearchHistoryEntry] private(set) var isLoading = false private(set) var isLoadingMore = false private(set) var isRefreshing = false private(set) var isSubmitting = false var error: String? - var filter: BuildListFilter = .attention - var searchText = "" + var filter: BuildListFilter = .attention { + didSet { updateFilteredJobs() } + } + var searchText = "" { + didSet { updateFilteredJobs() } + } var repoFilter: String = "" { - didSet { if repoFilter != oldValue { repoFilterDidChange() } } + didSet { + guard repoFilter != oldValue else { return } + repoFilterDidChange() + updateFilteredJobs() + } } + // Cached filtered result. Updated whenever jobs, filter, searchText, or + // repoFilter changes. Only notifies observers when the content actually + // differs, which prevents the list from re-rendering on auto-refresh when + // no visible data changed. + private(set) var filteredJobs: [JobSummary] = [] private var cursor: String? private var hasMore = true @@ -83,14 +98,18 @@ final class BuildListViewModel { return allTags.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } } - var filteredJobs: [JobSummary] { + private func updateFilteredJobs() { var result = Self.filterJobs(jobs, filter: filter) - if !repoFilter.isEmpty { result = result.filter { $0.tags.contains(repoFilter) } } - - return Self.searchJobs(result, matching: searchText) + let updated = Self.searchJobs(result, matching: searchText) + // Skip the assignment (and the resulting observer notification) when the + // filtered list hasn't actually changed — e.g. on auto-refresh when no + // builds have been added or updated. + if updated != filteredJobs { + filteredJobs = updated + } } // MARK: - Auto-Refresh diff --git a/Hutch/Views/Builds/BuildRowView.swift b/Hutch/Views/Builds/BuildRowView.swift index 9602e5c..a490123 100644 --- a/Hutch/Views/Builds/BuildRowView.swift +++ b/Hutch/Views/Builds/BuildRowView.swift @@ -1,6 +1,6 @@ import SwiftUI -struct BuildRowView: View { +struct BuildRowView: View, Equatable { let job: JobSummary var body: some View { |
