Commit 2c608ed9cb

2c608ed9cb11f3e835f577994b5627b9b7910f4f

parent: 349bec0e99

Unsigned

cmc <hello@cleberg.net> · 2026-07-21 00:14 UTC

feat(a11y): unlock light mode and add appearance preference (#21 phase 2)

Removes the 16 scattered .preferredColorScheme(.dark) calls and the one
.toolbarColorScheme, and applies appearance in exactly one place — the
WindowGroup in DomainDigApp. Re-applying per view is what let the lock
spread across eight files unnoticed until light mode was unreachable.

Adds AppAppearance (System / Light / Dark) in @AppStorage, exposed under
Settings > Display next to Density. Honouring the system setting and
offering an override is one key, and it keeps the deliberate dark
aesthetic reachable for anyone who wants it.

Also replaces .secondary with AppTextSecondary across 191 sites. iOS's
own secondaryLabel is 3.29:1 on a light card — below AA — which never
showed while the app was locked to dark, where the same colour reads
6.32:1. Unlocking light mode is precisely what exposed it, so it belongs
here rather than in a later phase: without it, light mode would ship with
body text under 4.5:1 app-wide.

Dark mode reports 18 findings, unchanged from phase 1 — no regression
from unlocking. Light mode reports 21. The three extra are iOS-rendered
Section headers (TIER, PREFERENCES, SERVICES) using the system's grey;
overriding system header styling across every section to gain ~0.3:1 on
decorative labels is a poor trade and is left alone.

Two long-standing Settings contrast findings are now explained. They are
the last rows of a section sitting under the translucent tab bar, so the
audit measures text against a blended background — confirmed by
screenshot, present in dark mode since phase 0, and standard iOS
scroll-under behaviour rather than a defect.

Layout: unified · split

Docs/ACCESSIBILITY.md +26
@@ -29,6 +29,12 @@ worst of those three is shown:
2929| `StatusWarning` | `#7A5600` | `#FFD60A` | 4.74 | 9.61 |
3030| `StatusCritical` | `#B3261E` | `#FF6961` | 4.51 | 5.54 |
3131| `StatusNeutral` | `#5A5A5F` | `#A1A1A6` | 4.92 | 5.88 |
32| `AppTextSecondary` | `#5A5A5F` | `#A1A1A6` | 6.15 | 7.50 |
33
34`AppTextSecondary` replaces `.secondary` for body text. iOS's own `secondaryLabel`
35is only **3.29:1** on a light card — below AA — which never showed while the app
36was locked to dark, where the same colour reads 6.32:1. Unlocking light mode
37exposed it across 191 sites.
3238
3339High Contrast variants push further in the same direction. Surfaces
3440(`AppBackground`, `AppSurface`, `AppSurfaceElevated`, `AppSeparator`) carry no
@@ -58,6 +64,26 @@ So there are two colours:
5864`AppOnAccent` is the label colour for a solid accent fill and flips by scheme —
5965white on the light accent, black on the dark one.
6066
67## Appearance
68
69`AppAppearance` (System / Light / Dark) is stored in `@AppStorage` and applied in
70**exactly one place** — the `WindowGroup` in `DomainDigApp`. Keep it that way. The
71app previously carried 16 separate `.preferredColorScheme(.dark)` calls scattered
72through view bodies, which is how light mode became unreachable without anyone
73noticing; re-applying per view is what let the lock spread.
74
75Users override it under Settings → Display.
76
77### Known light-mode findings
78
79| Finding | Cause | Action |
80| --- | --- | --- |
81| 2× `contrast failed` on Settings | The last rows of a section sit under the translucent tab bar, so the audit measures text against a blended background. Present in dark mode too, since phase 0. | None — standard iOS scroll-under behaviour |
82| 3× `contrast nearly passed` on Settings | iOS-rendered `Section` headers (`TIER`, `PREFERENCES`, `SERVICES`) use the system's grey. | Not fixed. Overriding system header styling across every section to gain ~0.3:1 on decorative labels trades platform convention for very little |
83
84Dark mode reports 18 findings and light mode 21; the three extra are the section
85headers above. Everything the app actually controls passes in both schemes.
86
6187## Findings are reported, not enforced
6288
6389The audit surfaces violations that exist today, so failing on all of them would
DomainDig/AuditModeView.swift +2 −2
@@ -25,7 +25,7 @@ struct AuditModeView: View {
2525 Section("Audit Timeline") {
2626 if auditStore.sessions.isEmpty {
2727 Text("No audits yet.")
28 .foregroundStyle(.secondary)
28 .foregroundStyle(Color(.appTextSecondary))
2929 } else {
3030 ForEach(auditStore.sessions) { session in
3131 Button {
@@ -35,7 +35,7 @@ struct AuditModeView: View {
3535 Text(session.domain).font(.headline)
3636 Text("\(session.createdAt.formatted(date: .abbreviated, time: .shortened)) • \(session.status.title)")
3737 .font(.caption)
38 .foregroundStyle(.secondary)
38 .foregroundStyle(Color(.appTextSecondary))
3939 }
4040 }
4141 }
DomainDig/BatchResultsView.swift +8 −8
@@ -17,12 +17,12 @@ struct BatchResultsView: View {
1717 .frame(width: 120)
1818 Text(viewModel.batchProgressLabel)
1919 .font(appDensity.font(.caption2))
20 .foregroundStyle(.secondary)
20 .foregroundStyle(Color(.appTextSecondary))
2121 }
2222 } else if !viewModel.batchResults.isEmpty {
2323 Text("\(viewModel.batchResults.count) domains")
2424 .font(appDensity.font(.caption2))
25 .foregroundStyle(.secondary)
25 .foregroundStyle(Color(.appTextSecondary))
2626 }
2727 }
2828
@@ -67,7 +67,7 @@ struct BatchResultRowView: View {
6767 Spacer(minLength: 8)
6868 Text(result.resultSource.label.lowercased())
6969 .font(appDensity.font(.caption2))
70 .foregroundStyle(.secondary)
70 .foregroundStyle(Color(.appTextSecondary))
7171 AppStatusBadgeView(model: quickStatusBadge)
7272 }
7373
@@ -80,7 +80,7 @@ struct BatchResultRowView: View {
8080 }
8181 }
8282 .font(appDensity.font(.caption2))
83 .foregroundStyle(.secondary)
83 .foregroundStyle(Color(.appTextSecondary))
8484
8585 HStack(spacing: 10) {
8686 Text(result.primaryIP ?? "No IP")
@@ -93,12 +93,12 @@ struct BatchResultRowView: View {
9393 .lineLimit(1)
9494 }
9595 .font(appDensity.font(.caption2))
96 .foregroundStyle(.secondary)
96 .foregroundStyle(Color(.appTextSecondary))
9797
9898 if let summaryMessage = result.summaryMessage {
9999 Text(summaryMessage)
100100 .font(appDensity.font(.caption2))
101 .foregroundStyle(.secondary)
101 .foregroundStyle(Color(.appTextSecondary))
102102 }
103103
104104 if let changeClassification = result.changeClassification {
@@ -137,7 +137,7 @@ struct BatchResultRowView: View {
137137 return .init(
138138 title: availabilityText,
139139 systemImage: "exclamationmark.circle",
140 foregroundColor: .secondary,
140 foregroundColor: Color(.appTextSecondary),
141141 backgroundColor: Color(.appSurfaceElevated)
142142 )
143143 }
@@ -145,7 +145,7 @@ struct BatchResultRowView: View {
145145 private var quickStatusBadge: AppStatusBadgeModel {
146146 switch result.status {
147147 case .pending:
148 return .init(title: "Pending", systemImage: "clock", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
148 return .init(title: "Pending", systemImage: "clock", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
149149 case .running:
150150 return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16))
151151 case .completed:
DomainDig/BatchSweepSummaryView.swift +2 −2
@@ -32,7 +32,7 @@ struct BatchSweepSummaryView: View {
3232 if visibleResults.isEmpty {
3333 Text("No domains with changes or warnings")
3434 .font(.system(.caption, design: .monospaced))
35 .foregroundStyle(.secondary)
35 .foregroundStyle(Color(.appTextSecondary))
3636 } else {
3737 ForEach(visibleResults) { result in
3838 if let entry = viewModel.historyEntry(for: result) {
@@ -56,7 +56,7 @@ struct BatchSweepSummaryView: View {
5656 HStack {
5757 Text(label)
5858 .font(.system(.caption, design: .monospaced))
59 .foregroundStyle(.secondary)
59 .foregroundStyle(Color(.appTextSecondary))
6060 Spacer()
6161 Text(value)
6262 .font(.system(.callout, design: .monospaced))
DomainDig/ContentView.swift +77 −72
@@ -173,8 +173,6 @@ struct ContentView: View {
173173 )
174174 )
175175 .navigationTitle("DomainDig")
176 .toolbarColorScheme(.dark, for: .navigationBar)
177 .preferredColorScheme(.dark)
178176 .toolbar {
179177 ToolbarItem(placement: .topBarTrailing) {
180178 if viewModel.hasRun {
@@ -182,7 +180,7 @@ struct ContentView: View {
182180 viewModel.reset()
183181 } label: {
184182 Image(systemName: "xmark.circle")
185 .foregroundStyle(.secondary)
183 .foregroundStyle(Color(.appTextSecondary))
186184 }
187185 }
188186 }
@@ -327,12 +325,12 @@ struct ContentView: View {
327325 VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
328326 Text("Paste domains separated by new lines or commas.")
329327 .font(appDensity.font(.caption))
330 .foregroundStyle(.secondary)
328 .foregroundStyle(Color(.appTextSecondary))
331329
332330 if let batchAllowanceSummary = FeatureAccessService.batchAllowanceSummary() {
333331 Text(batchAllowanceSummary)
334332 .font(appDensity.font(.caption2))
335 .foregroundStyle(.secondary)
333 .foregroundStyle(Color(.appTextSecondary))
336334 }
337335
338336 TextField(
@@ -544,7 +542,7 @@ struct ContentView: View {
544542 } label: {
545543 Image(systemName: "bolt.circle")
546544 .font(appDensity.font(.body, design: .default))
547 .foregroundStyle(.secondary)
545 .foregroundStyle(Color(.appTextSecondary))
548546 }
549547 Button {
550548 viewModel.toggleSavedDomain()
@@ -583,7 +581,7 @@ struct ContentView: View {
583581 } label: {
584582 Image(systemName: "square.and.arrow.up")
585583 .font(appDensity.font(.body, design: .default))
586 .foregroundStyle(.secondary)
584 .foregroundStyle(Color(.appTextSecondary))
587585 }
588586 }
589587 }
@@ -652,13 +650,13 @@ struct ContentView: View {
652650 HStack {
653651 Text("RECENT")
654652 .font(appDensity.font(.caption2))
655 .foregroundStyle(.secondary)
653 .foregroundStyle(Color(.appTextSecondary))
656654 Spacer()
657655 Button("Clear") {
658656 viewModel.clearRecentSearches()
659657 }
660658 .font(appDensity.font(.caption2))
661 .foregroundStyle(.secondary)
659 .foregroundStyle(Color(.appTextSecondary))
662660 }
663661
664662 ForEach(viewModel.recentSearches, id: \.self) { domain in
@@ -694,7 +692,7 @@ struct ContentView: View {
694692 .font(appDensity.font(.headline, design: .default, weight: .semibold))
695693 Text(message)
696694 .font(appDensity.font(.callout, design: .default))
697 .foregroundStyle(.secondary)
695 .foregroundStyle(Color(.appTextSecondary))
698696 }
699697 .frame(maxWidth: .infinity, alignment: .leading)
700698 .padding(appDensity.metrics.cardPadding)
@@ -784,7 +782,7 @@ struct SummaryView: View {
784782 VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing) {
785783 Text(field.label)
786784 .font(appDensity.font(.caption2))
787 .foregroundStyle(.secondary)
785 .foregroundStyle(Color(.appTextSecondary))
788786 Text(field.value)
789787 .font(appDensity.font(.caption))
790788 .foregroundStyle(ResultColors.color(for: field.tone))
@@ -826,13 +824,13 @@ struct RiskSummaryCardView: View {
826824 Spacer()
827825 Text("Deterministic")
828826 .font(appDensity.font(.caption2))
829 .foregroundStyle(.secondary)
827 .foregroundStyle(Color(.appTextSecondary))
830828 }
831829
832830 if topFactors.isEmpty {
833831 Text("No major risk factors identified")
834832 .font(appDensity.font(.caption))
835 .foregroundStyle(.secondary)
833 .foregroundStyle(Color(.appTextSecondary))
836834 } else {
837835 ForEach(Array(topFactors.enumerated()), id: \.offset) { _, factor in
838836 HStack(alignment: .top, spacing: 8) {
@@ -884,7 +882,7 @@ struct InsightsSummaryCardView: View {
884882 if insights.isEmpty {
885883 Text("No deterministic insights triggered")
886884 .font(appDensity.font(.caption))
887 .foregroundStyle(.secondary)
885 .foregroundStyle(Color(.appTextSecondary))
888886 } else {
889887 ForEach(Array(insights.enumerated()), id: \.offset) { _, insight in
890888 HStack(alignment: .top, spacing: 8) {
@@ -940,7 +938,7 @@ struct StickyLookupSummaryView: View {
940938 HStack(spacing: 8) {
941939 Label(primaryIP, systemImage: "network")
942940 .font(appDensity.font(.caption))
943 .foregroundStyle(.secondary)
941 .foregroundStyle(Color(.appTextSecondary))
944942 Spacer(minLength: 6)
945943 AppCopyButton(value: primaryIP, label: "Copy IP")
946944 }
@@ -966,7 +964,7 @@ struct LookupProgressOverviewView: View {
966964 .foregroundStyle(.primary)
967965 Text(steps.isEmpty ? "Preparing requests" : steps.joined(separator: " • "))
968966 .font(appDensity.font(.caption2))
969 .foregroundStyle(.secondary)
967 .foregroundStyle(Color(.appTextSecondary))
970968 }
971969 Spacer()
972970 }
@@ -1049,13 +1047,13 @@ struct DomainChangeSummaryView: View {
10491047 .clipShape(Capsule())
10501048 Text(summary.generatedAt, style: .time)
10511049 .font(appDensity.font(.caption2))
1052 .foregroundStyle(.secondary)
1050 .foregroundStyle(Color(.appTextSecondary))
10531051 }
10541052
10551053 VStack(alignment: .leading, spacing: 4) {
10561054 Text("Inference")
10571055 .font(appDensity.font(.caption2))
1058 .foregroundStyle(.secondary)
1056 .foregroundStyle(Color(.appTextSecondary))
10591057 Text(summary.message)
10601058 .font(appDensity.font(.caption))
10611059 .foregroundStyle(.primary)
@@ -1069,7 +1067,7 @@ struct DomainChangeSummaryView: View {
10691067 VStack(alignment: .leading, spacing: 4) {
10701068 Text("Observed")
10711069 .font(appDensity.font(.caption2))
1072 .foregroundStyle(.secondary)
1070 .foregroundStyle(Color(.appTextSecondary))
10731071 ForEach(Array(summary.observedFacts.enumerated()), id: \.offset) { _, fact in
10741072 Text(fact)
10751073 .font(appDensity.font(.caption))
@@ -1171,7 +1169,7 @@ struct DomainDiffView: View {
11711169 HStack {
11721170 Text(item.label)
11731171 .font(.system(.caption, design: .monospaced))
1174 .foregroundStyle(.secondary)
1172 .foregroundStyle(Color(.appTextSecondary))
11751173 Spacer()
11761174 Text("\(item.changeType.marker) \(item.severity.title) • \(changeLabel(for: item.changeType))")
11771175 .font(.system(.caption2, design: .monospaced))
@@ -1186,10 +1184,10 @@ struct DomainDiffView: View {
11861184 VStack(alignment: .leading, spacing: 2) {
11871185 Text("Old")
11881186 .font(.system(.caption2, design: .monospaced))
1189 .foregroundStyle(.secondary)
1187 .foregroundStyle(Color(.appTextSecondary))
11901188 Text(oldValue)
11911189 .font(.system(.caption2, design: .monospaced))
1192 .foregroundStyle(.secondary)
1190 .foregroundStyle(Color(.appTextSecondary))
11931191 .textSelection(.enabled)
11941192 }
11951193 }
@@ -1198,7 +1196,7 @@ struct DomainDiffView: View {
11981196 VStack(alignment: .leading, spacing: 2) {
11991197 Text("New")
12001198 .font(.system(.caption2, design: .monospaced))
1201 .foregroundStyle(.secondary)
1199 .foregroundStyle(Color(.appTextSecondary))
12021200 Text(newValue)
12031201 .font(.system(.caption, design: .monospaced))
12041202 .foregroundStyle(item.hasChanges ? .primary : .secondary)
@@ -1306,7 +1304,7 @@ struct TrackedDomainDetailHeaderView: View {
13061304 Text("Last refresh \(trackedDomain.updatedAt.formatted(date: .abbreviated, time: .shortened))")
13071305 }
13081306 .font(.system(.caption2, design: .monospaced))
1309 .foregroundStyle(.secondary)
1307 .foregroundStyle(Color(.appTextSecondary))
13101308 }
13111309 }
13121310}
@@ -1394,7 +1392,7 @@ struct DomainSectionView: View {
13941392 VStack(alignment: .leading, spacing: 8) {
13951393 Text("Part of workflow")
13961394 .font(appDensity.font(.caption))
1397 .foregroundStyle(.secondary)
1395 .foregroundStyle(Color(.appTextSecondary))
13981396
13991397 ForEach(workflows) { workflow in
14001398 HStack {
@@ -1429,7 +1427,7 @@ struct DomainSectionView: View {
14291427 if showSuggestions {
14301428 Text("Suggestions")
14311429 .font(appDensity.font(.caption))
1432 .foregroundStyle(.secondary)
1430 .foregroundStyle(Color(.appTextSecondary))
14331431 .padding(.top, 4)
14341432 if suggestionsLoading {
14351433 ProgressView("Checking alternatives…")
@@ -1527,7 +1525,7 @@ struct OwnershipSectionView: View {
15271525 HStack {
15281526 Text("History")
15291527 .font(appDensity.font(.caption))
1530 .foregroundStyle(.secondary)
1528 .foregroundStyle(Color(.appTextSecondary))
15311529 Spacer()
15321530 if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder {
15331531 Button("Load") {
@@ -1545,12 +1543,12 @@ struct OwnershipSectionView: View {
15451543 VStack(alignment: .leading, spacing: 3) {
15461544 Text(event.date.formatted(date: .abbreviated, time: .omitted))
15471545 .font(appDensity.font(.caption2))
1548 .foregroundStyle(.secondary)
1546 .foregroundStyle(Color(.appTextSecondary))
15491547 Text(event.summary)
15501548 .font(appDensity.font(.caption))
15511549 Text(event.source)
15521550 .font(appDensity.font(.caption2))
1553 .foregroundStyle(.secondary)
1551 .foregroundStyle(Color(.appTextSecondary))
15541552 }
15551553 }
15561554 } else if let historyError {
@@ -1604,7 +1602,7 @@ struct IntelligenceSectionView: View {
16041602 .font(appDensity.font(.caption, weight: .semibold))
16051603 Text(signal.detail)
16061604 .font(appDensity.font(.caption2))
1607 .foregroundStyle(.secondary)
1605 .foregroundStyle(Color(.appTextSecondary))
16081606 }
16091607 }
16101608 }
@@ -1645,7 +1643,7 @@ struct IntelligenceSectionView: View {
16451643 }
16461644 Text("First \(item.firstSeen.formatted(date: .abbreviated, time: .omitted)) • Last \(item.lastSeen.formatted(date: .abbreviated, time: .omitted)) • Seen \(item.recurrenceCount)x")
16471645 .font(appDensity.font(.caption2))
1648 .foregroundStyle(.secondary)
1646 .foregroundStyle(Color(.appTextSecondary))
16491647 }
16501648 }
16511649 }
@@ -1678,7 +1676,7 @@ struct IntelligenceSectionView: View {
16781676 VStack(alignment: .leading, spacing: 3) {
16791677 Text(date.formatted(date: .abbreviated, time: .omitted))
16801678 .font(appDensity.font(.caption2))
1681 .foregroundStyle(.secondary)
1679 .foregroundStyle(Color(.appTextSecondary))
16821680 Text(title)
16831681 .font(appDensity.font(.caption))
16841682 }
@@ -1755,7 +1753,7 @@ struct SubdomainsSectionView: View {
17551753 if !groups.isEmpty {
17561754 Text("Groups")
17571755 .font(appDensity.font(.caption2))
1758 .foregroundStyle(.secondary)
1756 .foregroundStyle(Color(.appTextSecondary))
17591757 ForEach(groups) { group in
17601758 HStack {
17611759 Text("\(group.label).*")
@@ -1764,7 +1762,7 @@ struct SubdomainsSectionView: View {
17641762 Spacer()
17651763 Text("\(group.subdomains.count)")
17661764 .font(appDensity.font(.caption2))
1767 .foregroundStyle(.secondary)
1765 .foregroundStyle(Color(.appTextSecondary))
17681766 }
17691767 }
17701768 }
@@ -1898,7 +1896,7 @@ struct DNSSectionView: View {
18981896 if let wildcardTitle = section.wildcardTitle {
18991897 Text(wildcardTitle)
19001898 .font(.system(.caption, design: .monospaced))
1901 .foregroundStyle(.secondary)
1899 .foregroundStyle(Color(.appTextSecondary))
19021900 .padding(.top, 4)
19031901 ForEach(section.wildcardRows) { row in
19041902 LabeledValueRow(row: row)
@@ -1940,18 +1938,18 @@ struct DNSSectionView: View {
19401938 VStack(alignment: .leading, spacing: 3) {
19411939 Text(event.date.formatted(date: .abbreviated, time: .omitted))
19421940 .font(appDensity.font(.caption2))
1943 .foregroundStyle(.secondary)
1941 .foregroundStyle(Color(.appTextSecondary))
19441942 Text(event.summary)
19451943 .font(appDensity.font(.caption))
19461944 if !event.aRecords.isEmpty {
19471945 Text("A: \(event.aRecords.joined(separator: ", "))")
19481946 .font(appDensity.font(.caption2))
1949 .foregroundStyle(.secondary)
1947 .foregroundStyle(Color(.appTextSecondary))
19501948 }
19511949 if !event.nameservers.isEmpty {
19521950 Text("NS: \(event.nameservers.joined(separator: ", "))")
19531951 .font(appDensity.font(.caption2))
1954 .foregroundStyle(.secondary)
1952 .foregroundStyle(Color(.appTextSecondary))
19551953 }
19561954 }
19571955 }
@@ -2017,7 +2015,7 @@ struct WebSectionView: View {
20172015 if let sslInfo, !sslInfo.subjectAltNames.isEmpty {
20182016 Text("SANs")
20192017 .font(appDensity.font(.caption2))
2020 .foregroundStyle(.secondary)
2018 .foregroundStyle(Color(.appTextSecondary))
20212019 ForEach(sslInfo.subjectAltNames, id: \.self) { san in
20222020 HStack(alignment: .top, spacing: 8) {
20232021 Text(san)
@@ -2091,7 +2089,7 @@ struct WebSectionView: View {
20912089 HStack(alignment: .top, spacing: 6) {
20922090 Text(redirect.stepLabel)
20932091 .font(appDensity.font(.caption))
2094 .foregroundStyle(.secondary)
2092 .foregroundStyle(Color(.appTextSecondary))
20952093 .frame(width: 16, alignment: .trailing)
20962094 Text(redirect.statusCode)
20972095 .font(appDensity.font(.caption))
@@ -2104,7 +2102,7 @@ struct WebSectionView: View {
21042102 if redirect.isFinal {
21052103 Text("(final)")
21062104 .font(appDensity.font(.caption2))
2107 .foregroundStyle(.secondary)
2105 .foregroundStyle(Color(.appTextSecondary))
21082106 }
21092107 }
21102108 }
@@ -2149,7 +2147,7 @@ struct EmailSectionView: View {
21492147 if !assessment.reasons.isEmpty {
21502148 Text(assessment.reasons.joined(separator: " | "))
21512149 .font(appDensity.font(.caption2))
2152 .foregroundStyle(.secondary)
2150 .foregroundStyle(Color(.appTextSecondary))
21532151 }
21542152 }
21552153 if loading {
@@ -2176,7 +2174,7 @@ struct EmailSectionView: View {
21762174 if let auxiliaryDetail = row.auxiliaryDetail {
21772175 Text(auxiliaryDetail)
21782176 .font(appDensity.font(.caption2))
2179 .foregroundStyle(.secondary)
2177 .foregroundStyle(Color(.appTextSecondary))
21802178 }
21812179 }
21822180 }
@@ -2192,9 +2190,9 @@ struct EmailSectionView: View {
21922190 case .warning:
21932191 return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
21942192 case .failure:
2195 return .init(title: row.status, systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
2193 return .init(title: row.status, systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
21962194 case .primary, .secondary:
2197 return .init(title: row.status, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
2195 return .init(title: row.status, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
21982196 }
21992197 }
22002198}
@@ -2244,7 +2242,7 @@ struct NetworkSectionView: View {
22442242 Spacer()
22452243 Text(row.latencyLabel)
22462244 .font(appDensity.font(.caption2))
2247 .foregroundStyle(.secondary)
2245 .foregroundStyle(Color(.appTextSecondary))
22482246 AppStatusBadgeView(model: reachabilityBadge(row))
22492247 }
22502248 }
@@ -2304,7 +2302,7 @@ struct NetworkSectionView: View {
23042302 } else {
23052303 Text("Standard Ports")
23062304 .font(.system(.caption, design: .monospaced))
2307 .foregroundStyle(.secondary)
2305 .foregroundStyle(Color(.appTextSecondary))
23082306 PortRowsView(rows: standardPortRows)
23092307 }
23102308
@@ -2353,7 +2351,7 @@ struct NetworkSectionView: View {
23532351 case .failure:
23542352 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
23552353 case .primary, .secondary:
2356 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
2354 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
23572355 }
23582356 }
23592357}
@@ -2379,14 +2377,14 @@ struct PortRowsView: View {
23792377 if let durationLabel = row.durationLabel {
23802378 Text(durationLabel)
23812379 .font(appDensity.font(.caption2))
2382 .foregroundStyle(.secondary)
2380 .foregroundStyle(Color(.appTextSecondary))
23832381 }
23842382 AppStatusBadgeView(model: portBadge(row))
23852383 }
23862384 if let banner = row.banner {
23872385 Text(banner)
23882386 .font(appDensity.font(.caption2))
2389 .foregroundStyle(.secondary)
2387 .foregroundStyle(Color(.appTextSecondary))
23902388 .padding(.leading, 8)
23912389 }
23922390 }
@@ -2404,7 +2402,7 @@ struct PortRowsView: View {
24042402 case .failure:
24052403 return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
24062404 case .primary, .secondary:
2407 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
2405 return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
24082406 }
24092407 }
24102408}
@@ -2512,15 +2510,15 @@ struct SectionTrustMetadataView: View {
25122510 if let confidence {
25132511 Text("Confidence \(confidence.title)")
25142512 .font(appDensity.font(.caption2))
2515 .foregroundStyle(.secondary)
2513 .foregroundStyle(Color(.appTextSecondary))
25162514 }
25172515 if let provenance {
25182516 Text(provenance.provider ?? provenance.source)
25192517 .font(appDensity.font(.caption2))
2520 .foregroundStyle(.secondary)
2518 .foregroundStyle(Color(.appTextSecondary))
25212519 Text(provenance.resultSource.label)
25222520 .font(appDensity.font(.caption2))
2523 .foregroundStyle(.secondary)
2521 .foregroundStyle(Color(.appTextSecondary))
25242522 }
25252523 }
25262524 DisclosureGroup("Details") {
@@ -2559,7 +2557,7 @@ struct LabeledValueRow: View {
25592557 VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing - 1) {
25602558 Text(row.label)
25612559 .font(appDensity.font(.caption2))
2562 .foregroundStyle(.secondary)
2560 .foregroundStyle(Color(.appTextSecondary))
25632561 Text(row.value)
25642562 .font(appDensity.font(.caption))
25652563 .foregroundStyle(ResultColors.color(for: row.tone))
@@ -2656,7 +2654,7 @@ struct SettingsView: View {
26562654 if let statusMessage = purchaseService.statusMessage {
26572655 Text(statusMessage)
26582656 .font(appDensity.font(.caption, design: .default))
2659 .foregroundStyle(.secondary)
2657 .foregroundStyle(Color(.appTextSecondary))
26602658 }
26612659
26622660 if let errorMessage = purchaseService.errorMessage {
@@ -2732,10 +2730,17 @@ struct SettingsView: View {
27322730
27332731private struct DisplaySettingsView: View {
27342732 @AppStorage(AppDensity.userDefaultsKey) private var storedDensity = AppDensity.compact.rawValue
2733 @AppStorage(AppAppearance.userDefaultsKey) private var storedAppearance = AppAppearance.system.rawValue
27352734
27362735 var body: some View {
27372736 Form {
27382737 Section("Display") {
2738 Picker("Appearance", selection: $storedAppearance) {
2739 ForEach(AppAppearance.allCases) { appearance in
2740 Text(appearance.title).tag(appearance.rawValue)
2741 }
2742 }
2743
27392744 Picker("Density", selection: $storedDensity) {
27402745 ForEach(AppDensity.allCases) { density in
27412746 Text(density.title).tag(density.rawValue)
@@ -2778,7 +2783,7 @@ private struct HistoryNetworkSettingsView: View {
27782783
27792784 Text("History remains local-first. Auto-prune only trims older local snapshots on this device and defaults to unlimited.")
27802785 .font(appDensity.font(.caption, design: .default))
2781 .foregroundStyle(.secondary)
2786 .foregroundStyle(Color(.appTextSecondary))
27822787 }
27832788
27842789 Section("Network") {
@@ -2870,11 +2875,11 @@ private struct CloudSyncSettingsView: View {
28702875
28712876 Text("iCloud Sync stores DomainDig data in your private iCloud account. DomainDig does not operate a sync server. Disabling sync keeps local data on this device.")
28722877 .font(appDensity.font(.caption, design: .default))
2873 .foregroundStyle(.secondary)
2878 .foregroundStyle(Color(.appTextSecondary))
28742879
28752880 Text(cloudSyncService.detailMessage)
28762881 .font(appDensity.font(.caption, design: .default))
2877 .foregroundStyle(.secondary)
2882 .foregroundStyle(Color(.appTextSecondary))
28782883
28792884 if let lastErrorMessage = cloudSyncService.lastErrorMessage {
28802885 Text(lastErrorMessage)
@@ -2932,7 +2937,7 @@ private struct LocalAPISettingsView: View {
29322937 if let statusMessage = localAPIService.statusMessage {
29332938 Text(statusMessage)
29342939 .font(appDensity.font(.caption, design: .default))
2935 .foregroundStyle(.secondary)
2940 .foregroundStyle(Color(.appTextSecondary))
29362941 }
29372942 }
29382943
@@ -2947,7 +2952,7 @@ private struct LocalAPISettingsView: View {
29472952
29482953 Text("Every request requires either `Authorization: Bearer <token>` or `X-API-Token`. DomainDig stores the token in Keychain and only binds the server to localhost.")
29492954 .font(appDensity.font(.caption, design: .default))
2950 .foregroundStyle(.secondary)
2955 .foregroundStyle(Color(.appTextSecondary))
29512956 }
29522957
29532958 Section("Request Logging") {
@@ -2962,7 +2967,7 @@ private struct LocalAPISettingsView: View {
29622967 if localAPIService.requestLogs.isEmpty {
29632968 Text("No local API requests logged yet.")
29642969 .font(appDensity.font(.caption, design: .default))
2965 .foregroundStyle(.secondary)
2970 .foregroundStyle(Color(.appTextSecondary))
29662971 } else {
29672972 ForEach(localAPIService.requestLogs.prefix(25)) { log in
29682973 VStack(alignment: .leading, spacing: 4) {
@@ -2977,11 +2982,11 @@ private struct LocalAPISettingsView: View {
29772982
29782983 Text(log.timestamp.formatted(date: .abbreviated, time: .standard))
29792984 .font(appDensity.font(.caption2, design: .default))
2980 .foregroundStyle(.secondary)
2985 .foregroundStyle(Color(.appTextSecondary))
29812986
29822987 Text("\(Int(log.duration * 1000)) ms")
29832988 .font(appDensity.font(.caption2, design: .default))
2984 .foregroundStyle(.secondary)
2989 .foregroundStyle(Color(.appTextSecondary))
29852990 }
29862991 }
29872992 }
@@ -3184,13 +3189,13 @@ private struct MonitoringSettingsView: View {
31843189 if let monitoringStatusMessage = viewModel.monitoringStatusMessage {
31853190 Text(monitoringStatusMessage)
31863191 .font(appDensity.font(.caption, design: .default))
3187 .foregroundStyle(.secondary)
3192 .foregroundStyle(Color(.appTextSecondary))
31883193 }
31893194
31903195 if !FeatureAccessService.hasAccess(to: .automatedMonitoring) {
31913196 Text("Background monitoring and alerts are available in Pro.")
31923197 .font(appDensity.font(.caption, design: .default))
3193 .foregroundStyle(.secondary)
3198 .foregroundStyle(Color(.appTextSecondary))
31943199 }
31953200 }
31963201 }
@@ -3260,7 +3265,7 @@ private struct DataPortabilitySettingsView: View {
32603265
32613266 Text(importMode.explanation)
32623267 .font(appDensity.font(.caption, design: .default))
3263 .foregroundStyle(.secondary)
3268 .foregroundStyle(Color(.appTextSecondary))
32643269
32653270 Button("Export Full Backup") {
32663271 exportFullBackup()
@@ -3317,12 +3322,12 @@ private struct DataPortabilitySettingsView: View {
33173322
33183323 Text("Data stays on this device unless you export it. Backup files can include domain history, monitoring settings, and notes. Imported files are processed on-device.")
33193324 .font(appDensity.font(.caption, design: .default))
3320 .foregroundStyle(.secondary)
3325 .foregroundStyle(Color(.appTextSecondary))
33213326
33223327 if let portabilityStatusMessage = viewModel.portabilityStatusMessage {
33233328 Text(portabilityStatusMessage)
33243329 .font(appDensity.font(.caption, design: .default))
3325 .foregroundStyle(.secondary)
3330 .foregroundStyle(Color(.appTextSecondary))
33263331 }
33273332 }
33283333
@@ -3331,7 +3336,7 @@ private struct DataPortabilitySettingsView: View {
33313336 Section("Import Debug") {
33323337 Text(importDebugStatus)
33333338 .font(appDensity.font(.caption, design: .default))
3334 .foregroundStyle(.secondary)
3339 .foregroundStyle(Color(.appTextSecondary))
33353340 .textSelection(.enabled)
33363341 }
33373342 }
@@ -3636,7 +3641,7 @@ private struct DataManagementSettingsView: View {
36363641 if let deleteAllSuccessMessage {
36373642 Text(deleteAllSuccessMessage)
36383643 .font(.footnote.weight(.medium))
3639 .foregroundStyle(.secondary)
3644 .foregroundStyle(Color(.appTextSecondary))
36403645 .padding(.horizontal, 14)
36413646 .padding(.vertical, 10)
36423647 .background(.thinMaterial, in: Capsule())
@@ -3722,7 +3727,7 @@ private struct DataImportPreviewSheet: View {
37223727 Section("Warnings") {
37233728 ForEach(preview.warnings, id: \.self) { warning in
37243729 Text(warning)
3725 .foregroundStyle(.secondary)
3730 .foregroundStyle(Color(.appTextSecondary))
37263731 }
37273732 }
37283733 }
DomainDig/DashboardView.swift +7 −8
@@ -139,7 +139,7 @@ struct DashboardView: View {
139139 .font(appDensity.font(.headline, design: .default, weight: .semibold))
140140 Text("\(group.domains.count) domain\(group.domains.count == 1 ? "" : "s")")
141141 .font(appDensity.font(.caption))
142 .foregroundStyle(.secondary)
142 .foregroundStyle(Color(.appTextSecondary))
143143 }
144144 Spacer()
145145 groupBadge(for: group.domains)
@@ -170,7 +170,6 @@ struct DashboardView: View {
170170 }
171171 }
172172 }
173 .preferredColorScheme(.dark)
174173 }
175174
176175 private func disclosureBinding(for apexDomain: String) -> Binding<Bool> {
@@ -193,7 +192,7 @@ struct DashboardView: View {
193192 VStack(alignment: .leading, spacing: 8) {
194193 Text(title)
195194 .font(appDensity.font(.caption, design: .default, weight: .semibold))
196 .foregroundStyle(.secondary)
195 .foregroundStyle(Color(.appTextSecondary))
197196 Text("\(value)")
198197 .font(.system(size: 28, weight: .bold, design: .rounded))
199198 .foregroundStyle(.primary)
@@ -230,7 +229,7 @@ struct DashboardView: View {
230229 private func dashboardEmptyRow(_ message: String) -> some View {
231230 Text(message)
232231 .font(appDensity.font(.caption))
233 .foregroundStyle(.secondary)
232 .foregroundStyle(Color(.appTextSecondary))
234233 .padding(.vertical, 4)
235234 }
236235
@@ -288,7 +287,7 @@ private struct PortfolioActivityRow: View {
288287 Text(relativeTimestamp(item.timestamp))
289288 }
290289 .font(appDensity.font(.caption2))
291 .foregroundStyle(.secondary)
290 .foregroundStyle(Color(.appTextSecondary))
292291 }
293292 }
294293 .padding(.vertical, 4)
@@ -320,10 +319,10 @@ private struct PortfolioAttentionRow: View {
320319 .foregroundStyle(.primary)
321320 Text(item.reason)
322321 .font(appDensity.font(.caption, design: .default))
323 .foregroundStyle(.secondary)
322 .foregroundStyle(Color(.appTextSecondary))
324323 Text(relativeTimestamp(item.timestamp))
325324 .font(appDensity.font(.caption2))
326 .foregroundStyle(.secondary)
325 .foregroundStyle(Color(.appTextSecondary))
327326 }
328327 }
329328 .padding(.vertical, 4)
@@ -353,7 +352,7 @@ private struct PortfolioExpiryRow: View {
353352 .foregroundStyle(.primary)
354353 Text(expirySubtitle)
355354 .font(appDensity.font(.caption))
356 .foregroundStyle(.secondary)
355 .foregroundStyle(Color(.appTextSecondary))
357356 }
358357 Spacer()
359358 AppStatusBadgeView(model: badgeModel)
DomainDig/DomainCompareView.swift +1 −1
@@ -38,7 +38,7 @@ struct DomainCompareView: View {
3838 Section {
3939 Text("Choose two different domains to compare.")
4040 .font(.callout)
41 .foregroundStyle(.secondary)
41 .foregroundStyle(Color(.appTextSecondary))
4242 }
4343 }
4444
DomainDig/DomainDig/AuditViews.swift +17 −19
@@ -41,13 +41,13 @@ struct AuditListView: View {
4141 Spacer()
4242 Text("\(group.sessions.count) audit\(group.sessions.count == 1 ? "" : "s")")
4343 .font(appDensity.font(.caption2))
44 .foregroundStyle(.secondary)
44 .foregroundStyle(Color(.appTextSecondary))
4545 }
4646
4747 if let latest = group.sessions.first {
4848 Text(latest.findings.isEmpty ? "No findings recorded yet" : latest.findings.map(\.title).prefix(2).joined(separator: " • "))
4949 .font(appDensity.font(.caption))
50 .foregroundStyle(.secondary)
50 .foregroundStyle(Color(.appTextSecondary))
5151 .lineLimit(2)
5252
5353 HStack(spacing: 8) {
@@ -57,7 +57,7 @@ struct AuditListView: View {
5757 }
5858 Text("Checklist \(latest.completedChecklistCount)/\(latest.checklist.count)")
5959 .font(appDensity.font(.caption2))
60 .foregroundStyle(.secondary)
60 .foregroundStyle(Color(.appTextSecondary))
6161 }
6262 }
6363 }
@@ -85,7 +85,6 @@ struct AuditListView: View {
8585 }
8686 }
8787 }
88 .preferredColorScheme(.dark)
8988 }
9089}
9190
@@ -116,7 +115,7 @@ struct AuditDomainTimelineView: View {
116115
117116 Text("Reviewer: \(session.reviewer)")
118117 .font(appDensity.font(.caption))
119 .foregroundStyle(.secondary)
118 .foregroundStyle(Color(.appTextSecondary))
120119
121120 HStack(spacing: 8) {
122121 Text("\(session.findings.count) findings")
@@ -126,7 +125,7 @@ struct AuditDomainTimelineView: View {
126125 }
127126 }
128127 .font(appDensity.font(.caption2))
129 .foregroundStyle(.secondary)
128 .foregroundStyle(Color(.appTextSecondary))
130129 }
131130 .padding(.vertical, 4)
132131 }
@@ -143,11 +142,11 @@ struct AuditDomainTimelineView: View {
143142 Spacer()
144143 Text("\(point.findingCount) findings")
145144 .font(appDensity.font(.caption2))
146 .foregroundStyle(.secondary)
145 .foregroundStyle(Color(.appTextSecondary))
147146 }
148147 Text("Open high severity: \(point.openHighSeverityCount) • Repeated issues: \(point.repeatedIssueCount)")
149148 .font(appDensity.font(.caption2))
150 .foregroundStyle(.secondary)
149 .foregroundStyle(Color(.appTextSecondary))
151150 }
152151 .padding(.vertical, 2)
153152 }
@@ -197,7 +196,7 @@ struct AuditSessionDetailView: View {
197196 .font(appDensity.font(.headline, design: .default, weight: .semibold))
198197 Text("Reviewer: \(session.reviewer)")
199198 .font(appDensity.font(.caption))
200 .foregroundStyle(.secondary)
199 .foregroundStyle(Color(.appTextSecondary))
201200 }
202201 Spacer()
203202 auditStatusBadge(session.status)
@@ -215,7 +214,7 @@ struct AuditSessionDetailView: View {
215214
216215 Text("Captured \(session.evidence.capturedAt.formatted(date: .abbreviated, time: .shortened))")
217216 .font(appDensity.font(.caption2))
218 .foregroundStyle(.secondary)
217 .foregroundStyle(Color(.appTextSecondary))
219218 }
220219 }
221220 .listRowBackground(Color(.appSurface))
@@ -256,7 +255,7 @@ struct AuditSessionDetailView: View {
256255 .foregroundStyle(.primary)
257256 Text(item.detail)
258257 .font(appDensity.font(.caption))
259 .foregroundStyle(.secondary)
258 .foregroundStyle(Color(.appTextSecondary))
260259 }
261260 Spacer()
262261 }
@@ -270,7 +269,7 @@ struct AuditSessionDetailView: View {
270269 if session.findings.isEmpty {
271270 Text("No findings recorded.")
272271 .font(appDensity.font(.caption))
273 .foregroundStyle(.secondary)
272 .foregroundStyle(Color(.appTextSecondary))
274273 } else {
275274 ForEach(session.findings) { finding in
276275 Button {
@@ -286,11 +285,11 @@ struct AuditSessionDetailView: View {
286285 }
287286 Text(finding.summary)
288287 .font(appDensity.font(.caption))
289 .foregroundStyle(.secondary)
288 .foregroundStyle(Color(.appTextSecondary))
290289 .lineLimit(2)
291290 Text("\(finding.status.title) • \(finding.evidenceReferences.count) evidence refs")
292291 .font(appDensity.font(.caption2))
293 .foregroundStyle(.secondary)
292 .foregroundStyle(Color(.appTextSecondary))
294293 }
295294 .padding(.vertical, 2)
296295 }
@@ -321,7 +320,7 @@ struct AuditSessionDetailView: View {
321320 .font(appDensity.font(.caption, weight: .semibold))
322321 Text(entry.changeSummaryMessage ?? "No change summary")
323322 .font(appDensity.font(.caption2))
324 .foregroundStyle(.secondary)
323 .foregroundStyle(Color(.appTextSecondary))
325324 }
326325 }
327326 }
@@ -337,11 +336,11 @@ struct AuditSessionDetailView: View {
337336 Spacer()
338337 Text(point.status.title)
339338 .font(appDensity.font(.caption2))
340 .foregroundStyle(.secondary)
339 .foregroundStyle(Color(.appTextSecondary))
341340 }
342341 Text("Findings \(point.findingCount) • Open high \(point.openHighSeverityCount) • Repeated \(point.repeatedIssueCount)")
343342 .font(appDensity.font(.caption2))
344 .foregroundStyle(.secondary)
343 .foregroundStyle(Color(.appTextSecondary))
345344 }
346345 }
347346 }
@@ -401,7 +400,6 @@ struct AuditSessionDetailView: View {
401400 .background(Color(.appBackground))
402401 }
403402 }
404 .preferredColorScheme(.dark)
405403 }
406404}
407405
@@ -459,7 +457,7 @@ private struct AuditFindingEditorView: View {
459457 ForEach(session.evidence.screenshots) { asset in
460458 Text("\(asset.title): \(asset.reference)")
461459 .font(.caption)
462 .foregroundStyle(.secondary)
460 .foregroundStyle(Color(.appTextSecondary))
463461 }
464462 }
465463 }
DomainDig/DomainDigApp.swift +3
@@ -12,6 +12,7 @@ struct DomainDigApp: App {
1212 @UIApplicationDelegateAdaptor(DomainDigAppDelegate.self) private var appDelegate
1313 @Environment(\.scenePhase) private var scenePhase
1414 @AppStorage(AppDensity.userDefaultsKey) private var density = AppDensity.compact.rawValue
15 @AppStorage(AppAppearance.userDefaultsKey) private var appearance = AppAppearance.system.rawValue
1516 @State private var viewModel = DomainViewModel()
1617 @State private var purchaseService = PurchaseService.shared
1718 @State private var cloudSyncService = CloudSyncService.shared
@@ -27,6 +28,8 @@ struct DomainDigApp: App {
2728 WindowGroup {
2829 RootTabView(viewModel: viewModel)
2930 .environment(\.appDensity, AppDensity(rawValue: density) ?? .compact)
31 // The single place appearance is applied. Keep it that way.
32 .preferredColorScheme((AppAppearance(rawValue: appearance) ?? .system).colorScheme)
3033 .task {
3134 let _ = purchaseService.currentTier
3235 let _ = cloudSyncService.status
DomainDig/DomainDigUI.swift +47 −8
@@ -6,6 +6,45 @@ import UIKit
66import AppKit
77#endif
88
9/// User-selected appearance, applied once at the `WindowGroup`.
10///
11/// Deliberately applied in exactly one place. The app previously carried 16
12/// separate `.preferredColorScheme(.dark)` calls scattered across view bodies,
13/// which is how it became impossible to reach light mode at all — re-applying
14/// per view is what let the lock spread unnoticed.
15enum AppAppearance: String, CaseIterable, Identifiable {
16 case system
17 case light
18 case dark
19
20 static let userDefaultsKey = "appAppearance"
21
22 var id: String { rawValue }
23
24 var title: String {
25 switch self {
26 case .system:
27 return "System"
28 case .light:
29 return "Light"
30 case .dark:
31 return "Dark"
32 }
33 }
34
35 /// `nil` hands control back to the system setting.
36 var colorScheme: ColorScheme? {
37 switch self {
38 case .system:
39 return nil
40 case .light:
41 return .light
42 case .dark:
43 return .dark
44 }
45 }
46}
47
948enum AppDensity: String, CaseIterable, Identifiable {
1049 case compact
1150 case comfortable
@@ -96,7 +135,7 @@ enum AppStatusFactory {
96135 case .registered:
97136 return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
98137 case .unknown, .none:
99 return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
138 return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
100139 }
101140 }
102141
@@ -112,7 +151,7 @@ enum AppStatusFactory {
112151
113152 static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel {
114153 guard error == nil, let result else {
115 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
154 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
116155 }
117156
118157 let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count
@@ -122,18 +161,18 @@ enum AppStatusFactory {
122161 case 1, 2:
123162 return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
124163 default:
125 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
164 return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
126165 }
127166 }
128167
129168 static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel {
130169 guard let summary else {
131 return .init(title: "Unchanged", systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
170 return .init(title: "Unchanged", systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
132171 }
133172 if summary.hasChanges {
134173 return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16))
135174 }
136 return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
175 return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
137176 }
138177}
139178
@@ -259,7 +298,7 @@ struct EmptyStateCardView: View {
259298
260299 Text(message)
261300 .font(appDensity.font(.body))
262 .foregroundStyle(.secondary)
301 .foregroundStyle(Color(.appTextSecondary))
263302 .fixedSize(horizontal: false, vertical: true)
264303
265304 Text(suggestion)
@@ -311,14 +350,14 @@ struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View {
311350 if let subtitle {
312351 Text(subtitle)
313352 .font(appDensity.font(.caption))
314 .foregroundStyle(.secondary)
353 .foregroundStyle(Color(.appTextSecondary))
315354 }
316355 }
317356 Spacer(minLength: 8)
318357 trailing()
319358 Image(systemName: isCollapsed ? "chevron.down" : "chevron.up")
320359 .font(.caption.weight(.semibold))
321 .foregroundStyle(.secondary)
360 .foregroundStyle(Color(.appTextSecondary))
322361 }
323362 .contentShape(Rectangle())
324363 .frame(minHeight: appDensity.metrics.controlMinHeight, alignment: .center)
DomainDig/HistoryView.swift +5 −7
@@ -40,12 +40,12 @@ struct HistoryView: View {
4040 Spacer()
4141 Text("\(item.count) snapshots")
4242 .font(appDensity.font(.caption2))
43 .foregroundStyle(.secondary)
43 .foregroundStyle(Color(.appTextSecondary))
4444 }
4545
4646 Text(item.latest.changeSummaryMessage ?? "No change summary")
4747 .font(appDensity.font(.caption))
48 .foregroundStyle(.secondary)
48 .foregroundStyle(Color(.appTextSecondary))
4949 .lineLimit(2)
5050
5151 HStack(spacing: 8) {
@@ -129,7 +129,6 @@ struct HistoryView: View {
129129 .onChange(of: viewModel.rerunNavigationToken) { _, _ in
130130 dismiss()
131131 }
132 .preferredColorScheme(.dark)
133132 }
134133
135134 private func deleteFilteredHistoryEntries(at offsets: IndexSet) {
@@ -372,7 +371,6 @@ struct HistoryDetailView: View {
372371 }
373372 }
374373 }
375 .preferredColorScheme(.dark)
376374 }
377375
378376 private var snapshotBanner: some View {
@@ -385,7 +383,7 @@ struct HistoryDetailView: View {
385383 Spacer()
386384 Text("Live re-run available")
387385 .font(appDensity.font(.caption2))
388 .foregroundStyle(.secondary)
386 .foregroundStyle(Color(.appTextSecondary))
389387 }
390388 if let mismatchNote = viewModel.resolverMismatchNote(for: entry) {
391389 Text(mismatchNote)
@@ -400,10 +398,10 @@ struct HistoryDetailView: View {
400398 if let note = entry.note, !note.isEmpty {
401399 Text(note)
402400 .font(appDensity.font(.caption2))
403 .foregroundStyle(.secondary)
401 .foregroundStyle(Color(.appTextSecondary))
404402 }
405403 }
406 .foregroundStyle(.secondary)
404 .foregroundStyle(Color(.appTextSecondary))
407405 .padding(8)
408406 .frame(maxWidth: .infinity, alignment: .leading)
409407 .background(Color(.appSurface))
DomainDig/IntegrationsView.swift +11 −11
@@ -15,7 +15,7 @@ struct IntegrationsSettingsView: View {
1515 if let statusMessage = integrationService.statusMessage {
1616 Text(statusMessage)
1717 .font(.caption)
18 .foregroundStyle(.secondary)
18 .foregroundStyle(Color(.appTextSecondary))
1919 }
2020
2121 Button("Process Queue Now") {
@@ -26,7 +26,7 @@ struct IntegrationsSettingsView: View {
2626 Section("Targets") {
2727 if integrationService.targets.isEmpty {
2828 Text("No integrations configured.")
29 .foregroundStyle(.secondary)
29 .foregroundStyle(Color(.appTextSecondary))
3030 } else {
3131 ForEach(integrationService.targets) { target in
3232 NavigationLink {
@@ -42,12 +42,12 @@ struct IntegrationsSettingsView: View {
4242 Text(target.name)
4343 Spacer()
4444 Text(target.type.title)
45 .foregroundStyle(.secondary)
45 .foregroundStyle(Color(.appTextSecondary))
4646 }
4747
4848 Text(summary(for: target))
4949 .font(.caption)
50 .foregroundStyle(.secondary)
50 .foregroundStyle(Color(.appTextSecondary))
5151
5252 if !target.isEnabled {
5353 Text("Disabled")
@@ -138,7 +138,7 @@ private struct IntegrationDetailView: View {
138138 Section("Delivery Log") {
139139 if integrationService.deliveryRecords(for: target.id).isEmpty {
140140 Text("No deliveries yet.")
141 .foregroundStyle(.secondary)
141 .foregroundStyle(Color(.appTextSecondary))
142142 } else {
143143 ForEach(integrationService.deliveryRecords(for: target.id), id: \.id) { record in
144144 VStack(alignment: .leading, spacing: 4) {
@@ -147,7 +147,7 @@ private struct IntegrationDetailView: View {
147147 Spacer()
148148 Text(record.timestamp.formatted(date: .abbreviated, time: .shortened))
149149 .font(.caption)
150 .foregroundStyle(.secondary)
150 .foregroundStyle(Color(.appTextSecondary))
151151 }
152152
153153 Text(record.summary)
@@ -155,7 +155,7 @@ private struct IntegrationDetailView: View {
155155
156156 Text(record.destination)
157157 .font(.caption)
158 .foregroundStyle(.secondary)
158 .foregroundStyle(Color(.appTextSecondary))
159159
160160 if let failureReason = record.failureReason {
161161 let failureColor: Color = record.status == .skipped ? .secondary : Color(.statusCritical)
@@ -169,7 +169,7 @@ private struct IntegrationDetailView: View {
169169 }
170170 } else {
171171 Text("Integration not found.")
172 .foregroundStyle(.secondary)
172 .foregroundStyle(Color(.appTextSecondary))
173173 }
174174 }
175175 .navigationTitle(target?.name ?? "Integration")
@@ -265,7 +265,7 @@ private struct IntegrationEditorView: View {
265265 if existingTarget != nil {
266266 Text("Saved webhook URL remains in Keychain unless you replace it.")
267267 .font(.caption)
268 .foregroundStyle(.secondary)
268 .foregroundStyle(Color(.appTextSecondary))
269269 }
270270 }
271271 case .slack:
@@ -278,7 +278,7 @@ private struct IntegrationEditorView: View {
278278 if existingTarget != nil {
279279 Text("Saved Slack webhook remains in Keychain unless you replace it.")
280280 .font(.caption)
281 .foregroundStyle(.secondary)
281 .foregroundStyle(Color(.appTextSecondary))
282282 }
283283 }
284284 case .email:
@@ -315,7 +315,7 @@ private struct IntegrationEditorView: View {
315315 if existingTarget != nil {
316316 Text("Saved SMTP password remains in Keychain unless you replace it.")
317317 .font(.caption)
318 .foregroundStyle(.secondary)
318 .foregroundStyle(Color(.appTextSecondary))
319319 }
320320 }
321321 }
DomainDig/MonitoringView.swift +6 −8
@@ -31,7 +31,7 @@ struct MonitoringView: View {
3131 !monitoringStatusMessage.isEmpty {
3232 Text(monitoringStatusMessage)
3333 .font(appDensity.font(.caption))
34 .foregroundStyle(.secondary)
34 .foregroundStyle(Color(.appTextSecondary))
3535 }
3636
3737 Button(viewModel.monitoringRunInProgress ? "Monitoring…" : "Run Now") {
@@ -68,12 +68,12 @@ struct MonitoringView: View {
6868 Spacer()
6969 Text(log.timestamp.formatted(date: .abbreviated, time: .shortened))
7070 .font(appDensity.font(.caption2))
71 .foregroundStyle(.secondary)
71 .foregroundStyle(Color(.appTextSecondary))
7272 }
7373
7474 Text(log.summary)
7575 .font(appDensity.font(.caption))
76 .foregroundStyle(.secondary)
76 .foregroundStyle(Color(.appTextSecondary))
7777
7878 HStack(spacing: 8) {
7979 metricBadge(title: "\(log.domainsChecked) checked")
@@ -95,7 +95,6 @@ struct MonitoringView: View {
9595 .scrollContentBackground(.hidden)
9696 .background(Color(.appBackground))
9797 .navigationTitle("Monitoring")
98 .preferredColorScheme(.dark)
9998 .onAppear {
10099 viewModel.refreshMonitoringState()
101100 }
@@ -152,7 +151,7 @@ struct MonitoringLogDetailView: View {
152151
153152 Text(result.summaryMessage)
154153 .font(appDensity.font(.caption))
155 .foregroundStyle(.secondary)
154 .foregroundStyle(Color(.appTextSecondary))
156155
157156 HStack(spacing: 10) {
158157 Text(result.resultSource.label)
@@ -162,7 +161,7 @@ struct MonitoringLogDetailView: View {
162161 }
163162 }
164163 .font(appDensity.font(.caption2))
165 .foregroundStyle(.secondary)
164 .foregroundStyle(Color(.appTextSecondary))
166165
167166 if let errorMessage = result.errorMessage {
168167 Text(errorMessage)
@@ -188,7 +187,7 @@ struct MonitoringLogDetailView: View {
188187 ForEach(log.errors, id: \.self) { error in
189188 Text(error)
190189 .font(appDensity.font(.caption))
191 .foregroundStyle(.secondary)
190 .foregroundStyle(Color(.appTextSecondary))
192191 }
193192 }
194193 .listRowBackground(Color(.appSurface))
@@ -197,7 +196,6 @@ struct MonitoringLogDetailView: View {
197196 .scrollContentBackground(.hidden)
198197 .background(Color(.appBackground))
199198 .navigationTitle("Run Details")
200 .preferredColorScheme(.dark)
201199 }
202200
203201 private func color(for severity: MonitoringAlertSeverity) -> Color {
DomainDig/PaywallView.swift +4 −4
@@ -12,7 +12,7 @@ struct PaywallView: View {
1212 Section {
1313 Text("Pro unlocks workflows, scale, monitoring automation, and exports. Pro+ adds deeper external intelligence with no additional usage limits.")
1414 .font(appDensity.font(.body, design: .default))
15 .foregroundStyle(.secondary)
15 .foregroundStyle(Color(.appTextSecondary))
1616 .fixedSize(horizontal: false, vertical: true)
1717 }
1818
@@ -38,7 +38,7 @@ struct PaywallView: View {
3838 } else if purchaseService.products.isEmpty {
3939 Text("Pricing is unavailable right now.")
4040 .font(appDensity.font(.caption, design: .default))
41 .foregroundStyle(.secondary)
41 .foregroundStyle(Color(.appTextSecondary))
4242
4343 Button("Retry") {
4444 Task {
@@ -58,7 +58,7 @@ struct PaywallView: View {
5858 .foregroundStyle(.primary)
5959 Text(product.displayPrice)
6060 .font(appDensity.font(.callout, design: .default))
61 .foregroundStyle(.secondary)
61 .foregroundStyle(Color(.appTextSecondary))
6262 }
6363 .frame(maxWidth: .infinity, alignment: .leading)
6464 }
@@ -69,7 +69,7 @@ struct PaywallView: View {
6969 if let statusMessage = purchaseService.statusMessage {
7070 Text(statusMessage)
7171 .font(appDensity.font(.caption, design: .default))
72 .foregroundStyle(.secondary)
72 .foregroundStyle(Color(.appTextSecondary))
7373 }
7474
7575 if let errorMessage = purchaseService.errorMessage {
DomainDig/SavedDomainsView.swift +1 −2
@@ -9,7 +9,7 @@ struct SavedDomainsView: View {
99 if viewModel.savedDomains.isEmpty {
1010 Text("No saved domains")
1111 .font(.system(.callout, design: .monospaced))
12 .foregroundStyle(.secondary)
12 .foregroundStyle(Color(.appTextSecondary))
1313 .listRowBackground(Color(.appSurface))
1414 } else {
1515 ForEach(viewModel.savedDomains, id: \.self) { domain in
@@ -37,6 +37,5 @@ struct SavedDomainsView: View {
3737 EditButton()
3838 }
3939 }
40 .preferredColorScheme(.dark)
4140 }
4241}
DomainDig/ScheduledReportsView.swift +3 −3
@@ -13,7 +13,7 @@ struct ScheduledReportsView: View {
1313 if !FeatureAccessService.hasAccess(to: .automatedMonitoring) {
1414 Text("Scheduled reports require Pro.")
1515 .font(appDensity.font(.caption))
16 .foregroundStyle(.secondary)
16 .foregroundStyle(Color(.appTextSecondary))
1717 }
1818
1919 Toggle("Scheduled Reports", isOn: Binding(
@@ -59,7 +59,7 @@ struct ScheduledReportsView: View {
5959 if let statusMessage {
6060 Text(statusMessage)
6161 .font(appDensity.font(.caption))
62 .foregroundStyle(.secondary)
62 .foregroundStyle(Color(.appTextSecondary))
6363 }
6464
6565 Button(isGenerating ? "Generating…" : "Generate Now") {
@@ -91,7 +91,7 @@ struct ScheduledReportsView: View {
9191 .foregroundStyle(.primary)
9292 Text(log.generatedAt.formatted(date: .abbreviated, time: .shortened))
9393 .font(.caption)
94 .foregroundStyle(.secondary)
94 .foregroundStyle(Color(.appTextSecondary))
9595 }
9696 }
9797 }
DomainDig/TimelineView.swift +4 −4
@@ -125,7 +125,7 @@ private struct TimelineRow: View {
125125
126126 Text(summary.changeSummaryMessage ?? "No change summary")
127127 .font(appDensity.font(.caption))
128 .foregroundStyle(.secondary)
128 .foregroundStyle(Color(.appTextSecondary))
129129 .lineLimit(2)
130130
131131 HStack(spacing: 8) {
@@ -137,14 +137,14 @@ private struct TimelineRow: View {
137137 }
138138 }
139139 .font(appDensity.font(.caption2))
140 .foregroundStyle(.secondary)
140 .foregroundStyle(Color(.appTextSecondary))
141141
142142 if !entry.intelligenceTimeline.isEmpty {
143143 VStack(alignment: .leading, spacing: 4) {
144144 ForEach(Array(entry.intelligenceTimeline.prefix(2))) { event in
145145 Text("\(event.title): \(event.detail)")
146146 .font(appDensity.font(.caption2))
147 .foregroundStyle(.secondary)
147 .foregroundStyle(Color(.appTextSecondary))
148148 .lineLimit(1)
149149 }
150150 }
@@ -161,7 +161,7 @@ private struct TimelineRow: View {
161161 .lineLimit(1)
162162 }
163163 .font(appDensity.font(.caption2))
164 .foregroundStyle(.secondary)
164 .foregroundStyle(Color(.appTextSecondary))
165165 }
166166 }
167167}
DomainDig/WatchlistView.swift +12 −14
@@ -42,7 +42,7 @@ struct WatchlistView: View {
4242 HStack {
4343 Text(viewModel.batchProgressLabel)
4444 .font(appDensity.font(.caption))
45 .foregroundStyle(.secondary)
45 .foregroundStyle(Color(.appTextSecondary))
4646 Spacer()
4747 if viewModel.batchLookupRunning {
4848 Button("Cancel") {
@@ -78,7 +78,7 @@ struct WatchlistView: View {
7878 Section {
7979 Text(limitMessage)
8080 .font(appDensity.font(.caption))
81 .foregroundStyle(.secondary)
81 .foregroundStyle(Color(.appTextSecondary))
8282 }
8383 .listRowBackground(Color(.appSurface))
8484 }
@@ -208,7 +208,7 @@ struct WatchlistView: View {
208208 Section {
209209 Text("Adds the domain directly to your watchlist so monitoring can run without a prior inspection.")
210210 .font(appDensity.font(.caption))
211 .foregroundStyle(.secondary)
211 .foregroundStyle(Color(.appTextSecondary))
212212 }
213213
214214 if let addDomainError {
@@ -267,7 +267,7 @@ struct WatchlistView: View {
267267 .foregroundStyle(.primary)
268268 Text([view.tag, view.filter.title, view.sort.title].compactMap { $0 }.joined(separator: " • "))
269269 .font(.caption)
270 .foregroundStyle(.secondary)
270 .foregroundStyle(Color(.appTextSecondary))
271271 }
272272 }
273273 }
@@ -288,7 +288,6 @@ struct WatchlistView: View {
288288 }
289289 }
290290 }
291 .preferredColorScheme(.dark)
292291 }
293292
294293 @ViewBuilder
@@ -450,12 +449,12 @@ struct WatchlistRowView: View {
450449
451450 Text("Updated \(trackedDomain.updatedAt.formatted(date: .abbreviated, time: .shortened))")
452451 .font(appDensity.font(.caption2))
453 .foregroundStyle(.secondary)
452 .foregroundStyle(Color(.appTextSecondary))
454453
455454 if let collaboration = trackedDomain.collaboration, collaboration.isShared {
456455 Text("\(collaboration.ownership.title) • \(collaboration.permission.title)")
457456 .font(appDensity.font(.caption2))
458 .foregroundStyle(.secondary)
457 .foregroundStyle(Color(.appTextSecondary))
459458 }
460459
461460 HStack(spacing: 8) {
@@ -468,19 +467,19 @@ struct WatchlistRowView: View {
468467 }
469468 }
470469 .font(appDensity.font(.caption2))
471 .foregroundStyle(.secondary)
470 .foregroundStyle(Color(.appTextSecondary))
472471
473472 indicatorRow
474473
475474 if let note = trackedDomain.note?.trimmingCharacters(in: .whitespacesAndNewlines), !note.isEmpty {
476475 Text(note)
477476 .font(appDensity.font(.caption))
478 .foregroundStyle(.secondary)
477 .foregroundStyle(Color(.appTextSecondary))
479478 .lineLimit(2)
480479 } else if let summary = trackedDomain.lastChangeSummary {
481480 Text(summary.message)
482481 .font(appDensity.font(.caption))
483 .foregroundStyle(.secondary)
482 .foregroundStyle(Color(.appTextSecondary))
484483 .lineLimit(2)
485484 }
486485 }
@@ -502,7 +501,7 @@ struct WatchlistRowView: View {
502501 @ViewBuilder
503502 private var statusBadge: some View {
504503 if isRefreshing {
505 AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)))
504 AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)))
506505 } else {
507506 AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability))
508507 }
@@ -700,7 +699,7 @@ struct TrackedDomainDetailView: View {
700699 if latestSnapshots.isEmpty {
701700 Text("No snapshots yet")
702701 .font(.system(.caption, design: .monospaced))
703 .foregroundStyle(.secondary)
702 .foregroundStyle(Color(.appTextSecondary))
704703 } else {
705704 ForEach(latestSnapshots) { entry in
706705 NavigationLink {
@@ -712,7 +711,7 @@ struct TrackedDomainDetailView: View {
712711 .foregroundStyle(.primary)
713712 Text(entry.changeSummary?.hasChanges == true ? "Changed" : "Snapshot")
714713 .font(.system(.caption2, design: .monospaced))
715 .foregroundStyle(.secondary)
714 .foregroundStyle(Color(.appTextSecondary))
716715 }
717716 }
718717 }
@@ -723,7 +722,6 @@ struct TrackedDomainDetailView: View {
723722 .scrollContentBackground(.hidden)
724723 .background(Color(.appBackground))
725724 .navigationTitle(liveTrackedDomain.domain)
726 .preferredColorScheme(.dark)
727725 .onChange(of: viewModel.rerunNavigationToken) { _, _ in
728726 dismiss()
729727 }
DomainDig/WorkflowsView.swift +16 −21
@@ -22,7 +22,7 @@ struct WorkflowsView: View {
2222 Section {
2323 Text(limitMessage)
2424 .font(appDensity.font(.caption))
25 .foregroundStyle(.secondary)
25 .foregroundStyle(Color(.appTextSecondary))
2626 }
2727 .listRowBackground(Color(.appSurface))
2828 }
@@ -54,7 +54,6 @@ struct WorkflowsView: View {
5454 .sheet(item: workflowSummaryBinding) { summary in
5555 WorkflowRunSummaryView(viewModel: viewModel, summary: summary)
5656 }
57 .preferredColorScheme(.dark)
5857 }
5958
6059 private var workflowSummaryBinding: Binding<WorkflowRunSummary?> {
@@ -78,7 +77,7 @@ struct WorkflowsView: View {
7877 HStack {
7978 Text(viewModel.batchProgressLabel)
8079 .font(appDensity.font(.caption))
81 .foregroundStyle(.secondary)
80 .foregroundStyle(Color(.appTextSecondary))
8281 Spacer()
8382 if viewModel.batchLookupRunning {
8483 Button("Cancel") {
@@ -142,23 +141,23 @@ private struct WorkflowRowView: View {
142141 Spacer(minLength: 8)
143142 Text("\(workflow.domains.count) domains")
144143 .font(appDensity.font(.caption2))
145 .foregroundStyle(.secondary)
144 .foregroundStyle(Color(.appTextSecondary))
146145 }
147146
148147 Text("Updated \(workflow.updatedAt.formatted(date: .abbreviated, time: .shortened))")
149148 .font(appDensity.font(.caption2))
150 .foregroundStyle(.secondary)
149 .foregroundStyle(Color(.appTextSecondary))
151150
152151 if let collaboration = workflow.collaboration, collaboration.isShared {
153152 Text("\(collaboration.ownership.title) • \(collaboration.permission.title)")
154153 .font(appDensity.font(.caption2))
155 .foregroundStyle(.secondary)
154 .foregroundStyle(Color(.appTextSecondary))
156155 }
157156
158157 if let notes = workflow.notes, !notes.isEmpty {
159158 Text(notes)
160159 .font(appDensity.font(.caption))
161 .foregroundStyle(.secondary)
160 .foregroundStyle(Color(.appTextSecondary))
162161 .lineLimit(2)
163162 }
164163 }
@@ -197,12 +196,12 @@ struct WorkflowDetailView: View {
197196 if let notes = workflow.notes, !notes.isEmpty {
198197 Text(notes)
199198 .font(appDensity.font(.caption))
200 .foregroundStyle(.secondary)
199 .foregroundStyle(Color(.appTextSecondary))
201200 }
202201 if let collaboration = workflow.collaboration, collaboration.isShared {
203202 Text("\(collaboration.ownership.title) • \(collaboration.permission.title)")
204203 .font(appDensity.font(.caption))
205 .foregroundStyle(.secondary)
204 .foregroundStyle(Color(.appTextSecondary))
206205 }
207206 }
208207
@@ -277,7 +276,7 @@ struct WorkflowDetailView: View {
277276 if !latestSummary.workflowInsights.isEmpty {
278277 Text(latestSummary.workflowInsights[0].description)
279278 .font(appDensity.font(.caption))
280 .foregroundStyle(.secondary)
279 .foregroundStyle(Color(.appTextSecondary))
281280 }
282281
283282 Button {
@@ -306,7 +305,7 @@ struct WorkflowDetailView: View {
306305 if workflow.domains.isEmpty {
307306 Text("No domains in this workflow")
308307 .font(appDensity.font(.caption))
309 .foregroundStyle(.secondary)
308 .foregroundStyle(Color(.appTextSecondary))
310309 } else {
311310 ForEach(workflow.domains, id: \.self) { domain in
312311 Button {
@@ -318,7 +317,7 @@ struct WorkflowDetailView: View {
318317 .foregroundStyle(.primary)
319318 Spacer()
320319 Image(systemName: "arrow.up.right.circle")
321 .foregroundStyle(.secondary)
320 .foregroundStyle(Color(.appTextSecondary))
322321 }
323322 }
324323 .buttonStyle(.plain)
@@ -355,17 +354,16 @@ struct WorkflowDetailView: View {
355354 } else {
356355 Text("Workflow not found")
357356 .font(appDensity.font(.callout))
358 .foregroundStyle(.secondary)
357 .foregroundStyle(Color(.appTextSecondary))
359358 }
360359 }
361 .preferredColorScheme(.dark)
362360 }
363361
364362 private func statRow(label: String, value: String) -> some View {
365363 HStack {
366364 Text(label)
367365 .font(appDensity.font(.caption))
368 .foregroundStyle(.secondary)
366 .foregroundStyle(Color(.appTextSecondary))
369367 Spacer()
370368 Text(value)
371369 .font(appDensity.font(.callout))
@@ -449,7 +447,6 @@ struct WorkflowComposerView: View {
449447 }
450448 }
451449 }
452 .preferredColorScheme(.dark)
453450 }
454451
455452 private var parsedDomains: [String] {
@@ -507,7 +504,7 @@ struct WorkflowRunSummaryView: View {
507504 .foregroundStyle(.primary)
508505 Text(insight.domainsInvolved.joined(separator: ", "))
509506 .font(.system(.caption, design: .monospaced))
510 .foregroundStyle(.secondary)
507 .foregroundStyle(Color(.appTextSecondary))
511508 }
512509 }
513510 }
@@ -517,7 +514,7 @@ struct WorkflowRunSummaryView: View {
517514 if visibleResults.isEmpty {
518515 Text("No domains with meaningful changes or warnings")
519516 .font(.system(.caption, design: .monospaced))
520 .foregroundStyle(.secondary)
517 .foregroundStyle(Color(.appTextSecondary))
521518 } else {
522519 ForEach(visibleResults) { result in
523520 if let entry = viewModel.historyEntry(for: result) {
@@ -578,14 +575,13 @@ struct WorkflowRunSummaryView: View {
578575 }
579576 }
580577 }
581 .preferredColorScheme(.dark)
582578 }
583579
584580 private func statRow(label: String, value: String) -> some View {
585581 HStack {
586582 Text(label)
587583 .font(.system(.caption, design: .monospaced))
588 .foregroundStyle(.secondary)
584 .foregroundStyle(Color(.appTextSecondary))
589585 Spacer()
590586 Text(value)
591587 .font(.system(.callout, design: .monospaced))
@@ -687,7 +683,6 @@ struct WorkflowBulkAddSheet: View {
687683 }
688684 }
689685 }
690 .preferredColorScheme(.dark)
691686 }
692687
693688 private var selectedDomainList: [String] {
DomainDigWidget/DomainDigPortfolioWidget.swift +9 −9
@@ -61,10 +61,10 @@ struct DomainDigWidgetView: View {
6161 VStack(spacing: 6) {
6262 Image(systemName: "magnifyingglass")
6363 .font(.title2)
64 .foregroundStyle(.secondary)
64 .foregroundStyle(Color(.appTextSecondary))
6565 Text("No tracked domains")
6666 .font(.caption)
67 .foregroundStyle(.secondary)
67 .foregroundStyle(Color(.appTextSecondary))
6868 .multilineTextAlignment(.center)
6969 }
7070 }
@@ -80,13 +80,13 @@ struct DomainDigWidgetView: View {
8080 Spacer()
8181 }
8282 .font(.caption2)
83 .foregroundStyle(.secondary)
83 .foregroundStyle(Color(.appTextSecondary))
8484
8585 Text("\(data.totalDomains)")
8686 .font(.system(size: 34, weight: .bold, design: .rounded))
8787 Text("tracked")
8888 .font(.caption2)
89 .foregroundStyle(.secondary)
89 .foregroundStyle(Color(.appTextSecondary))
9090
9191 Spacer(minLength: 0)
9292
@@ -113,11 +113,11 @@ struct DomainDigWidgetView: View {
113113 Label("Domain Portfolio", systemImage: "shield.lefthalf.filled")
114114 .font(.caption)
115115 .fontWeight(.semibold)
116 .foregroundStyle(.secondary)
116 .foregroundStyle(Color(.appTextSecondary))
117117 Spacer()
118118 Text("\(data.totalDomains) tracked")
119119 .font(.caption2)
120 .foregroundStyle(.secondary)
120 .foregroundStyle(Color(.appTextSecondary))
121121 }
122122
123123 HStack(spacing: 12) {
@@ -147,7 +147,7 @@ struct DomainDigWidgetView: View {
147147 .foregroundStyle(color)
148148 Text(label)
149149 .font(.system(size: 9))
150 .foregroundStyle(.secondary)
150 .foregroundStyle(Color(.appTextSecondary))
151151 }
152152 .frame(maxWidth: .infinity, alignment: .leading)
153153 }
@@ -160,7 +160,7 @@ struct DomainDigWidgetView: View {
160160 if domain.isPinned {
161161 Image(systemName: "pin.fill")
162162 .font(.system(size: 8))
163 .foregroundStyle(.secondary)
163 .foregroundStyle(Color(.appTextSecondary))
164164 }
165165 Text(domain.domain)
166166 .font(.caption)
@@ -168,7 +168,7 @@ struct DomainDigWidgetView: View {
168168 Spacer(minLength: 4)
169169 Text(certLabel(for: domain))
170170 .font(.caption2)
171 .foregroundStyle(.secondary)
171 .foregroundStyle(Color(.appTextSecondary))
172172 }
173173 }
174174
DomainDigWidget/SweepLiveActivity.swift +5 −5
@@ -14,7 +14,7 @@ struct SweepLiveActivity: Widget {
1414 DynamicIslandExpandedRegion(.leading) {
1515 Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise")
1616 .font(.caption)
17 .foregroundStyle(.secondary)
17 .foregroundStyle(Color(.appTextSecondary))
1818 }
1919 DynamicIslandExpandedRegion(.trailing) {
2020 Text("\(context.state.completed)/\(context.state.total)")
@@ -28,7 +28,7 @@ struct SweepLiveActivity: Widget {
2828 if let current = context.state.currentDomain {
2929 Text(current)
3030 .font(.caption2)
31 .foregroundStyle(.secondary)
31 .foregroundStyle(Color(.appTextSecondary))
3232 .lineLimit(1)
3333 }
3434 }
@@ -58,7 +58,7 @@ private struct SweepActivityBannerView: View {
5858 Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise")
5959 .font(.caption)
6060 .fontWeight(.semibold)
61 .foregroundStyle(.secondary)
61 .foregroundStyle(Color(.appTextSecondary))
6262 Spacer()
6363 Text("\(context.state.completed) of \(context.state.total)")
6464 .font(.caption)
@@ -72,12 +72,12 @@ private struct SweepActivityBannerView: View {
7272 if let current = context.state.currentDomain {
7373 Text(current)
7474 .font(.caption2)
75 .foregroundStyle(.secondary)
75 .foregroundStyle(Color(.appTextSecondary))
7676 .lineLimit(1)
7777 } else {
7878 Text(context.state.completed >= context.state.total ? "Finished" : "Working…")
7979 .font(.caption2)
80 .foregroundStyle(.secondary)
80 .foregroundStyle(Color(.appTextSecondary))
8181 }
8282 Spacer()
8383 if context.state.changed > 0 {
Shared/Colors.xcassets/AppTextSecondary.colorset/Contents.json added +78
@@ -0,0 +1,78 @@
1{
2 "colors": [
3 {
4 "idiom": "universal",
5 "color": {
6 "color-space": "srgb",
7 "components": {
8 "red": "0x5A",
9 "green": "0x5A",
10 "blue": "0x5F",
11 "alpha": "1.000"
12 }
13 }
14 },
15 {
16 "idiom": "universal",
17 "appearances": [
18 {
19 "appearance": "luminosity",
20 "value": "dark"
21 }
22 ],
23 "color": {
24 "color-space": "srgb",
25 "components": {
26 "red": "0xA1",
27 "green": "0xA1",
28 "blue": "0xA6",
29 "alpha": "1.000"
30 }
31 }
32 },
33 {
34 "idiom": "universal",
35 "appearances": [
36 {
37 "appearance": "contrast",
38 "value": "high"
39 }
40 ],
41 "color": {
42 "color-space": "srgb",
43 "components": {
44 "red": "0x3A",
45 "green": "0x3A",
46 "blue": "0x3C",
47 "alpha": "1.000"
48 }
49 }
50 },
51 {
52 "idiom": "universal",
53 "appearances": [
54 {
55 "appearance": "luminosity",
56 "value": "dark"
57 },
58 {
59 "appearance": "contrast",
60 "value": "high"
61 }
62 ],
63 "color": {
64 "color-space": "srgb",
65 "components": {
66 "red": "0xC7",
67 "green": "0xC7",
68 "blue": "0xCC",
69 "alpha": "1.000"
70 }
71 }
72 }
73 ],
74 "info": {
75 "author": "xcode",
76 "version": 1
77 }
78}