Commit 951501ccb2

951501ccb2e1f826d7b4573c7b6bce1b1582d600

parent: 77cd5ece16

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-07 04:10 UTC

Fix review findings: cheap post-action reload, tautological and last-request test cleanup

RepoDetailViewModel gains reloadDetail(), re-reading repo show only
with try? so a transient failure leaves the last good state in place.
RepoView's bookmark/watch/mute actions call it instead of the full
load() (repo show + dashboard + tree + cat), cutting each action's
request count from 5 to 2. perform() now reuses reloadDetail() instead
of duplicating the read.

Removed noStateOffersAReturnToTheDefault, which held for any WatchAction
implementation regardless of correctness. Fixed
bookmarkingAndUnbookmarkingAreDifferentCommands to identify requests by
argv instead of stub.seen.last, and to assert exactly one request per
action.

Layout: unified · split

gitbay/Repos/RepoDetailViewModel.swift +10 −3
@@ -44,6 +44,15 @@ final class RepoDetailViewModel {
4444 isPinned = dashboard.pinned.contains { $0.path == path }
4545 }
4646
47 /// Re-reads `repo show` only, leaving the last good state in place on
48 /// failure. For refreshing after an action performed elsewhere
49 /// (bookmark, watch, mute) without paying for a full screen load.
50 func reloadDetail() async {
51 if let detail = try? await client.read(["repo", "show", path], as: RepoDetail.self) {
52 state = .loaded(detail)
53 }
54 }
55
4756 // MARK: - Management actions
4857
4958 func setPinned(_ pinned: Bool) async {
@@ -61,9 +70,7 @@ final class RepoDetailViewModel {
6170 defer { working = false }
6271 do {
6372 try await client.run(argv)
64 if let detail = try? await client.read(["repo", "show", path], as: RepoDetail.self) {
65 state = .loaded(detail)
66 }
73 await reloadDetail()
6774 } catch let error as GitbayError {
6875 actionError = error.userFacingMessage
6976 } catch {
gitbay/Views/Repos/RepoView.swift +3 −3
@@ -152,7 +152,7 @@ struct RepoView: View {
152152 Button {
153153 Task {
154154 await actionsModel.setBookmarked(!detail.bookmarked)
155 await model.load()
155 await model.reloadDetail()
156156 }
157157 } label: {
158158 Label(detail.bookmarked ? "Unbookmark" : "Bookmark",
@@ -173,7 +173,7 @@ struct RepoView: View {
173173 Button {
174174 Task {
175175 await actionsModel.watch()
176 await model.load()
176 await model.reloadDetail()
177177 }
178178 } label: {
179179 Label("Watch this repository", systemImage: "eye")
@@ -183,7 +183,7 @@ struct RepoView: View {
183183 Button {
184184 Task {
185185 await actionsModel.unwatch()
186 await model.load()
186 await model.reloadDetail()
187187 }
188188 } label: {
189189 Label("Mute this repository", systemImage: "bell.slash")
gitbayTests/RepoActionTests.swift +5 −2
@@ -30,12 +30,15 @@ struct RepoActionsTests {
3030
3131 stub.enqueue(.init(status: 200, json: ok))
3232 await model.setBookmarked(false)
33 #expect(try argvOf(try #require(stub.seen.last)) == ["repo", "unbookmark", "krz/gitbay"])
33 #expect(stub.seen.contains { (try? argvOf($0)) == ["repo", "unbookmark", "krz/gitbay"] })
34 #expect(stub.seen.count == 1)
3435
3536 let model2 = RepoActionsViewModel(client: client, repoPath: "krz/other")
37 let before = stub.seen.count
3638 stub.enqueue(.init(status: 200, json: ok))
3739 await model2.setBookmarked(true)
38 #expect(try argvOf(try #require(stub.seen.last)) == ["repo", "bookmark", "krz/other"])
40 #expect(stub.seen.contains { (try? argvOf($0)) == ["repo", "bookmark", "krz/other"] })
41 #expect(stub.seen.count == before + 1)
3942 }
4043
4144 @Test func watchAndUnwatchSendTheirOwnCommands() async throws {
gitbayTests/RepoStateTests.swift −6
@@ -53,10 +53,4 @@ struct WatchStateTests {
5353 #expect(WatchState.muted.available == [.watch])
5454 #expect(WatchState.default.available == [.watch, .mute])
5555 }
56
57 @Test func noStateOffersAReturnToTheDefault() {
58 for state in [WatchState.watching, .muted, .default] {
59 #expect(state.available.allSatisfy { $0 == .watch || $0 == .mute })
60 }
61 }
6256}