Commit 5bf7b91cd7
5bf7b91cd757507e1fba8fea710933430baae9fa
parent: 3b4a8cfe33
Verified · cmc
cmc <hello@cleberg.net> · 2026-09-30 02:10 UTC
Sentence case for states and merge requests
Issue and MR states read Open, Merged, Closed, Source gone in filters,
chips and headers. The MR filter says Source gone instead of Gone.
Layout: unified · split
gitbay/Issues/IssueFilter.swift
+1 −1
| @@ -8,7 +8,7 @@ nonisolated struct IssueFilter: Equatable, Sendable { |
| 8 | enum State: String, CaseIterable, Identifiable, Sendable { |
8 | enum State: String, CaseIterable, Identifiable, Sendable { |
| 9 | case open, closed, all |
9 | case open, closed, all |
| 10 | var id: String { rawValue } |
10 | var id: String { rawValue } |
| 11 | var label: String { rawValue.capitalized } |
11 | var label: String { stateLabel(rawValue) } |
| 12 | } |
12 | } |
| 13 | |
13 | |
| 14 | var state: State = .open |
14 | var state: State = .open |
gitbay/MRs/MRFilter.swift
+8 −6
| @@ -10,12 +10,7 @@ nonisolated struct MRFilter: Equatable, Sendable { |
| 10 | case all |
10 | case all |
| 11 | |
11 | |
| 12 | var id: String { rawValue } |
12 | var id: String { rawValue } |
| 13 | /// Segment text. Five segments share a phone's width, so the |
13 | var label: String { stateLabel(rawValue) } |
| 14 | /// source-gone state is one word; the rows and chips still say |
| |
| 15 | /// "source gone" in full. |
| |
| 16 | var label: String { |
| |
| 17 | self == .sourceGone ? "Gone" : rawValue.capitalized |
| |
| 18 | } |
| |
| 19 | } |
14 | } |
| 20 | |
15 | |
| 21 | var state: State = .open |
16 | var state: State = .open |
| @@ -45,3 +40,10 @@ nonisolated struct MRFilter: Equatable, Sendable { |
| 45 | return state == .all ? "No \(noun)." : "No \(state.label.lowercased()) \(noun)." |
40 | return state == .all ? "No \(noun)." : "No \(state.label.lowercased()) \(noun)." |
| 46 | } |
41 | } |
| 47 | } |
42 | } |
| |
43 | |
| |
44 | /// A wire state (`open`, `source_gone`) as the app shows it: sentence |
| |
45 | /// case, underscores as spaces. |
| |
46 | nonisolated func stateLabel(_ state: String) -> String { |
| |
47 | let words = state.replacingOccurrences(of: "_", with: " ") |
| |
48 | return words.prefix(1).uppercased() + words.dropFirst() |
| |
49 | } |
gitbay/Views/Dashboard/DashboardView.swift
+1 −1
| @@ -124,7 +124,7 @@ struct DashboardView: View { |
| 124 | Text(item.updatedAt, format: .relative(presentation: .named)) |
124 | Text(item.updatedAt, format: .relative(presentation: .named)) |
| 125 | if item.state == "source_gone" { |
125 | if item.state == "source_gone" { |
| 126 | Text("·") |
126 | Text("·") |
| 127 | GBChip("source gone", .secondary) |
127 | GBChip(stateLabel(item.state), .secondary) |
| 128 | } |
128 | } |
| 129 | } |
129 | } |
| 130 | .font(.gbSans(.caption)) |
130 | .font(.gbSans(.caption)) |
gitbay/Views/Discovery/ExploreView.swift
+1 −1
| @@ -159,7 +159,7 @@ private struct SearchEntryRow: View { |
| 159 | } |
159 | } |
| 160 | HStack(spacing: 6) { |
160 | HStack(spacing: 6) { |
| 161 | if let state = result.state { |
161 | if let state = result.state { |
| 162 | GBChip(state, color(for: state)) |
162 | GBChip(stateLabel(state), color(for: state)) |
| 163 | } |
163 | } |
| 164 | Spacer() |
164 | Spacer() |
| 165 | if let updatedAt = result.updatedAt { |
165 | if let updatedAt = result.updatedAt { |
gitbay/Views/Issues/IssueListView.swift
+1 −1
| @@ -65,7 +65,7 @@ struct IssueListView: View { |
| 65 | } |
65 | } |
| 66 | .sheet(isPresented: $filtering) { |
66 | .sheet(isPresented: $filtering) { |
| 67 | FilterSheet( |
67 | FilterSheet( |
| 68 | title: "Filter Issues", |
68 | title: "Filter issues", |
| 69 | author: Bindable(model).filter.author, |
69 | author: Bindable(model).filter.author, |
| 70 | milestone: Bindable(model).filter.milestone, |
70 | milestone: Bindable(model).filter.milestone, |
| 71 | milestones: model.milestones, |
71 | milestones: model.milestones, |
gitbay/Views/Issues/IssueView.swift
+1 −1
| @@ -72,7 +72,7 @@ struct IssueView: View { |
| 72 | Text(issue.title) |
72 | Text(issue.title) |
| 73 | .font(.gbSans(.headline)) |
73 | .font(.gbSans(.headline)) |
| 74 | HStack(spacing: 6) { |
74 | HStack(spacing: 6) { |
| 75 | Label(issue.state, systemImage: issue.isOpen ? "circle" : "checkmark.circle.fill") |
75 | Label(stateLabel(issue.state), systemImage: issue.isOpen ? "circle" : "checkmark.circle.fill") |
| 76 | .font(.gbSans(.caption).weight(.medium)) |
76 | .font(.gbSans(.caption).weight(.medium)) |
| 77 | .foregroundStyle(issue.isOpen ? Color.gbOK : Color.gbBad) |
77 | .foregroundStyle(issue.isOpen ? Color.gbOK : Color.gbBad) |
| 78 | Text("by \(issue.author)") |
78 | Text("by \(issue.author)") |
gitbay/Views/MRs/MRListView.swift
+3 −3
| @@ -32,7 +32,7 @@ struct MRListView: View { |
| 32 | } |
32 | } |
| 33 | .overlay { LoadStateOverlay(state: model.state) } |
33 | .overlay { LoadStateOverlay(state: model.state) } |
| 34 | .searchable(text: Bindable(model).filter.search, prompt: "Search titles and bodies") |
34 | .searchable(text: Bindable(model).filter.search, prompt: "Search titles and bodies") |
| 35 | .navigationTitle("Merge Requests") |
35 | .navigationTitle("Merge requests") |
| 36 | .navigationBarTitleDisplayMode(.inline) |
36 | .navigationBarTitleDisplayMode(.inline) |
| 37 | .toolbar { |
37 | .toolbar { |
| 38 | ToolbarItem(placement: .topBarTrailing) { |
38 | ToolbarItem(placement: .topBarTrailing) { |
| @@ -58,7 +58,7 @@ struct MRListView: View { |
| 58 | } |
58 | } |
| 59 | .sheet(isPresented: $filtering) { |
59 | .sheet(isPresented: $filtering) { |
| 60 | FilterSheet( |
60 | FilterSheet( |
| 61 | title: "Filter Merge Requests", |
61 | title: "Filter merge requests", |
| 62 | author: Bindable(model).filter.author, |
62 | author: Bindable(model).filter.author, |
| 63 | milestone: Bindable(model).filter.milestone, |
63 | milestone: Bindable(model).filter.milestone, |
| 64 | milestones: model.milestones, |
64 | milestones: model.milestones, |
| @@ -243,7 +243,7 @@ struct MRStateBadge: View { |
| 243 | let state: String |
243 | let state: String |
| 244 | |
244 | |
| 245 | var body: some View { |
245 | var body: some View { |
| 246 | GBChip(state.replacingOccurrences(of: "_", with: " "), color) |
246 | GBChip(stateLabel(state), color) |
| 247 | } |
247 | } |
| 248 | |
248 | |
| 249 | /// The web's chip mapping: open=ok, merged=done, closed=bad, |
249 | /// The web's chip mapping: open=ok, merged=done, closed=bad, |
gitbay/Views/Repos/RepoView.swift
+1 −1
| @@ -53,7 +53,7 @@ struct RepoView: View { |
| 53 | Grid(horizontalSpacing: 0, verticalSpacing: 0) { |
53 | Grid(horizontalSpacing: 0, verticalSpacing: 0) { |
| 54 | GridRow { |
54 | GridRow { |
| 55 | tile("Files", systemImage: "folder", id: "files", to: .files) |
55 | tile("Files", systemImage: "folder", id: "files", to: .files) |
| 56 | tile("Merge Requests", systemImage: "arrow.triangle.merge", id: "merge-requests", |
56 | tile("Merge requests", systemImage: "arrow.triangle.merge", id: "merge-requests", |
| 57 | to: .mergeRequests, count: model.openMRs) |
57 | to: .mergeRequests, count: model.openMRs) |
| 58 | } |
58 | } |
| 59 | GridRow { |
59 | GridRow { |
gitbayTests/ListFilterTests.swift
+11 −1
| @@ -107,7 +107,17 @@ struct MRFilterTests { |
| 107 | var f = MRFilter() |
107 | var f = MRFilter() |
| 108 | f.state = .sourceGone |
108 | f.state = .sourceGone |
| 109 | #expect(f.flags() == ["--state", "source_gone"]) |
109 | #expect(f.flags() == ["--state", "source_gone"]) |
| 110 | #expect(MRFilter.State.sourceGone.label == "Gone") |
110 | #expect(MRFilter.State.sourceGone.label == "Source gone") |
| |
111 | } |
| |
112 | |
| |
113 | /// Filters, chips and headers all say a state the same way. |
| |
114 | @Test func statesReadInSentenceCase() { |
| |
115 | #expect(stateLabel("open") == "Open") |
| |
116 | #expect(stateLabel("merged") == "Merged") |
| |
117 | #expect(stateLabel("source_gone") == "Source gone") |
| |
118 | #expect(MRFilter.State.allCases.map(\.label) |
| |
119 | == ["Open", "Merged", "Closed", "Source gone", "All"]) |
| |
120 | #expect(IssueFilter.State.allCases.map(\.label) == ["Open", "Closed", "All"]) |
| 111 | } |
121 | } |
| 112 | |
122 | |
| 113 | @Test func everyStateTheCommandAcceptsIsOffered() { |
123 | @Test func everyStateTheCommandAcceptsIsOffered() { |
gitbayUITests/LiveSmokeUITests.swift
+1 −1
| @@ -412,7 +412,7 @@ final class LiveSmokeUITests: XCTestCase { |
| 412 | XCTAssertTrue(confirm.waitForExistence(timeout: 5), "close confirmation missing") |
412 | XCTAssertTrue(confirm.waitForExistence(timeout: 5), "close confirmation missing") |
| 413 | confirm.tap() |
413 | confirm.tap() |
| 414 | |
414 | |
| 415 | XCTAssertTrue(app.staticTexts["closed"].firstMatch |
415 | XCTAssertTrue(app.staticTexts["Closed"].firstMatch |
| 416 | .waitForExistence(timeout: 15), "MR did not show closed after close") |
416 | .waitForExistence(timeout: 15), "MR did not show closed after close") |
| 417 | } |
417 | } |
| 418 | |
418 | |