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 | 8 | enum State: String, CaseIterable, Identifiable, Sendable { |
| 9 | 9 | case open, closed, all |
| 10 | 10 | var id: String { rawValue } |
| 11 | | var label: String { rawValue.capitalized } |
| 11 | var label: String { stateLabel(rawValue) } |
| 12 | 12 | } |
| 13 | 13 | |
| 14 | 14 | var state: State = .open |
gitbay/MRs/MRFilter.swift
+8 −6
| @@ -10,12 +10,7 @@ nonisolated struct MRFilter: Equatable, Sendable { |
| 10 | 10 | case all |
| 11 | 11 | |
| 12 | 12 | 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) } |
| 19 | 14 | } |
| 20 | 15 | |
| 21 | 16 | var state: State = .open |
| @@ -45,3 +40,10 @@ nonisolated struct MRFilter: Equatable, Sendable { |
| 45 | 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 | 124 | Text(item.updatedAt, format: .relative(presentation: .named)) |
| 125 | 125 | if item.state == "source_gone" { |
| 126 | 126 | Text("·") |
| 127 | | GBChip("source gone", .secondary) |
| 127 | GBChip(stateLabel(item.state), .secondary) |
| 128 | 128 | } |
| 129 | 129 | } |
| 130 | 130 | .font(.gbSans(.caption)) |
gitbay/Views/Discovery/ExploreView.swift
+1 −1
| @@ -159,7 +159,7 @@ private struct SearchEntryRow: View { |
| 159 | 159 | } |
| 160 | 160 | HStack(spacing: 6) { |
| 161 | 161 | if let state = result.state { |
| 162 | | GBChip(state, color(for: state)) |
| 162 | GBChip(stateLabel(state), color(for: state)) |
| 163 | 163 | } |
| 164 | 164 | Spacer() |
| 165 | 165 | if let updatedAt = result.updatedAt { |
gitbay/Views/Issues/IssueListView.swift
+1 −1
| @@ -65,7 +65,7 @@ struct IssueListView: View { |
| 65 | 65 | } |
| 66 | 66 | .sheet(isPresented: $filtering) { |
| 67 | 67 | FilterSheet( |
| 68 | | title: "Filter Issues", |
| 68 | title: "Filter issues", |
| 69 | 69 | author: Bindable(model).filter.author, |
| 70 | 70 | milestone: Bindable(model).filter.milestone, |
| 71 | 71 | milestones: model.milestones, |
gitbay/Views/Issues/IssueView.swift
+1 −1
| @@ -72,7 +72,7 @@ struct IssueView: View { |
| 72 | 72 | Text(issue.title) |
| 73 | 73 | .font(.gbSans(.headline)) |
| 74 | 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 | 76 | .font(.gbSans(.caption).weight(.medium)) |
| 77 | 77 | .foregroundStyle(issue.isOpen ? Color.gbOK : Color.gbBad) |
| 78 | 78 | Text("by \(issue.author)") |
gitbay/Views/MRs/MRListView.swift
+3 −3
| @@ -32,7 +32,7 @@ struct MRListView: View { |
| 32 | 32 | } |
| 33 | 33 | .overlay { LoadStateOverlay(state: model.state) } |
| 34 | 34 | .searchable(text: Bindable(model).filter.search, prompt: "Search titles and bodies") |
| 35 | | .navigationTitle("Merge Requests") |
| 35 | .navigationTitle("Merge requests") |
| 36 | 36 | .navigationBarTitleDisplayMode(.inline) |
| 37 | 37 | .toolbar { |
| 38 | 38 | ToolbarItem(placement: .topBarTrailing) { |
| @@ -58,7 +58,7 @@ struct MRListView: View { |
| 58 | 58 | } |
| 59 | 59 | .sheet(isPresented: $filtering) { |
| 60 | 60 | FilterSheet( |
| 61 | | title: "Filter Merge Requests", |
| 61 | title: "Filter merge requests", |
| 62 | 62 | author: Bindable(model).filter.author, |
| 63 | 63 | milestone: Bindable(model).filter.milestone, |
| 64 | 64 | milestones: model.milestones, |
| @@ -243,7 +243,7 @@ struct MRStateBadge: View { |
| 243 | 243 | let state: String |
| 244 | 244 | |
| 245 | 245 | var body: some View { |
| 246 | | GBChip(state.replacingOccurrences(of: "_", with: " "), color) |
| 246 | GBChip(stateLabel(state), color) |
| 247 | 247 | } |
| 248 | 248 | |
| 249 | 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 | 53 | Grid(horizontalSpacing: 0, verticalSpacing: 0) { |
| 54 | 54 | GridRow { |
| 55 | 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 | 57 | to: .mergeRequests, count: model.openMRs) |
| 58 | 58 | } |
| 59 | 59 | GridRow { |
gitbayTests/ListFilterTests.swift
+11 −1
| @@ -107,7 +107,17 @@ struct MRFilterTests { |
| 107 | 107 | var f = MRFilter() |
| 108 | 108 | f.state = .sourceGone |
| 109 | 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 | 123 | @Test func everyStateTheCommandAcceptsIsOffered() { |
gitbayUITests/LiveSmokeUITests.swift
+1 −1
| @@ -412,7 +412,7 @@ final class LiveSmokeUITests: XCTestCase { |
| 412 | 412 | XCTAssertTrue(confirm.waitForExistence(timeout: 5), "close confirmation missing") |
| 413 | 413 | confirm.tap() |
| 414 | 414 | |
| 415 | | XCTAssertTrue(app.staticTexts["closed"].firstMatch |
| 415 | XCTAssertTrue(app.staticTexts["Closed"].firstMatch |
| 416 | 416 | .waitForExistence(timeout: 15), "MR did not show closed after close") |
| 417 | 417 | } |
| 418 | 418 | |