krz/octosentry

macOS menu bar app to monitor GitHub security alerts github macos menubar security

Commit 91f87b483b

91f87b483b12d57711fb1372a31a9e8e9e141207

parent: 0e94907674

Unsigned

cmc <hello@cleberg.net> · 2026-08-22 23:02 UTC
committer: <noreply@github.com>

Start new installs with an empty watch list (#45)

The placeholder seeded every fresh install with ccleberg/cleberg.net,
so a new user's first fetch was someone else's repo, usually failing.
The empty state now points at the gear menu instead of claiming there
are no open alerts.

Refresh no longer treats an empty watch list as a complete poll. It
carries no information, and counting it as complete would prune every
dismissal and record every tracked alert as resolved — reachable before
by removing all repos, and the default path now.

Layout: unified · split

octosentry/PersistedState.swift +4 −1
@@ -93,8 +93,11 @@ nonisolated struct PersistedState: Codable {
9393 history = try container.decodeIfPresent(AlertHistory.self, forKey: .history) ?? AlertHistory()
9494 }
9595
96 /// A fresh install watches nothing until the user adds a repo. Seeding a
97 /// specific repo here meant every new install started by fetching someone
98 /// else's alerts, which it usually can't read.
9699 static let placeholder = PersistedState(
97 watchedRepos: [WatchedRepo(fullName: "ccleberg/cleberg.net", accountID: 0)],
100 watchedRepos: [],
98101 seenEventIDs: [],
99102 lastFetchByRepo: [:],
100103 minimumSeverity: .low
octosentry/SecurityEventListView.swift +8
@@ -277,6 +277,14 @@ struct SecurityEventListView: View {
277277 tint: .secondary,
278278 message: "\(store.totalFetchedCount) alert(s) are below your minimum severity filter"
279279 )
280 } else if store.watchedRepos.isEmpty {
281 // "No open alerts" would be misleading when nothing is
282 // being watched in the first place.
283 StatusView(
284 systemImage: "plus.circle",
285 tint: .secondary,
286 message: "No repositories watched yet — add one from the gear menu"
287 )
280288 } else {
281289 StatusView(systemImage: "checkmark.shield", tint: .green, message: "No open security alerts")
282290 }
octosentry/SecurityEventStore.swift +5 −1
@@ -172,7 +172,11 @@ final class SecurityEventStore {
172172
173173 // Only prune against a complete picture: if a repo failed this round
174174 // its alerts are missing, and pruning would forget they were hidden.
175 if fetchedEventsByRepo.count == Set(state.watchedRepos.map(\.fullName)).count {
175 // An empty watch list is not a complete picture either — it carries no
176 // information, and treating it as one would drop every dismissal and
177 // record every tracked alert as resolved.
178 let watchedRepoNames = Set(state.watchedRepos.map(\.fullName))
179 if !watchedRepoNames.isEmpty, fetchedEventsByRepo.count == watchedRepoNames.count {
176180 let now = Date()
177181 state.triage = state.triage.pruned(
178182 presentEventIDs: Set(fetchedEvents.map(\.id)),
octosentryTests/PersistedStateTests.swift +5
@@ -112,6 +112,11 @@ struct PersistedStateTests {
112112 }
113113 }
114114
115 // A fresh install must not arrive watching somebody else's repo.
116 @Test func placeholderWatchesNothing() {
117 #expect(PersistedState.placeholder.watchedRepos.isEmpty)
118 }
119
115120 @Test func placeholderStartsWithNoSeenStateAndNoRepoScope() {
116121 #expect(PersistedState.placeholder.seenEventIDs.isEmpty)
117122 #expect(PersistedState.placeholder.lastFetchByRepo.isEmpty)