Commit 5d491ae4e8

5d491ae4e80917a23672c2afaf09f6480a56d9d1

parent: 2a9597a69f

Unsigned

cmc <hello@cleberg.net> · 2026-08-28 03:12 UTC

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

Layout: unified · split

gitbay/Theme/Theme.swift +28
@@ -37,6 +37,8 @@ extension Color {
37 /// --bg: the page ground the accent is mixed toward. Chrome uses the 37 /// --bg: the page ground the accent is mixed toward. Chrome uses the
38 /// system background; this is for blends that must match the web. 38 /// system background; this is for blends that must match the web.
39 nonisolated static let gbBackground = paired(0xFFFFFF, 0x0A0A0A) 39 nonisolated static let gbBackground = paired(0xFFFFFF, 0x0A0A0A)
40 /// --line: control borders and table edges.
41 nonisolated static let gbLine = paired(0xD4D4D4, 0x2E2E2E)
40} 42}
41 43
42extension UIColor { 44extension UIColor {
@@ -101,6 +103,32 @@ enum GitbayFonts {
101 } 103 }
102} 104}
103 105
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
104/// The web's `.chip` recipe: state color, a 7% tint ground, a 35% tint 132/// The web's `.chip` recipe: state color, a 7% tint ground, a 35% tint
105/// border, 2px radius. 133/// border, 2px radius.
106struct GBChip: View { 134struct GBChip: View {
gitbay/Views/Account/AccountView.swift +5 −18
@@ -27,11 +27,7 @@ struct AccountView: View {
27 Label(membership.org, systemImage: "building.2") 27 Label(membership.org, systemImage: "building.2")
28 .font(.gbSans(.subheadline)) 28 .font(.gbSans(.subheadline))
29 Spacer() 29 Spacer()
30 Text(membership.role) 30 GBChip(membership.role, .secondary)
31 .font(.gbSans(.caption))
32 .padding(.horizontal, 6)
33 .padding(.vertical, 2)
34 .background(.quaternary, in: gbChipShape)
35 } 31 }
36 } 32 }
37 } 33 }
@@ -44,16 +40,12 @@ struct AccountView: View {
44 // scrolled out of existence. 40 // scrolled out of existence.
45 if let error = model.actionError { 41 if let error = model.actionError {
46 Section { 42 Section {
47 Label(error, systemImage: "hand.raised") 43 GBNotice(error, .gbWarn)
48 .foregroundStyle(Color.gbWarn)
49 .font(.gbSans(.subheadline))
50 } 44 }
51 } 45 }
52 if let notice = model.notice { 46 if let notice = model.notice {
53 Section { 47 Section {
54 Label(notice, systemImage: "envelope") 48 GBNotice(notice, .gbOK)
55 .foregroundStyle(.secondary)
56 .font(.gbSans(.subheadline))
57 } 49 }
58 } 50 }
59 emailSection 51 emailSection
@@ -136,10 +128,7 @@ struct AccountView: View {
136 .truncationMode(.middle) 128 .truncationMode(.middle)
137 HStack(spacing: 6) { 129 HStack(spacing: 6) {
138 Text(key.algo) 130 Text(key.algo)
139 Text(key.scope) 131 GBChip(key.scope, .secondary)
140 .padding(.horizontal, 5)
141 .padding(.vertical, 1)
142 .background(.quaternary, in: gbChipShape)
143 } 132 }
144 .font(.gbSans(.caption2)) 133 .font(.gbSans(.caption2))
145 .foregroundStyle(.secondary) 134 .foregroundStyle(.secondary)
@@ -273,9 +262,7 @@ private struct KeyPasteSheet: View {
273 } 262 }
274 if let errorMessage { 263 if let errorMessage {
275 Section { 264 Section {
276 Label(errorMessage, systemImage: "exclamationmark.triangle") 265 GBNotice(errorMessage)
277 .foregroundStyle(Color.gbBad)
278 .font(.gbSans(.subheadline))
279 } 266 }
280 } 267 }
281 } 268 }
gitbay/Views/Builds/BuildListView.swift +1 −3
@@ -14,9 +14,7 @@ struct BuildListView: View {
14 List { 14 List {
15 if let error = model.actionError { 15 if let error = model.actionError {
16 Section { 16 Section {
17 Label(error, systemImage: "hand.raised") 17 GBNotice(error, .gbWarn)
18 .foregroundStyle(Color.gbWarn)
19 .font(.gbSans(.subheadline))
20 } 18 }
21 } 19 }
22 ForEach(model.state.value ?? []) { build in 20 ForEach(model.state.value ?? []) { build in
gitbay/Views/Discovery/ProfileView.swift +1 −1
@@ -40,7 +40,7 @@ struct ProfileView: View {
40 VStack(alignment: .leading, spacing: 8) { 40 VStack(alignment: .leading, spacing: 8) {
41 HStack(spacing: 8) { 41 HStack(spacing: 8) {
42 Image(systemName: profile.isOrg ? "building.2" : "person.crop.circle") 42 Image(systemName: profile.isOrg ? "building.2" : "person.crop.circle")
43 .font(.title2) 43 .font(.gbSans(.title2))
44 .foregroundStyle(Color.gbAccent) 44 .foregroundStyle(Color.gbAccent)
45 Text(profile.name) 45 Text(profile.name)
46 .font(.gbSans(.title3).weight(.semibold)) 46 .font(.gbSans(.title3).weight(.semibold))
gitbay/Views/Issues/IssueListView.swift +1 −5
@@ -86,11 +86,7 @@ private struct IssueRow: View {
86 .font(.gbSans(.caption2)) 86 .font(.gbSans(.caption2))
87 .foregroundStyle(issue.isOpen ? Color.gbOK : Color.gbBad) 87 .foregroundStyle(issue.isOpen ? Color.gbOK : Color.gbBad)
88 ForEach(issue.labels ?? [], id: \.self) { label in 88 ForEach(issue.labels ?? [], id: \.self) { label in
89 Text(label) 89 GBChip(label, .secondary)
90 .font(.gbSans(.caption2))
91 .padding(.horizontal, 5)
92 .padding(.vertical, 1)
93 .background(.quaternary, in: gbChipShape)
94 } 90 }
95 Spacer() 91 Spacer()
96 if let assignees = issue.assignees, !assignees.isEmpty { 92 if let assignees = issue.assignees, !assignees.isEmpty {
gitbay/Views/Issues/IssueView.swift +3 −4
@@ -23,9 +23,7 @@ struct IssueView: View {
23 23
24 if let error = model.actionError { 24 if let error = model.actionError {
25 Section { 25 Section {
26 Label(error, systemImage: "hand.raised") 26 GBNotice(error, .gbWarn)
27 .foregroundStyle(Color.gbWarn)
28 .font(.gbSans(.subheadline))
29 } 27 }
30 } 28 }
31 29
@@ -170,7 +168,8 @@ struct IssueView: View {
170 .font(.gbSans(.caption)) 168 .font(.gbSans(.caption))
171 .padding(.horizontal, 8) 169 .padding(.horizontal, 8)
172 .padding(.vertical, 3) 170 .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))
174 } 173 }
175 } 174 }
176 } 175 }
gitbay/Views/MRs/DiffView.swift +10 −2
@@ -103,12 +103,20 @@ private struct HunkView: View {
103 var body: some View { 103 var body: some View {
104 ScrollView(.horizontal) { 104 ScrollView(.horizontal) {
105 VStack(alignment: .leading, spacing: 0) { 105 VStack(alignment: .leading, spacing: 0) {
106 // The stylesheet colors the hunk header with --accent on
107 // --fill-subtle, ruled above and below with --line.
106 Text(hunk.header) 108 Text(hunk.header)
107 .font(.gbMono(.caption2)) 109 .font(.gbMono(.caption2))
108 .foregroundStyle(.secondary) 110 .foregroundStyle(Color.gbAccent)
109 .padding(.vertical, 2) 111 .padding(.vertical, 3)
110 .frame(maxWidth: .infinity, alignment: .leading) 112 .frame(maxWidth: .infinity, alignment: .leading)
111 .background(Color.gbFillSubtle) 113 .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 }
112 ForEach(hunk.lines) { line in 120 ForEach(hunk.lines) { line in
113 LineView(line: line) 121 LineView(line: line)
114 if let model { 122 if let model {
gitbay/Views/MRs/MRListView.swift +1 −3
@@ -93,9 +93,7 @@ private struct MRCreateSheet: View {
93 } 93 }
94 if let error = model.errorMessage { 94 if let error = model.errorMessage {
95 Section { 95 Section {
96 Label(error, systemImage: "exclamationmark.triangle") 96 GBNotice(error)
97 .foregroundStyle(Color.gbBad)
98 .font(.gbSans(.subheadline))
99 } 97 }
100 } 98 }
101 } 99 }
gitbay/Views/MRs/MRView.swift +3 −13
@@ -23,9 +23,7 @@ struct MRView: View {
23 23
24 if let error = model.actionError { 24 if let error = model.actionError {
25 Section { 25 Section {
26 Label(error, systemImage: "hand.raised") 26 GBNotice(error, .gbWarn)
27 .foregroundStyle(Color.gbWarn)
28 .font(.gbSans(.subheadline))
29 } 27 }
30 } 28 }
31 29
@@ -180,11 +178,7 @@ struct MRView: View {
180 .font(.gbSans(.subheadline)) 178 .font(.gbSans(.subheadline))
181 Spacer() 179 Spacer()
182 if review.stale { 180 if review.stale {
183 Text("stale") 181 GBChip("stale", .gbWarn)
184 .font(.gbSans(.caption2))
185 .padding(.horizontal, 5)
186 .padding(.vertical, 1)
187 .background(.quaternary, in: gbChipShape)
188 } 182 }
189 } 183 }
190 } 184 }
@@ -316,11 +310,7 @@ struct ReviewThreadView: View {
316 .font(.gbMono(.caption)) 310 .font(.gbMono(.caption))
317 .lineLimit(1) 311 .lineLimit(1)
318 if thread.stale { 312 if thread.stale {
319 Text("stale") 313 GBChip("stale", .gbWarn)
320 .font(.gbSans(.caption2))
321 .padding(.horizontal, 5)
322 .padding(.vertical, 1)
323 .background(.quaternary, in: gbChipShape)
324 } 314 }
325 Spacer() 315 Spacer()
326 Button(thread.isResolved ? "Reopen" : "Resolve") { 316 Button(thread.isResolved ? "Reopen" : "Resolve") {
gitbay/Views/Orgs/OrgView.swift +2 −8
@@ -20,9 +20,7 @@ struct OrgView: View {
20 if let loaded = model.state.value { 20 if let loaded = model.state.value {
21 if let error = model.actionError { 21 if let error = model.actionError {
22 Section { 22 Section {
23 Label(error, systemImage: "hand.raised") 23 GBNotice(error, .gbWarn)
24 .foregroundStyle(Color.gbWarn)
25 .font(.gbSans(.subheadline))
26 } 24 }
27 } 25 }
28 membersSection(loaded.members) 26 membersSection(loaded.members)
@@ -79,11 +77,7 @@ struct OrgView: View {
79 Button("Admin") { Task { await model.setRole(member, role: "admin") } } 77 Button("Admin") { Task { await model.setRole(member, role: "admin") } }
80 Button("Member") { Task { await model.setRole(member, role: "member") } } 78 Button("Member") { Task { await model.setRole(member, role: "member") } }
81 } label: { 79 } label: {
82 Text(member.role) 80 GBChip(member.role, .secondary)
83 .font(.gbSans(.caption))
84 .padding(.horizontal, 6)
85 .padding(.vertical, 2)
86 .background(.quaternary, in: gbChipShape)
87 } 81 }
88 .disabled(model.working) 82 .disabled(model.working)
89 } 83 }
gitbay/Views/Orgs/TeamView.swift +2 −8
@@ -17,9 +17,7 @@ struct TeamView: View {
17 if let loaded = model.state.value { 17 if let loaded = model.state.value {
18 if let error = model.actionError { 18 if let error = model.actionError {
19 Section { 19 Section {
20 Label(error, systemImage: "hand.raised") 20 GBNotice(error, .gbWarn)
21 .foregroundStyle(Color.gbWarn)
22 .font(.gbSans(.subheadline))
23 } 21 }
24 } 22 }
25 23
@@ -56,11 +54,7 @@ struct TeamView: View {
56 .font(.gbSans(.subheadline)) 54 .font(.gbSans(.subheadline))
57 .lineLimit(1) 55 .lineLimit(1)
58 Spacer() 56 Spacer()
59 Text(grant.role) 57 GBChip(grant.role, .secondary)
60 .font(.gbSans(.caption))
61 .padding(.horizontal, 6)
62 .padding(.vertical, 2)
63 .background(.quaternary, in: gbChipShape)
64 } 58 }
65 .swipeActions { 59 .swipeActions {
66 Button("Revoke", role: .destructive) { 60 Button("Revoke", role: .destructive) {
gitbay/Views/Releases/ReleaseListView.swift +1 −3
@@ -100,9 +100,7 @@ private struct ReleaseCreateSheet: View {
100 } 100 }
101 if let error = model.createError { 101 if let error = model.createError {
102 Section { 102 Section {
103 Label(error, systemImage: "exclamationmark.triangle") 103 GBNotice(error)
104 .foregroundStyle(Color.gbBad)
105 .font(.gbSans(.subheadline))
106 } 104 }
107 } 105 }
108 } 106 }
gitbay/Views/Releases/ReleaseView.swift +1 −3
@@ -18,9 +18,7 @@ struct ReleaseView: View {
18 if let release = model.state.value { 18 if let release = model.state.value {
19 if let error = model.actionError { 19 if let error = model.actionError {
20 Section { 20 Section {
21 Label(error, systemImage: "hand.raised") 21 GBNotice(error, .gbWarn)
22 .foregroundStyle(Color.gbWarn)
23 .font(.gbSans(.subheadline))
24 } 22 }
25 } 23 }
26 Section { 24 Section {
gitbay/Views/Repos/BlameView.swift +5 −2
@@ -76,8 +76,11 @@ private struct HunkAttribution: View {
76 .foregroundStyle(.tertiary) 76 .foregroundStyle(.tertiary)
77 } 77 }
78 .padding(.horizontal, 10) 78 .padding(.horizontal, 10)
79 .padding(.vertical, 4) 79 .padding(.vertical, 6)
80 .frame(minWidth: 360, alignment: .leading) 80 .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 }
82 } 85 }
83} 86}
gitbay/Views/Repos/FileEditSheet.swift +1 −3
@@ -35,9 +35,7 @@ struct FileEditSheet: View {
35 } 35 }
36 if let error = model.actionError { 36 if let error = model.actionError {
37 Section { 37 Section {
38 Label(error, systemImage: "hand.raised") 38 GBNotice(error, .gbWarn)
39 .foregroundStyle(Color.gbWarn)
40 .font(.gbSans(.subheadline))
41 } 39 }
42 } 40 }
43 } 41 }
gitbay/Views/Repos/FileView.swift +4 −6
@@ -69,14 +69,12 @@ struct FileView: View {
69 } else if let text = file.content { 69 } else if let text = file.content {
70 VStack(spacing: 0) { 70 VStack(spacing: 0) {
71 if file.isTruncated { 71 if file.isTruncated {
72 Label( 72 GBNotice(
73 "Truncated — showing the first \(Int64(file.size).formatted(.byteCount(style: .file))).", 73 "Truncated — showing the first \(Int64(file.size).formatted(.byteCount(style: .file))).",
74 systemImage: "scissors" 74 .gbWarn
75 ) 75 )
76 .font(.gbSans(.caption)) 76 .padding(.horizontal, 12)
77 .frame(maxWidth: .infinity, alignment: .leading) 77 .padding(.vertical, 6)
78 .padding(8)
79 .background(.yellow.opacity(0.15))
80 } 78 }
81 CodeScrollView(text: text, fileName: model.fileName, colorScheme: colorScheme) 79 CodeScrollView(text: text, fileName: model.fileName, colorScheme: colorScheme)
82 } 80 }
gitbay/Views/Repos/RepoListView.swift +2 −8
@@ -80,9 +80,7 @@ struct RepoCreateSheet: View {
80 } 80 }
81 if let error = model.errorMessage { 81 if let error = model.errorMessage {
82 Section { 82 Section {
83 Label(error, systemImage: "exclamationmark.triangle") 83 GBNotice(error)
84 .foregroundStyle(Color.gbBad)
85 .font(.gbSans(.subheadline))
86 } 84 }
87 } 85 }
88 } 86 }
@@ -136,11 +134,7 @@ private struct RepoRow: View {
136 .foregroundStyle(.secondary) 134 .foregroundStyle(.secondary)
137 } 135 }
138 if repo.isArchived { 136 if repo.isArchived {
139 Text("archived") 137 GBChip("archived", .secondary)
140 .font(.gbSans(.caption2))
141 .padding(.horizontal, 5)
142 .padding(.vertical, 1)
143 .background(.quaternary, in: gbChipShape)
144 } 138 }
145 } 139 }
146 if let description = repo.description, !description.isEmpty { 140 if let description = repo.description, !description.isEmpty {
gitbay/Views/Repos/RepoSettingsView.swift +4 −4
@@ -20,9 +20,7 @@ struct RepoSettingsView: View {
20 if let loaded = model.state.value { 20 if let loaded = model.state.value {
21 if let error = model.actionError { 21 if let error = model.actionError {
22 Section { 22 Section {
23 Label(error, systemImage: "hand.raised") 23 GBNotice(error, .gbWarn)
24 .foregroundStyle(Color.gbWarn)
25 .font(.gbSans(.subheadline))
26 } 24 }
27 } 25 }
28 aboutSection(loaded) 26 aboutSection(loaded)
@@ -98,9 +96,11 @@ struct RepoSettingsView: View {
98 .disabled(model.working) 96 .disabled(model.working)
99 } 97 }
100 .font(.gbSans(.caption)) 98 .font(.gbSans(.caption))
99 .foregroundStyle(Color.gbAccent)
101 .padding(.horizontal, 8) 100 .padding(.horizontal, 8)
102 .padding(.vertical, 3) 101 .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))
104 } 104 }
105 } 105 }
106 } 106 }
gitbay/Views/Repos/RepoView.swift +2 −8
@@ -17,9 +17,7 @@ struct RepoView: View {
17 if let detail = model.state.value { 17 if let detail = model.state.value {
18 if let error = model.actionError { 18 if let error = model.actionError {
19 Section { 19 Section {
20 Label(error, systemImage: "hand.raised") 20 GBNotice(error, .gbWarn)
21 .foregroundStyle(Color.gbWarn)
22 .font(.gbSans(.subheadline))
23 } 21 }
24 } 22 }
25 header(detail) 23 header(detail)
@@ -130,11 +128,7 @@ struct RepoView: View {
130 .foregroundStyle(.secondary) 128 .foregroundStyle(.secondary)
131 } 129 }
132 if detail.isArchived { 130 if detail.isArchived {
133 Text("archived") 131 GBChip("archived", .secondary)
134 .font(.gbSans(.caption2))
135 .padding(.horizontal, 5)
136 .padding(.vertical, 1)
137 .background(.quaternary, in: gbChipShape)
138 } 132 }
139 } 133 }
140 if let description = detail.description, !description.isEmpty { 134 if let description = detail.description, !description.isEmpty {
gitbay/Views/Shared/ComposeSheet.swift +1 −3
@@ -33,9 +33,7 @@ struct ComposeSheet: View {
33 } 33 }
34 if let errorMessage { 34 if let errorMessage {
35 Section { 35 Section {
36 Label(errorMessage, systemImage: "exclamationmark.triangle") 36 GBNotice(errorMessage)
37 .foregroundStyle(Color.gbBad)
38 .font(.gbSans(.subheadline))
39 } 37 }
40 } 38 }
41 } 39 }
gitbay/Views/SignInView.swift +1 −2
@@ -52,8 +52,7 @@ struct SignInView: View {
52 52
53 if let errorMessage { 53 if let errorMessage {
54 Section { 54 Section {
55 Label(errorMessage, systemImage: "exclamationmark.triangle") 55 GBNotice(errorMessage)
56 .foregroundStyle(Color.gbBad)
57 } 56 }
58 } 57 }
59 58