From 391c11dbad5b27612eb705d204be36722aeb29b3 Mon Sep 17 00:00:00 2001 From: Max Heidinger Date: Tue, 11 Nov 2025 16:19:58 +0100 Subject: [PATCH 1/4] feat: show current app version on settings screen --- Localizable.xcstrings | 4 +++ PReek/Views/Settings/SettingsScreen.swift | 33 ++++++++++++++--------- build-dmg.sh | 3 --- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 18bfcee..082ea3c 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -1191,6 +1191,10 @@ } } }, + "Version: %@" : { + "comment" : "A label showing the current version of the app.", + "isCommentAutoGenerated" : true + }, "welcome.subtitle" : { "localizations" : { "de" : { diff --git a/PReek/Views/Settings/SettingsScreen.swift b/PReek/Views/Settings/SettingsScreen.swift index a78f971..57b0546 100644 --- a/PReek/Views/Settings/SettingsScreen.swift +++ b/PReek/Views/Settings/SettingsScreen.swift @@ -5,6 +5,14 @@ import SwiftUI import CodeScanner #endif +func getAppVersion() -> String { + if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { + return appVersion + } + return "Unknown" +} + + struct SettingsScreen: View { @ObservedObject var configViewModel: ConfigViewModel @@ -32,20 +40,21 @@ struct SettingsScreen: View { } } .importSheet(configViewModel: configViewModel, isPresented: $showImportSheet) - #endif - #if os(macOS) - .safeAreaInset(edge: .bottom, spacing: 0) { - HStack { - Button("Quit App", action: { NSApplication.shared.terminate(nil) }) - NavigationLink(value: Screen.share) { - Text("Share") + #elseif os(macOS) + .safeAreaInset(edge: .bottom, spacing: 0) { + HStack { + Button("Quit App", action: { NSApplication.shared.terminate(nil) }) + NavigationLink(value: Screen.share) { + Text("Share") + } + Spacer() + Text("Version: \(getAppVersion())") + .foregroundStyle(.secondary) } - Spacer() + .padding(.horizontal, 20) + .padding(.vertical, 10) + .background(.windowBackground) } - .padding(.horizontal, 20) - .padding(.vertical, 10) - .background(.windowBackground) - } #endif } diff --git a/build-dmg.sh b/build-dmg.sh index a486561..39d60f4 100755 --- a/build-dmg.sh +++ b/build-dmg.sh @@ -1,8 +1,5 @@ #!/bin/bash -# Exit on error -set -e - # Configuration SCHEME_NAME="PReek" PROJECT_PATH="PReek.xcodeproj" From 2837dfcd8742471371dbb4fb39211f1390062fc5 Mon Sep 17 00:00:00 2001 From: Max Heidinger Date: Tue, 11 Nov 2025 16:20:12 +0100 Subject: [PATCH 2/4] fix: better align approval/changes requested icons --- .../DisclosureGroupList/PullRequestHeaderView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PReek/Views/PullRequestViews/DisclosureGroupList/PullRequestHeaderView.swift b/PReek/Views/PullRequestViews/DisclosureGroupList/PullRequestHeaderView.swift index 4b21258..da76842 100644 --- a/PReek/Views/PullRequestViews/DisclosureGroupList/PullRequestHeaderView.swift +++ b/PReek/Views/PullRequestViews/DisclosureGroupList/PullRequestHeaderView.swift @@ -106,6 +106,7 @@ struct PullRequestHeaderView: View, Equatable { ResourceIcon(image: .check) .frame(width: 13) .foregroundColor(.success) + .padding(.top, 1) } .help(usersToString(pullRequest.approvalFrom)) } @@ -115,7 +116,7 @@ struct PullRequestHeaderView: View, Equatable { ResourceIcon(image: .fileDiff) .frame(width: 12) .foregroundColor(.failure) - .padding(.top, 1) + .padding(.top, 2) } .help(usersToString(pullRequest.changesRequestedFrom)) } From 2b311083d6ea51ab72aec60da23dda31579f279e Mon Sep 17 00:00:00 2001 From: Max Heidinger Date: Wed, 12 Nov 2025 14:51:03 +0100 Subject: [PATCH 3/4] ci: switch ci to macos 26 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d60b1f5..1dce1b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: jobs: build: name: Build and analyse - runs-on: macos-latest + runs-on: macos-26 strategy: matrix: From 82de47a87c1c638bc1d44cad82dec7ba1648f198 Mon Sep 17 00:00:00 2001 From: Max Heidinger Date: Wed, 12 Nov 2025 14:51:21 +0100 Subject: [PATCH 4/4] fix: memory safety --- PReek/ViewModel/PullRequestsViewModel.swift | 26 +++++++++++++++++---- PReek/Views/Settings/SettingsScreen.swift | 1 - 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/PReek/ViewModel/PullRequestsViewModel.swift b/PReek/ViewModel/PullRequestsViewModel.swift index 1db7b6e..9e086cf 100644 --- a/PReek/ViewModel/PullRequestsViewModel.swift +++ b/PReek/ViewModel/PullRequestsViewModel.swift @@ -28,6 +28,12 @@ class PullRequestsViewModel: ObservableObject { updatePullRequestIndexMap() } + deinit { + timer?.invalidate() + timer = nil + cancellables.removeAll() + } + @Published private(set) var lastUpdated: Date? = nil @Published private(set) var isRefreshing = false @Published var error: Error? = nil @@ -64,6 +70,8 @@ class PullRequestsViewModel: ObservableObject { private let setFocusTrigger = PassthroughSubject() private var pullRequestIndexMap: [String: Int] = [:] + private let maxCacheSize = 500 + private func updatePullRequestIndexMap() { pullRequestIndexMap = Dictionary( pullRequests.enumerated().map { index, pr in (pr.id, index) }, @@ -166,8 +174,8 @@ class PullRequestsViewModel: ObservableObject { if timer != nil { return } - timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { _ in - self.triggerUpdatePullRequests() + timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in + self?.triggerUpdatePullRequests() } } @@ -332,10 +340,20 @@ class PullRequestsViewModel: ObservableObject { let daysToDeduct = (ConfigService.deleteAfterWeeks * 7) + 1 let deleteFrom = Calendar.current.date(byAdding: .day, value: daysToDeduct * -1, to: Date())! - let filteredPullRequestMap = pullRequestMap.filter { _, pullRequest in + var filteredPullRequestMap = pullRequestMap.filter { _, pullRequest in pullRequest.lastUpdated > deleteFrom || (ConfigService.deleteOnlyClosed && !pullRequest.isClosed) } + // Enforce maximum cache size to prevent unbounded memory growth + if filteredPullRequestMap.count > maxCacheSize { + let sortedPRs = filteredPullRequestMap.values.sorted { $0.lastUpdated > $1.lastUpdated } + let keysToKeep = Set(sortedPRs.prefix(maxCacheSize).map { $0.id }) + filteredPullRequestMap = filteredPullRequestMap.filter { keysToKeep.contains($0.key) } + // swiftformat:disable redundantSelf + logger.info("Limiting cache to \(self.maxCacheSize) most recent pull requests") + // swiftformat:enable redundantSelf + } + let filteredPullRequestReadMap = pullRequestReadMap.filter { pullRequestId, _ in filteredPullRequestMap.index(forKey: pullRequestId) != nil } @@ -345,7 +363,7 @@ class PullRequestsViewModel: ObservableObject { logger.info("Removing \(self.pullRequestMap.count - filteredPullRequestMap.count) pull requests") logger.info("Removing \(self.pullRequestReadMap.count - filteredPullRequestReadMap.count) pull requests read info") // swiftformat:enable redundantSelf - await MainActor.run { + await MainActor.run { [filteredPullRequestMap, filteredPullRequestReadMap] in self.pullRequestMap = filteredPullRequestMap self.pullRequestReadMap = filteredPullRequestReadMap } diff --git a/PReek/Views/Settings/SettingsScreen.swift b/PReek/Views/Settings/SettingsScreen.swift index 57b0546..332e168 100644 --- a/PReek/Views/Settings/SettingsScreen.swift +++ b/PReek/Views/Settings/SettingsScreen.swift @@ -12,7 +12,6 @@ func getAppVersion() -> String { return "Unknown" } - struct SettingsScreen: View { @ObservedObject var configViewModel: ConfigViewModel