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 {
88 enum State: String, CaseIterable, Identifiable, Sendable {
99 case open, closed, all
1010 var id: String { rawValue }
11 var label: String { rawValue.capitalized }
11 var label: String { stateLabel(rawValue) }
1212 }
1313
1414 var state: State = .open
gitbay/MRs/MRFilter.swift +8 −6
@@ -10,12 +10,7 @@ nonisolated struct MRFilter: Equatable, Sendable {
1010 case all
1111
1212 var id: String { rawValue }
13 /// Segment text. Five segments share a phone's width, so the
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 }
13 var label: String { stateLabel(rawValue) }
1914 }
2015
2116 var state: State = .open
@@ -45,3 +40,10 @@ nonisolated struct MRFilter: Equatable, Sendable {
4540 return state == .all ? "No \(noun)." : "No \(state.label.lowercased()) \(noun)."
4641 }
4742}
43
44/// A wire state (`open`, `source_gone`) as the app shows it: sentence
45/// case, underscores as spaces.
46nonisolated 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 {
124124 Text(item.updatedAt, format: .relative(presentation: .named))
125125 if item.state == "source_gone" {
126126 Text("·")
127 GBChip("source gone", .secondary)
127 GBChip(stateLabel(item.state), .secondary)
128128 }
129129 }
130130 .font(.gbSans(.caption))
gitbay/Views/Discovery/ExploreView.swift +1 −1
@@ -159,7 +159,7 @@ private struct SearchEntryRow: View {
159159 }
160160 HStack(spacing: 6) {
161161 if let state = result.state {
162 GBChip(state, color(for: state))
162 GBChip(stateLabel(state), color(for: state))
163163 }
164164 Spacer()
165165 if let updatedAt = result.updatedAt {
gitbay/Views/Issues/IssueListView.swift +1 −1
@@ -65,7 +65,7 @@ struct IssueListView: View {
6565 }
6666 .sheet(isPresented: $filtering) {
6767 FilterSheet(
68 title: "Filter Issues",
68 title: "Filter issues",
6969 author: Bindable(model).filter.author,
7070 milestone: Bindable(model).filter.milestone,
7171 milestones: model.milestones,
gitbay/Views/Issues/IssueView.swift +1 −1
@@ -72,7 +72,7 @@ struct IssueView: View {
7272 Text(issue.title)
7373 .font(.gbSans(.headline))
7474 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")
7676 .font(.gbSans(.caption).weight(.medium))
7777 .foregroundStyle(issue.isOpen ? Color.gbOK : Color.gbBad)
7878 Text("by \(issue.author)")
gitbay/Views/MRs/MRListView.swift +3 −3
@@ -32,7 +32,7 @@ struct MRListView: View {
3232 }
3333 .overlay { LoadStateOverlay(state: model.state) }
3434 .searchable(text: Bindable(model).filter.search, prompt: "Search titles and bodies")
35 .navigationTitle("Merge Requests")
35 .navigationTitle("Merge requests")
3636 .navigationBarTitleDisplayMode(.inline)
3737 .toolbar {
3838 ToolbarItem(placement: .topBarTrailing) {
@@ -58,7 +58,7 @@ struct MRListView: View {
5858 }
5959 .sheet(isPresented: $filtering) {
6060 FilterSheet(
61 title: "Filter Merge Requests",
61 title: "Filter merge requests",
6262 author: Bindable(model).filter.author,
6363 milestone: Bindable(model).filter.milestone,
6464 milestones: model.milestones,
@@ -243,7 +243,7 @@ struct MRStateBadge: View {
243243 let state: String
244244
245245 var body: some View {
246 GBChip(state.replacingOccurrences(of: "_", with: " "), color)
246 GBChip(stateLabel(state), color)
247247 }
248248
249249 /// 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 {
5353 Grid(horizontalSpacing: 0, verticalSpacing: 0) {
5454 GridRow {
5555 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",
5757 to: .mergeRequests, count: model.openMRs)
5858 }
5959 GridRow {
gitbayTests/ListFilterTests.swift +11 −1
@@ -107,7 +107,17 @@ struct MRFilterTests {
107107 var f = MRFilter()
108108 f.state = .sourceGone
109109 #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"])
111121 }
112122
113123 @Test func everyStateTheCommandAcceptsIsOffered() {
gitbayUITests/LiveSmokeUITests.swift +1 −1
@@ -412,7 +412,7 @@ final class LiveSmokeUITests: XCTestCase {
412412 XCTAssertTrue(confirm.waitForExistence(timeout: 5), "close confirmation missing")
413413 confirm.tap()
414414
415 XCTAssertTrue(app.staticTexts["closed"].firstMatch
415 XCTAssertTrue(app.staticTexts["Closed"].firstMatch
416416 .waitForExistence(timeout: 15), "MR did not show closed after close")
417417 }
418418