krz/octosentry
macOS menu bar app to monitor GitHub security alerts
clone: git clone https://gitbay.org/krz/octosentry.git
88899df0f707d7140e72b43fefc47f97f1c35884
signed_unknown_key
author: Christian Cleberg <hello@cleberg.net> · 2026-08-22T23:02:04Z
committer: <noreply@github.com>
octosentry/PersistedState.swift | 5 ++++- octosentry/SecurityEventListView.swift | 8 ++++++++ octosentry/SecurityEventStore.swift | 6 +++++- octosentryTests/PersistedStateTests.swift | 5 +++++ 4 files changed, 22 insertions(+), 2 deletions(-) @@ -93,8 +93,11 @@ nonisolated struct PersistedState: Codable { history = try container.decodeIfPresent(AlertHistory.self, forKey: .history) ?? AlertHistory() } + /// A fresh install watches nothing until the user adds a repo. Seeding a + /// specific repo here meant every new install started by fetching someone + /// else's alerts, which it usually can't read. static let placeholder = PersistedState( - watchedRepos: [WatchedRepo(fullName: "ccleberg/cleberg.net", accountID: 0)], + watchedRepos: [], seenEventIDs: [], lastFetchByRepo: [:], minimumSeverity: .low @@ -277,6 +277,14 @@ struct SecurityEventListView: View { tint: .secondary, message: "\(store.totalFetchedCount) alert(s) are below your minimum severity filter" ) + } else if store.watchedRepos.isEmpty { + // "No open alerts" would be misleading when nothing is + // being watched in the first place. + StatusView( + systemImage: "plus.circle", + tint: .secondary, + message: "No repositories watched yet — add one from the gear menu" + ) } else { StatusView(systemImage: "checkmark.shield", tint: .green, message: "No open security alerts") } @@ -172,7 +172,11 @@ final class SecurityEventStore { // Only prune against a complete picture: if a repo failed this round // its alerts are missing, and pruning would forget they were hidden. - if fetchedEventsByRepo.count == Set(state.watchedRepos.map(\.fullName)).count { + // An empty watch list is not a complete picture either — it carries no + // information, and treating it as one would drop every dismissal and + // record every tracked alert as resolved. + let watchedRepoNames = Set(state.watchedRepos.map(\.fullName)) + if !watchedRepoNames.isEmpty, fetchedEventsByRepo.count == watchedRepoNames.count { let now = Date() state.triage = state.triage.pruned( presentEventIDs: Set(fetchedEvents.map(\.id)), @@ -112,6 +112,11 @@ struct PersistedStateTests { } } + // A fresh install must not arrive watching somebody else's repo. + @Test func placeholderWatchesNothing() { + #expect(PersistedState.placeholder.watchedRepos.isEmpty) + } + @Test func placeholderStartsWithNoSeenStateAndNoRepoScope() { #expect(PersistedState.placeholder.seenEventIDs.isEmpty) #expect(PersistedState.placeholder.lastFetchByRepo.isEmpty)