a native ios client for gitbay

client ios swift

https://gitbay.org

Commit 4e2a19cc30

4e2a19cc3025c14956a9186a524fa25e5e81ead3

parent: 0f215ef0ee

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-28T03:12:30Z

audit the new UI against style.css

Checking the graph turned up more of the same, so I diffed every
visual decision against the stylesheet rather than the one you saw.

Wrong, now fixed:
- Chips: eleven places filled with the system's quaternary gray, which
  has no border and is not the .chip recipe. All are GBChip now. Two
  carried the wrong semantic besides: topics are --accent on the web,
  and a stale review or thread is --warn, not neutral.
- Notices: seventeen ad-hoc Labels in assorted colors. The web's idiom
  is a 3px stripe down the left edge — .error uses --bad, .notice --ok
  — so GBNotice does that and every banner uses it. The truncated-file
  banner was a --yellow wash, a color the palette does not contain.
- Hunk headers: I changed these to secondary during the design pass;
  the stylesheet colors them --accent and rules them with --line above
  and below. Restored, with the rules.
- Blame: the attribution ran on a filled band. .blamehunk separates
  with a --line rule and no fill.

Already correct, confirmed rather than assumed: all ten ported color
tokens match style.css exactly in both schemes, every radius is 2px as
--r-* prescribes, and every font goes through the Atkinson ramp.

New tokens the audit needed: --line and --bg.

Ref #11
gitbay/Theme/Theme.swift +28
@@ -37,6 +37,8 @@ extension Color {
3737 /// --bg: the page ground the accent is mixed toward. Chrome uses the
3838 /// system background; this is for blends that must match the web.
3939 nonisolated static let gbBackground = paired(0xFFFFFF, 0x0A0A0A)
40 /// --line: control borders and table edges.
41 nonisolated static let gbLine = paired(0xD4D4D4, 0x2E2E2E)
4042 }
4143
4244 extension UIColor {
@@ -101,6 +103,32 @@ enum GitbayFonts {
101103 }
102104 }
103105
106/// The web's notice idiom: a 3px stripe in the state's color down the
107/// left edge, not a tinted fill. `.error` uses --bad, `.notice` --ok;
108/// anything the server refused is --warn.
109struct GBNotice: View {
110 let text: String
111 let color: Color
112
113 init(_ text: String, _ color: Color = .gbBad) {
114 self.text = text
115 self.color = color
116 }
117
118 var body: some View {
119 HStack(alignment: .top, spacing: 10) {
120 Rectangle()
121 .fill(color)
122 .frame(width: 3)
123 Text(text)
124 .font(.gbSans(.subheadline))
125 .frame(maxWidth: .infinity, alignment: .leading)
126 }
127 .fixedSize(horizontal: false, vertical: true)
128 .padding(.vertical, 2)
129 }
130}
131
104132 /// The web's `.chip` recipe: state color, a 7% tint ground, a 35% tint
105133 /// border, 2px radius.
106134 struct GBChip: View {
gitbay/Views/Account/AccountView.swift +5 −18
@@ -27,11 +27,7 @@ struct AccountView: View {
2727 Label(membership.org, systemImage: "building.2")
2828 .font(.gbSans(.subheadline))
2929 Spacer()
30 Text(membership.role)
31 .font(.gbSans(.caption))
32 .padding(.horizontal, 6)
33 .padding(.vertical, 2)
34 .background(.quaternary, in: gbChipShape)
30 GBChip(membership.role, .secondary)
3531 }
3632 }
3733 }
@@ -44,16 +40,12 @@ struct AccountView: View {
4440 // scrolled out of existence.
4541 if let error = model.actionError {
4642 Section {
47 Label(error, systemImage: "hand.raised")
48 .foregroundStyle(Color.gbWarn)
49 .font(.gbSans(.subheadline))
43 GBNotice(error, .gbWarn)
5044 }
5145 }
5246 if let notice = model.notice {
5347 Section {
54 Label(notice, systemImage: "envelope")
55 .foregroundStyle(.secondary)
56 .font(.gbSans(.subheadline))
48 GBNotice(notice, .gbOK)
5749 }
5850 }
5951 emailSection
@@ -136,10 +128,7 @@ struct AccountView: View {
136128 .truncationMode(.middle)
137129 HStack(spacing: 6) {
138130 Text(key.algo)
139 Text(key.scope)
140 .padding(.horizontal, 5)
141 .padding(.vertical, 1)
142 .background(.quaternary, in: gbChipShape)
131 GBChip(key.scope, .secondary)
143132 }
144133 .font(.gbSans(.caption2))
145134 .foregroundStyle(.secondary)
@@ -273,9 +262,7 @@ private struct KeyPasteSheet: View {
273262 }
274263 if let errorMessage {
275264 Section {
276 Label(errorMessage, systemImage: "exclamationmark.triangle")
277 .foregroundStyle(Color.gbBad)
278 .font(.gbSans(.subheadline))
265 GBNotice(errorMessage)
279266 }
280267 }
281268 }
gitbay/Views/Builds/BuildListView.swift +1 −3
@@ -14,9 +14,7 @@ struct BuildListView: View {
1414 List {
1515 if let error = model.actionError {
1616 Section {
17 Label(error, systemImage: "hand.raised")
18 .foregroundStyle(Color.gbWarn)
19 .font(.gbSans(.subheadline))
17 GBNotice(error, .gbWarn)
2018 }
2119 }
2220 ForEach(model.state.value ?? []) { build in
gitbay/Views/Discovery/ProfileView.swift +1 −1
@@ -40,7 +40,7 @@ struct ProfileView: View {
4040 VStack(alignment: .leading, spacing: 8) {
4141 HStack(spacing: 8) {
4242 Image(systemName: profile.isOrg ? "building.2" : "person.crop.circle")
43 .font(.title2)
43 .font(.gbSans(.title2))
4444 .foregroundStyle(Color.gbAccent)
4545 Text(profile.name)
4646 .font(.gbSans(.title3).weight(.semibold))
gitbay/Views/Issues/IssueListView.swift +1 −5
@@ -86,11 +86,7 @@ private struct IssueRow: View {
8686 .font(.gbSans(.caption2))
8787 .foregroundStyle(issue.isOpen ? Color.gbOK : Color.gbBad)
8888 ForEach(issue.labels ?? [], id: \.self) { label in
89 Text(label)
90 .font(.gbSans(.caption2))
91 .padding(.horizontal, 5)
92 .padding(.vertical, 1)
93 .background(.quaternary, in: gbChipShape)
89 GBChip(label, .secondary)
9490 }
9591 Spacer()
9692 if let assignees = issue.assignees, !assignees.isEmpty {
gitbay/Views/Issues/IssueView.swift +3 −4
@@ -23,9 +23,7 @@ struct IssueView: View {
2323
2424 if let error = model.actionError {
2525 Section {
26 Label(error, systemImage: "hand.raised")
27 .foregroundStyle(Color.gbWarn)
28 .font(.gbSans(.subheadline))
26 GBNotice(error, .gbWarn)
2927 }
3028 }
3129
@@ -170,7 +168,8 @@ struct IssueView: View {
170168 .font(.gbSans(.caption))
171169 .padding(.horizontal, 8)
172170 .padding(.vertical, 3)
173 .background(.quaternary, in: gbChipShape)
171 .background(Color.secondary.opacity(0.07), in: gbChipShape)
172 .overlay(gbChipShape.stroke(Color.secondary.opacity(0.35), lineWidth: 1))
174173 }
175174 }
176175 }
gitbay/Views/MRs/DiffView.swift +10 −2
@@ -103,12 +103,20 @@ private struct HunkView: View {
103103 var body: some View {
104104 ScrollView(.horizontal) {
105105 VStack(alignment: .leading, spacing: 0) {
106 // The stylesheet colors the hunk header with --accent on
107 // --fill-subtle, ruled above and below with --line.
106108 Text(hunk.header)
107109 .font(.gbMono(.caption2))
108 .foregroundStyle(.secondary)
109 .padding(.vertical, 2)
110 .foregroundStyle(Color.gbAccent)
111 .padding(.vertical, 3)
110112 .frame(maxWidth: .infinity, alignment: .leading)
111113 .background(Color.gbFillSubtle)
114 .overlay(alignment: .top) {
115 Rectangle().fill(Color.gbLine).frame(height: 1)
116 }
117 .overlay(alignment: .bottom) {
118 Rectangle().fill(Color.gbLine).frame(height: 1)
119 }
112120 ForEach(hunk.lines) { line in
113121 LineView(line: line)
114122 if let model {
gitbay/Views/MRs/MRListView.swift +1 −3
@@ -93,9 +93,7 @@ private struct MRCreateSheet: View {
9393 }
9494 if let error = model.errorMessage {
9595 Section {
96 Label(error, systemImage: "exclamationmark.triangle")
97 .foregroundStyle(Color.gbBad)
98 .font(.gbSans(.subheadline))
96 GBNotice(error)
9997 }
10098 }
10199 }
gitbay/Views/MRs/MRView.swift +3 −13
@@ -23,9 +23,7 @@ struct MRView: View {
2323
2424 if let error = model.actionError {
2525 Section {
26 Label(error, systemImage: "hand.raised")
27 .foregroundStyle(Color.gbWarn)
28 .font(.gbSans(.subheadline))
26 GBNotice(error, .gbWarn)
2927 }
3028 }
3129
@@ -180,11 +178,7 @@ struct MRView: View {
180178 .font(.gbSans(.subheadline))
181179 Spacer()
182180 if review.stale {
183 Text("stale")
184 .font(.gbSans(.caption2))
185 .padding(.horizontal, 5)
186 .padding(.vertical, 1)
187 .background(.quaternary, in: gbChipShape)
181 GBChip("stale", .gbWarn)
188182 }
189183 }
190184 }
@@ -316,11 +310,7 @@ struct ReviewThreadView: View {
316310 .font(.gbMono(.caption))
317311 .lineLimit(1)
318312 if thread.stale {
319 Text("stale")
320 .font(.gbSans(.caption2))
321 .padding(.horizontal, 5)
322 .padding(.vertical, 1)
323 .background(.quaternary, in: gbChipShape)
313 GBChip("stale", .gbWarn)
324314 }
325315 Spacer()
326316 Button(thread.isResolved ? "Reopen" : "Resolve") {
gitbay/Views/Orgs/OrgView.swift +2 −8
@@ -20,9 +20,7 @@ struct OrgView: View {
2020 if let loaded = model.state.value {
2121 if let error = model.actionError {
2222 Section {
23 Label(error, systemImage: "hand.raised")
24 .foregroundStyle(Color.gbWarn)
25 .font(.gbSans(.subheadline))
23 GBNotice(error, .gbWarn)
2624 }
2725 }
2826 membersSection(loaded.members)
@@ -79,11 +77,7 @@ struct OrgView: View {
7977 Button("Admin") { Task { await model.setRole(member, role: "admin") } }
8078 Button("Member") { Task { await model.setRole(member, role: "member") } }
8179 } label: {
82 Text(member.role)
83 .font(.gbSans(.caption))
84 .padding(.horizontal, 6)
85 .padding(.vertical, 2)
86 .background(.quaternary, in: gbChipShape)
80 GBChip(member.role, .secondary)
8781 }
8882 .disabled(model.working)
8983 }
gitbay/Views/Orgs/TeamView.swift +2 −8
@@ -17,9 +17,7 @@ struct TeamView: View {
1717 if let loaded = model.state.value {
1818 if let error = model.actionError {
1919 Section {
20 Label(error, systemImage: "hand.raised")
21 .foregroundStyle(Color.gbWarn)
22 .font(.gbSans(.subheadline))
20 GBNotice(error, .gbWarn)
2321 }
2422 }
2523
@@ -56,11 +54,7 @@ struct TeamView: View {
5654 .font(.gbSans(.subheadline))
5755 .lineLimit(1)
5856 Spacer()
59 Text(grant.role)
60 .font(.gbSans(.caption))
61 .padding(.horizontal, 6)
62 .padding(.vertical, 2)
63 .background(.quaternary, in: gbChipShape)
57 GBChip(grant.role, .secondary)
6458 }
6559 .swipeActions {
6660 Button("Revoke", role: .destructive) {
gitbay/Views/Releases/ReleaseListView.swift +1 −3
@@ -100,9 +100,7 @@ private struct ReleaseCreateSheet: View {
100100 }
101101 if let error = model.createError {
102102 Section {
103 Label(error, systemImage: "exclamationmark.triangle")
104 .foregroundStyle(Color.gbBad)
105 .font(.gbSans(.subheadline))
103 GBNotice(error)
106104 }
107105 }
108106 }
gitbay/Views/Releases/ReleaseView.swift +1 −3
@@ -18,9 +18,7 @@ struct ReleaseView: View {
1818 if let release = model.state.value {
1919 if let error = model.actionError {
2020 Section {
21 Label(error, systemImage: "hand.raised")
22 .foregroundStyle(Color.gbWarn)
23 .font(.gbSans(.subheadline))
21 GBNotice(error, .gbWarn)
2422 }
2523 }
2624 Section {
gitbay/Views/Repos/BlameView.swift +5 −2
@@ -76,8 +76,11 @@ private struct HunkAttribution: View {
7676 .foregroundStyle(.tertiary)
7777 }
7878 .padding(.horizontal, 10)
79 .padding(.vertical, 4)
79 .padding(.vertical, 6)
8080 .frame(minWidth: 360, alignment: .leading)
81 .background(Color.gbFillSubtle)
81 // .blamehunk is ruled off from the one above, not filled.
82 .overlay(alignment: .top) {
83 Rectangle().fill(Color.gbLine).frame(height: 1)
84 }
8285 }
8386 }
gitbay/Views/Repos/FileEditSheet.swift +1 −3
@@ -35,9 +35,7 @@ struct FileEditSheet: View {
3535 }
3636 if let error = model.actionError {
3737 Section {
38 Label(error, systemImage: "hand.raised")
39 .foregroundStyle(Color.gbWarn)
40 .font(.gbSans(.subheadline))
38 GBNotice(error, .gbWarn)
4139 }
4240 }
4341 }
gitbay/Views/Repos/FileView.swift +4 −6
@@ -69,14 +69,12 @@ struct FileView: View {
6969 } else if let text = file.content {
7070 VStack(spacing: 0) {
7171 if file.isTruncated {
72 Label(
72 GBNotice(
7373 "Truncated — showing the first \(Int64(file.size).formatted(.byteCount(style: .file))).",
74 systemImage: "scissors"
74 .gbWarn
7575 )
76 .font(.gbSans(.caption))
77 .frame(maxWidth: .infinity, alignment: .leading)
78 .padding(8)
79 .background(.yellow.opacity(0.15))
76 .padding(.horizontal, 12)
77 .padding(.vertical, 6)
8078 }
8179 CodeScrollView(text: text, fileName: model.fileName, colorScheme: colorScheme)
8280 }
gitbay/Views/Repos/RepoListView.swift +2 −8
@@ -80,9 +80,7 @@ struct RepoCreateSheet: View {
8080 }
8181 if let error = model.errorMessage {
8282 Section {
83 Label(error, systemImage: "exclamationmark.triangle")
84 .foregroundStyle(Color.gbBad)
85 .font(.gbSans(.subheadline))
83 GBNotice(error)
8684 }
8785 }
8886 }
@@ -136,11 +134,7 @@ private struct RepoRow: View {
136134 .foregroundStyle(.secondary)
137135 }
138136 if repo.isArchived {
139 Text("archived")
140 .font(.gbSans(.caption2))
141 .padding(.horizontal, 5)
142 .padding(.vertical, 1)
143 .background(.quaternary, in: gbChipShape)
137 GBChip("archived", .secondary)
144138 }
145139 }
146140 if let description = repo.description, !description.isEmpty {
gitbay/Views/Repos/RepoSettingsView.swift +4 −4
@@ -20,9 +20,7 @@ struct RepoSettingsView: View {
2020 if let loaded = model.state.value {
2121 if let error = model.actionError {
2222 Section {
23 Label(error, systemImage: "hand.raised")
24 .foregroundStyle(Color.gbWarn)
25 .font(.gbSans(.subheadline))
23 GBNotice(error, .gbWarn)
2624 }
2725 }
2826 aboutSection(loaded)
@@ -98,9 +96,11 @@ struct RepoSettingsView: View {
9896 .disabled(model.working)
9997 }
10098 .font(.gbSans(.caption))
99 .foregroundStyle(Color.gbAccent)
101100 .padding(.horizontal, 8)
102101 .padding(.vertical, 3)
103 .background(.quaternary, in: gbChipShape)
102 .background(Color.gbAccent.opacity(0.07), in: gbChipShape)
103 .overlay(gbChipShape.stroke(Color.gbAccent.opacity(0.35), lineWidth: 1))
104104 }
105105 }
106106 }
gitbay/Views/Repos/RepoView.swift +2 −8
@@ -17,9 +17,7 @@ struct RepoView: View {
1717 if let detail = model.state.value {
1818 if let error = model.actionError {
1919 Section {
20 Label(error, systemImage: "hand.raised")
21 .foregroundStyle(Color.gbWarn)
22 .font(.gbSans(.subheadline))
20 GBNotice(error, .gbWarn)
2321 }
2422 }
2523 header(detail)
@@ -130,11 +128,7 @@ struct RepoView: View {
130128 .foregroundStyle(.secondary)
131129 }
132130 if detail.isArchived {
133 Text("archived")
134 .font(.gbSans(.caption2))
135 .padding(.horizontal, 5)
136 .padding(.vertical, 1)
137 .background(.quaternary, in: gbChipShape)
131 GBChip("archived", .secondary)
138132 }
139133 }
140134 if let description = detail.description, !description.isEmpty {
gitbay/Views/Shared/ComposeSheet.swift +1 −3
@@ -33,9 +33,7 @@ struct ComposeSheet: View {
3333 }
3434 if let errorMessage {
3535 Section {
36 Label(errorMessage, systemImage: "exclamationmark.triangle")
37 .foregroundStyle(Color.gbBad)
38 .font(.gbSans(.subheadline))
36 GBNotice(errorMessage)
3937 }
4038 }
4139 }
gitbay/Views/SignInView.swift +1 −2
@@ -52,8 +52,7 @@ struct SignInView: View {
5252
5353 if let errorMessage {
5454 Section {
55 Label(errorMessage, systemImage: "exclamationmark.triangle")
56 .foregroundStyle(Color.gbBad)
55 GBNotice(errorMessage)
5756 }
5857 }
5958