summaryrefslogtreecommitdiff
path: root/DomainDig
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-20 19:14:04 -0500
committerChristian Cleberg <[email protected]>2026-07-20 21:03:10 -0500
commit2c608ed9cb11f3e835f577994b5627b9b7910f4f (patch)
tree078ff50c5151fcc5e91b9d28051562f0bc28088d /DomainDig
parent349bec0e99f330fc1172ca9847c58f019e861f61 (diff)
downloaddomain-dig-2c608ed9cb11f3e835f577994b5627b9b7910f4f.tar.gz
domain-dig-2c608ed9cb11f3e835f577994b5627b9b7910f4f.tar.bz2
domain-dig-2c608ed9cb11f3e835f577994b5627b9b7910f4f.zip
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.
Diffstat (limited to 'DomainDig')
-rw-r--r--DomainDig/AuditModeView.swift4
-rw-r--r--DomainDig/BatchResultsView.swift16
-rw-r--r--DomainDig/BatchSweepSummaryView.swift4
-rw-r--r--DomainDig/ContentView.swift149
-rw-r--r--DomainDig/DashboardView.swift15
-rw-r--r--DomainDig/DomainCompareView.swift2
-rw-r--r--DomainDig/DomainDig/AuditViews.swift36
-rw-r--r--DomainDig/DomainDigApp.swift3
-rw-r--r--DomainDig/DomainDigUI.swift55
-rw-r--r--DomainDig/HistoryView.swift12
-rw-r--r--DomainDig/IntegrationsView.swift22
-rw-r--r--DomainDig/MonitoringView.swift14
-rw-r--r--DomainDig/PaywallView.swift8
-rw-r--r--DomainDig/SavedDomainsView.swift3
-rw-r--r--DomainDig/ScheduledReportsView.swift6
-rw-r--r--DomainDig/TimelineView.swift8
-rw-r--r--DomainDig/WatchlistView.swift26
-rw-r--r--DomainDig/WorkflowsView.swift37
18 files changed, 226 insertions, 194 deletions
diff --git a/DomainDig/AuditModeView.swift b/DomainDig/AuditModeView.swift
index b3e363e..08b9e9c 100644
--- a/DomainDig/AuditModeView.swift
+++ b/DomainDig/AuditModeView.swift
@@ -25,7 +25,7 @@ struct AuditModeView: View {
Section("Audit Timeline") {
if auditStore.sessions.isEmpty {
Text("No audits yet.")
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(auditStore.sessions) { session in
Button {
@@ -35,7 +35,7 @@ struct AuditModeView: View {
Text(session.domain).font(.headline)
Text("\(session.createdAt.formatted(date: .abbreviated, time: .shortened)) • \(session.status.title)")
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
diff --git a/DomainDig/BatchResultsView.swift b/DomainDig/BatchResultsView.swift
index afef5cb..bf211dc 100644
--- a/DomainDig/BatchResultsView.swift
+++ b/DomainDig/BatchResultsView.swift
@@ -17,12 +17,12 @@ struct BatchResultsView: View {
.frame(width: 120)
Text(viewModel.batchProgressLabel)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
} else if !viewModel.batchResults.isEmpty {
Text("\(viewModel.batchResults.count) domains")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
@@ -67,7 +67,7 @@ struct BatchResultRowView: View {
Spacer(minLength: 8)
Text(result.resultSource.label.lowercased())
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
AppStatusBadgeView(model: quickStatusBadge)
}
@@ -80,7 +80,7 @@ struct BatchResultRowView: View {
}
}
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
HStack(spacing: 10) {
Text(result.primaryIP ?? "No IP")
@@ -93,12 +93,12 @@ struct BatchResultRowView: View {
.lineLimit(1)
}
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if let summaryMessage = result.summaryMessage {
Text(summaryMessage)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if let changeClassification = result.changeClassification {
@@ -137,7 +137,7 @@ struct BatchResultRowView: View {
return .init(
title: availabilityText,
systemImage: "exclamationmark.circle",
- foregroundColor: .secondary,
+ foregroundColor: Color(.appTextSecondary),
backgroundColor: Color(.appSurfaceElevated)
)
}
@@ -145,7 +145,7 @@ struct BatchResultRowView: View {
private var quickStatusBadge: AppStatusBadgeModel {
switch result.status {
case .pending:
- return .init(title: "Pending", systemImage: "clock", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: "Pending", systemImage: "clock", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
case .running:
return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16))
case .completed:
diff --git a/DomainDig/BatchSweepSummaryView.swift b/DomainDig/BatchSweepSummaryView.swift
index 43ffa74..ae69d06 100644
--- a/DomainDig/BatchSweepSummaryView.swift
+++ b/DomainDig/BatchSweepSummaryView.swift
@@ -32,7 +32,7 @@ struct BatchSweepSummaryView: View {
if visibleResults.isEmpty {
Text("No domains with changes or warnings")
.font(.system(.caption, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(visibleResults) { result in
if let entry = viewModel.historyEntry(for: result) {
@@ -56,7 +56,7 @@ struct BatchSweepSummaryView: View {
HStack {
Text(label)
.font(.system(.caption, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Spacer()
Text(value)
.font(.system(.callout, design: .monospaced))
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift
index 7eec8a0..efaaf05 100644
--- a/DomainDig/ContentView.swift
+++ b/DomainDig/ContentView.swift
@@ -173,8 +173,6 @@ struct ContentView: View {
)
)
.navigationTitle("DomainDig")
- .toolbarColorScheme(.dark, for: .navigationBar)
- .preferredColorScheme(.dark)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
if viewModel.hasRun {
@@ -182,7 +180,7 @@ struct ContentView: View {
viewModel.reset()
} label: {
Image(systemName: "xmark.circle")
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -327,12 +325,12 @@ struct ContentView: View {
VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
Text("Paste domains separated by new lines or commas.")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if let batchAllowanceSummary = FeatureAccessService.batchAllowanceSummary() {
Text(batchAllowanceSummary)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
TextField(
@@ -544,7 +542,7 @@ struct ContentView: View {
} label: {
Image(systemName: "bolt.circle")
.font(appDensity.font(.body, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Button {
viewModel.toggleSavedDomain()
@@ -583,7 +581,7 @@ struct ContentView: View {
} label: {
Image(systemName: "square.and.arrow.up")
.font(appDensity.font(.body, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -652,13 +650,13 @@ struct ContentView: View {
HStack {
Text("RECENT")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Spacer()
Button("Clear") {
viewModel.clearRecentSearches()
}
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
ForEach(viewModel.recentSearches, id: \.self) { domain in
@@ -694,7 +692,7 @@ struct ContentView: View {
.font(appDensity.font(.headline, design: .default, weight: .semibold))
Text(message)
.font(appDensity.font(.callout, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(appDensity.metrics.cardPadding)
@@ -784,7 +782,7 @@ struct SummaryView: View {
VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing) {
Text(field.label)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(field.value)
.font(appDensity.font(.caption))
.foregroundStyle(ResultColors.color(for: field.tone))
@@ -826,13 +824,13 @@ struct RiskSummaryCardView: View {
Spacer()
Text("Deterministic")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if topFactors.isEmpty {
Text("No major risk factors identified")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(Array(topFactors.enumerated()), id: \.offset) { _, factor in
HStack(alignment: .top, spacing: 8) {
@@ -884,7 +882,7 @@ struct InsightsSummaryCardView: View {
if insights.isEmpty {
Text("No deterministic insights triggered")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(Array(insights.enumerated()), id: \.offset) { _, insight in
HStack(alignment: .top, spacing: 8) {
@@ -940,7 +938,7 @@ struct StickyLookupSummaryView: View {
HStack(spacing: 8) {
Label(primaryIP, systemImage: "network")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Spacer(minLength: 6)
AppCopyButton(value: primaryIP, label: "Copy IP")
}
@@ -966,7 +964,7 @@ struct LookupProgressOverviewView: View {
.foregroundStyle(.primary)
Text(steps.isEmpty ? "Preparing requests" : steps.joined(separator: " • "))
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Spacer()
}
@@ -1049,13 +1047,13 @@ struct DomainChangeSummaryView: View {
.clipShape(Capsule())
Text(summary.generatedAt, style: .time)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
VStack(alignment: .leading, spacing: 4) {
Text("Inference")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(summary.message)
.font(appDensity.font(.caption))
.foregroundStyle(.primary)
@@ -1069,7 +1067,7 @@ struct DomainChangeSummaryView: View {
VStack(alignment: .leading, spacing: 4) {
Text("Observed")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
ForEach(Array(summary.observedFacts.enumerated()), id: \.offset) { _, fact in
Text(fact)
.font(appDensity.font(.caption))
@@ -1171,7 +1169,7 @@ struct DomainDiffView: View {
HStack {
Text(item.label)
.font(.system(.caption, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Spacer()
Text("\(item.changeType.marker) \(item.severity.title) • \(changeLabel(for: item.changeType))")
.font(.system(.caption2, design: .monospaced))
@@ -1186,10 +1184,10 @@ struct DomainDiffView: View {
VStack(alignment: .leading, spacing: 2) {
Text("Old")
.font(.system(.caption2, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(oldValue)
.font(.system(.caption2, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.textSelection(.enabled)
}
}
@@ -1198,7 +1196,7 @@ struct DomainDiffView: View {
VStack(alignment: .leading, spacing: 2) {
Text("New")
.font(.system(.caption2, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(newValue)
.font(.system(.caption, design: .monospaced))
.foregroundStyle(item.hasChanges ? .primary : .secondary)
@@ -1306,7 +1304,7 @@ struct TrackedDomainDetailHeaderView: View {
Text("Last refresh \(trackedDomain.updatedAt.formatted(date: .abbreviated, time: .shortened))")
}
.font(.system(.caption2, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -1394,7 +1392,7 @@ struct DomainSectionView: View {
VStack(alignment: .leading, spacing: 8) {
Text("Part of workflow")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
ForEach(workflows) { workflow in
HStack {
@@ -1429,7 +1427,7 @@ struct DomainSectionView: View {
if showSuggestions {
Text("Suggestions")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.padding(.top, 4)
if suggestionsLoading {
ProgressView("Checking alternatives…")
@@ -1527,7 +1525,7 @@ struct OwnershipSectionView: View {
HStack {
Text("History")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Spacer()
if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder {
Button("Load") {
@@ -1545,12 +1543,12 @@ struct OwnershipSectionView: View {
VStack(alignment: .leading, spacing: 3) {
Text(event.date.formatted(date: .abbreviated, time: .omitted))
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(event.summary)
.font(appDensity.font(.caption))
Text(event.source)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
} else if let historyError {
@@ -1604,7 +1602,7 @@ struct IntelligenceSectionView: View {
.font(appDensity.font(.caption, weight: .semibold))
Text(signal.detail)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -1645,7 +1643,7 @@ struct IntelligenceSectionView: View {
}
Text("First \(item.firstSeen.formatted(date: .abbreviated, time: .omitted)) • Last \(item.lastSeen.formatted(date: .abbreviated, time: .omitted)) • Seen \(item.recurrenceCount)x")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -1678,7 +1676,7 @@ struct IntelligenceSectionView: View {
VStack(alignment: .leading, spacing: 3) {
Text(date.formatted(date: .abbreviated, time: .omitted))
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(title)
.font(appDensity.font(.caption))
}
@@ -1755,7 +1753,7 @@ struct SubdomainsSectionView: View {
if !groups.isEmpty {
Text("Groups")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
ForEach(groups) { group in
HStack {
Text("\(group.label).*")
@@ -1764,7 +1762,7 @@ struct SubdomainsSectionView: View {
Spacer()
Text("\(group.subdomains.count)")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -1898,7 +1896,7 @@ struct DNSSectionView: View {
if let wildcardTitle = section.wildcardTitle {
Text(wildcardTitle)
.font(.system(.caption, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.padding(.top, 4)
ForEach(section.wildcardRows) { row in
LabeledValueRow(row: row)
@@ -1940,18 +1938,18 @@ struct DNSSectionView: View {
VStack(alignment: .leading, spacing: 3) {
Text(event.date.formatted(date: .abbreviated, time: .omitted))
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(event.summary)
.font(appDensity.font(.caption))
if !event.aRecords.isEmpty {
Text("A: \(event.aRecords.joined(separator: ", "))")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if !event.nameservers.isEmpty {
Text("NS: \(event.nameservers.joined(separator: ", "))")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -2017,7 +2015,7 @@ struct WebSectionView: View {
if let sslInfo, !sslInfo.subjectAltNames.isEmpty {
Text("SANs")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
ForEach(sslInfo.subjectAltNames, id: \.self) { san in
HStack(alignment: .top, spacing: 8) {
Text(san)
@@ -2091,7 +2089,7 @@ struct WebSectionView: View {
HStack(alignment: .top, spacing: 6) {
Text(redirect.stepLabel)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.frame(width: 16, alignment: .trailing)
Text(redirect.statusCode)
.font(appDensity.font(.caption))
@@ -2104,7 +2102,7 @@ struct WebSectionView: View {
if redirect.isFinal {
Text("(final)")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -2149,7 +2147,7 @@ struct EmailSectionView: View {
if !assessment.reasons.isEmpty {
Text(assessment.reasons.joined(separator: " | "))
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
if loading {
@@ -2176,7 +2174,7 @@ struct EmailSectionView: View {
if let auxiliaryDetail = row.auxiliaryDetail {
Text(auxiliaryDetail)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -2192,9 +2190,9 @@ struct EmailSectionView: View {
case .warning:
return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
case .failure:
- return .init(title: row.status, systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: row.status, systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
case .primary, .secondary:
- return .init(title: row.status, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: row.status, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
}
}
}
@@ -2244,7 +2242,7 @@ struct NetworkSectionView: View {
Spacer()
Text(row.latencyLabel)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
AppStatusBadgeView(model: reachabilityBadge(row))
}
}
@@ -2304,7 +2302,7 @@ struct NetworkSectionView: View {
} else {
Text("Standard Ports")
.font(.system(.caption, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
PortRowsView(rows: standardPortRows)
}
@@ -2353,7 +2351,7 @@ struct NetworkSectionView: View {
case .failure:
return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
case .primary, .secondary:
- return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
}
}
}
@@ -2379,14 +2377,14 @@ struct PortRowsView: View {
if let durationLabel = row.durationLabel {
Text(durationLabel)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
AppStatusBadgeView(model: portBadge(row))
}
if let banner = row.banner {
Text(banner)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.padding(.leading, 8)
}
}
@@ -2404,7 +2402,7 @@ struct PortRowsView: View {
case .failure:
return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16))
case .primary, .secondary:
- return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
}
}
}
@@ -2512,15 +2510,15 @@ struct SectionTrustMetadataView: View {
if let confidence {
Text("Confidence \(confidence.title)")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if let provenance {
Text(provenance.provider ?? provenance.source)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(provenance.resultSource.label)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
DisclosureGroup("Details") {
@@ -2559,7 +2557,7 @@ struct LabeledValueRow: View {
VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing - 1) {
Text(row.label)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(row.value)
.font(appDensity.font(.caption))
.foregroundStyle(ResultColors.color(for: row.tone))
@@ -2656,7 +2654,7 @@ struct SettingsView: View {
if let statusMessage = purchaseService.statusMessage {
Text(statusMessage)
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if let errorMessage = purchaseService.errorMessage {
@@ -2732,10 +2730,17 @@ struct SettingsView: View {
private struct DisplaySettingsView: View {
@AppStorage(AppDensity.userDefaultsKey) private var storedDensity = AppDensity.compact.rawValue
+ @AppStorage(AppAppearance.userDefaultsKey) private var storedAppearance = AppAppearance.system.rawValue
var body: some View {
Form {
Section("Display") {
+ Picker("Appearance", selection: $storedAppearance) {
+ ForEach(AppAppearance.allCases) { appearance in
+ Text(appearance.title).tag(appearance.rawValue)
+ }
+ }
+
Picker("Density", selection: $storedDensity) {
ForEach(AppDensity.allCases) { density in
Text(density.title).tag(density.rawValue)
@@ -2778,7 +2783,7 @@ private struct HistoryNetworkSettingsView: View {
Text("History remains local-first. Auto-prune only trims older local snapshots on this device and defaults to unlimited.")
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Section("Network") {
@@ -2870,11 +2875,11 @@ private struct CloudSyncSettingsView: View {
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.")
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(cloudSyncService.detailMessage)
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if let lastErrorMessage = cloudSyncService.lastErrorMessage {
Text(lastErrorMessage)
@@ -2932,7 +2937,7 @@ private struct LocalAPISettingsView: View {
if let statusMessage = localAPIService.statusMessage {
Text(statusMessage)
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
@@ -2947,7 +2952,7 @@ private struct LocalAPISettingsView: View {
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.")
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Section("Request Logging") {
@@ -2962,7 +2967,7 @@ private struct LocalAPISettingsView: View {
if localAPIService.requestLogs.isEmpty {
Text("No local API requests logged yet.")
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(localAPIService.requestLogs.prefix(25)) { log in
VStack(alignment: .leading, spacing: 4) {
@@ -2977,11 +2982,11 @@ private struct LocalAPISettingsView: View {
Text(log.timestamp.formatted(date: .abbreviated, time: .standard))
.font(appDensity.font(.caption2, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text("\(Int(log.duration * 1000)) ms")
.font(appDensity.font(.caption2, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -3184,13 +3189,13 @@ private struct MonitoringSettingsView: View {
if let monitoringStatusMessage = viewModel.monitoringStatusMessage {
Text(monitoringStatusMessage)
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if !FeatureAccessService.hasAccess(to: .automatedMonitoring) {
Text("Background monitoring and alerts are available in Pro.")
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -3260,7 +3265,7 @@ private struct DataPortabilitySettingsView: View {
Text(importMode.explanation)
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Button("Export Full Backup") {
exportFullBackup()
@@ -3317,12 +3322,12 @@ private struct DataPortabilitySettingsView: View {
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.")
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if let portabilityStatusMessage = viewModel.portabilityStatusMessage {
Text(portabilityStatusMessage)
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
@@ -3331,7 +3336,7 @@ private struct DataPortabilitySettingsView: View {
Section("Import Debug") {
Text(importDebugStatus)
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.textSelection(.enabled)
}
}
@@ -3636,7 +3641,7 @@ private struct DataManagementSettingsView: View {
if let deleteAllSuccessMessage {
Text(deleteAllSuccessMessage)
.font(.footnote.weight(.medium))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.padding(.horizontal, 14)
.padding(.vertical, 10)
.background(.thinMaterial, in: Capsule())
@@ -3722,7 +3727,7 @@ private struct DataImportPreviewSheet: View {
Section("Warnings") {
ForEach(preview.warnings, id: \.self) { warning in
Text(warning)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
diff --git a/DomainDig/DashboardView.swift b/DomainDig/DashboardView.swift
index da8d90a..8d41ccb 100644
--- a/DomainDig/DashboardView.swift
+++ b/DomainDig/DashboardView.swift
@@ -139,7 +139,7 @@ struct DashboardView: View {
.font(appDensity.font(.headline, design: .default, weight: .semibold))
Text("\(group.domains.count) domain\(group.domains.count == 1 ? "" : "s")")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Spacer()
groupBadge(for: group.domains)
@@ -170,7 +170,6 @@ struct DashboardView: View {
}
}
}
- .preferredColorScheme(.dark)
}
private func disclosureBinding(for apexDomain: String) -> Binding<Bool> {
@@ -193,7 +192,7 @@ struct DashboardView: View {
VStack(alignment: .leading, spacing: 8) {
Text(title)
.font(appDensity.font(.caption, design: .default, weight: .semibold))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text("\(value)")
.font(.system(size: 28, weight: .bold, design: .rounded))
.foregroundStyle(.primary)
@@ -230,7 +229,7 @@ struct DashboardView: View {
private func dashboardEmptyRow(_ message: String) -> some View {
Text(message)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.padding(.vertical, 4)
}
@@ -288,7 +287,7 @@ private struct PortfolioActivityRow: View {
Text(relativeTimestamp(item.timestamp))
}
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
.padding(.vertical, 4)
@@ -320,10 +319,10 @@ private struct PortfolioAttentionRow: View {
.foregroundStyle(.primary)
Text(item.reason)
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Text(relativeTimestamp(item.timestamp))
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
.padding(.vertical, 4)
@@ -353,7 +352,7 @@ private struct PortfolioExpiryRow: View {
.foregroundStyle(.primary)
Text(expirySubtitle)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Spacer()
AppStatusBadgeView(model: badgeModel)
diff --git a/DomainDig/DomainCompareView.swift b/DomainDig/DomainCompareView.swift
index 90ee5b1..8219fa7 100644
--- a/DomainDig/DomainCompareView.swift
+++ b/DomainDig/DomainCompareView.swift
@@ -38,7 +38,7 @@ struct DomainCompareView: View {
Section {
Text("Choose two different domains to compare.")
.font(.callout)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
diff --git a/DomainDig/DomainDig/AuditViews.swift b/DomainDig/DomainDig/AuditViews.swift
index df5a9a9..50ea137 100644
--- a/DomainDig/DomainDig/AuditViews.swift
+++ b/DomainDig/DomainDig/AuditViews.swift
@@ -41,13 +41,13 @@ struct AuditListView: View {
Spacer()
Text("\(group.sessions.count) audit\(group.sessions.count == 1 ? "" : "s")")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if let latest = group.sessions.first {
Text(latest.findings.isEmpty ? "No findings recorded yet" : latest.findings.map(\.title).prefix(2).joined(separator: " • "))
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.lineLimit(2)
HStack(spacing: 8) {
@@ -57,7 +57,7 @@ struct AuditListView: View {
}
Text("Checklist \(latest.completedChecklistCount)/\(latest.checklist.count)")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -85,7 +85,6 @@ struct AuditListView: View {
}
}
}
- .preferredColorScheme(.dark)
}
}
@@ -116,7 +115,7 @@ struct AuditDomainTimelineView: View {
Text("Reviewer: \(session.reviewer)")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
HStack(spacing: 8) {
Text("\(session.findings.count) findings")
@@ -126,7 +125,7 @@ struct AuditDomainTimelineView: View {
}
}
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
.padding(.vertical, 4)
}
@@ -143,11 +142,11 @@ struct AuditDomainTimelineView: View {
Spacer()
Text("\(point.findingCount) findings")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Text("Open high severity: \(point.openHighSeverityCount) • Repeated issues: \(point.repeatedIssueCount)")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
.padding(.vertical, 2)
}
@@ -197,7 +196,7 @@ struct AuditSessionDetailView: View {
.font(appDensity.font(.headline, design: .default, weight: .semibold))
Text("Reviewer: \(session.reviewer)")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Spacer()
auditStatusBadge(session.status)
@@ -215,7 +214,7 @@ struct AuditSessionDetailView: View {
Text("Captured \(session.evidence.capturedAt.formatted(date: .abbreviated, time: .shortened))")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
.listRowBackground(Color(.appSurface))
@@ -256,7 +255,7 @@ struct AuditSessionDetailView: View {
.foregroundStyle(.primary)
Text(item.detail)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Spacer()
}
@@ -270,7 +269,7 @@ struct AuditSessionDetailView: View {
if session.findings.isEmpty {
Text("No findings recorded.")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(session.findings) { finding in
Button {
@@ -286,11 +285,11 @@ struct AuditSessionDetailView: View {
}
Text(finding.summary)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.lineLimit(2)
Text("\(finding.status.title) • \(finding.evidenceReferences.count) evidence refs")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
.padding(.vertical, 2)
}
@@ -321,7 +320,7 @@ struct AuditSessionDetailView: View {
.font(appDensity.font(.caption, weight: .semibold))
Text(entry.changeSummaryMessage ?? "No change summary")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -337,11 +336,11 @@ struct AuditSessionDetailView: View {
Spacer()
Text(point.status.title)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Text("Findings \(point.findingCount) • Open high \(point.openHighSeverityCount) • Repeated \(point.repeatedIssueCount)")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -401,7 +400,6 @@ struct AuditSessionDetailView: View {
.background(Color(.appBackground))
}
}
- .preferredColorScheme(.dark)
}
}
@@ -459,7 +457,7 @@ private struct AuditFindingEditorView: View {
ForEach(session.evidence.screenshots) { asset in
Text("\(asset.title): \(asset.reference)")
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
diff --git a/DomainDig/DomainDigApp.swift b/DomainDig/DomainDigApp.swift
index 2f2070c..d392dde 100644
--- a/DomainDig/DomainDigApp.swift
+++ b/DomainDig/DomainDigApp.swift
@@ -12,6 +12,7 @@ struct DomainDigApp: App {
@UIApplicationDelegateAdaptor(DomainDigAppDelegate.self) private var appDelegate
@Environment(\.scenePhase) private var scenePhase
@AppStorage(AppDensity.userDefaultsKey) private var density = AppDensity.compact.rawValue
+ @AppStorage(AppAppearance.userDefaultsKey) private var appearance = AppAppearance.system.rawValue
@State private var viewModel = DomainViewModel()
@State private var purchaseService = PurchaseService.shared
@State private var cloudSyncService = CloudSyncService.shared
@@ -27,6 +28,8 @@ struct DomainDigApp: App {
WindowGroup {
RootTabView(viewModel: viewModel)
.environment(\.appDensity, AppDensity(rawValue: density) ?? .compact)
+ // The single place appearance is applied. Keep it that way.
+ .preferredColorScheme((AppAppearance(rawValue: appearance) ?? .system).colorScheme)
.task {
let _ = purchaseService.currentTier
let _ = cloudSyncService.status
diff --git a/DomainDig/DomainDigUI.swift b/DomainDig/DomainDigUI.swift
index 9c63e73..7d99e4e 100644
--- a/DomainDig/DomainDigUI.swift
+++ b/DomainDig/DomainDigUI.swift
@@ -6,6 +6,45 @@ import UIKit
import AppKit
#endif
+/// User-selected appearance, applied once at the `WindowGroup`.
+///
+/// Deliberately applied in exactly one place. The app previously carried 16
+/// separate `.preferredColorScheme(.dark)` calls scattered across view bodies,
+/// which is how it became impossible to reach light mode at all — re-applying
+/// per view is what let the lock spread unnoticed.
+enum AppAppearance: String, CaseIterable, Identifiable {
+ case system
+ case light
+ case dark
+
+ static let userDefaultsKey = "appAppearance"
+
+ var id: String { rawValue }
+
+ var title: String {
+ switch self {
+ case .system:
+ return "System"
+ case .light:
+ return "Light"
+ case .dark:
+ return "Dark"
+ }
+ }
+
+ /// `nil` hands control back to the system setting.
+ var colorScheme: ColorScheme? {
+ switch self {
+ case .system:
+ return nil
+ case .light:
+ return .light
+ case .dark:
+ return .dark
+ }
+ }
+}
+
enum AppDensity: String, CaseIterable, Identifiable {
case compact
case comfortable
@@ -96,7 +135,7 @@ enum AppStatusFactory {
case .registered:
return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
case .unknown, .none:
- return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
}
}
@@ -112,7 +151,7 @@ enum AppStatusFactory {
static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel {
guard error == nil, let result else {
- return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
}
let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count
@@ -122,18 +161,18 @@ enum AppStatusFactory {
case 1, 2:
return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16))
default:
- return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
}
}
static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel {
guard let summary else {
- return .init(title: "Unchanged", systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: "Unchanged", systemImage: "circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
}
if summary.hasChanges {
return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16))
}
- return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated))
+ return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated))
}
}
@@ -259,7 +298,7 @@ struct EmptyStateCardView: View {
Text(message)
.font(appDensity.font(.body))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.fixedSize(horizontal: false, vertical: true)
Text(suggestion)
@@ -311,14 +350,14 @@ struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View {
if let subtitle {
Text(subtitle)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
Spacer(minLength: 8)
trailing()
Image(systemName: isCollapsed ? "chevron.down" : "chevron.up")
.font(.caption.weight(.semibold))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
.contentShape(Rectangle())
.frame(minHeight: appDensity.metrics.controlMinHeight, alignment: .center)
diff --git a/DomainDig/HistoryView.swift b/DomainDig/HistoryView.swift
index ffa7745..5b788c3 100644
--- a/DomainDig/HistoryView.swift
+++ b/DomainDig/HistoryView.swift
@@ -40,12 +40,12 @@ struct HistoryView: View {
Spacer()
Text("\(item.count) snapshots")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Text(item.latest.changeSummaryMessage ?? "No change summary")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.lineLimit(2)
HStack(spacing: 8) {
@@ -129,7 +129,6 @@ struct HistoryView: View {
.onChange(of: viewModel.rerunNavigationToken) { _, _ in
dismiss()
}
- .preferredColorScheme(.dark)
}
private func deleteFilteredHistoryEntries(at offsets: IndexSet) {
@@ -372,7 +371,6 @@ struct HistoryDetailView: View {
}
}
}
- .preferredColorScheme(.dark)
}
private var snapshotBanner: some View {
@@ -385,7 +383,7 @@ struct HistoryDetailView: View {
Spacer()
Text("Live re-run available")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if let mismatchNote = viewModel.resolverMismatchNote(for: entry) {
Text(mismatchNote)
@@ -400,10 +398,10 @@ struct HistoryDetailView: View {
if let note = entry.note, !note.isEmpty {
Text(note)
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.padding(8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(.appSurface))
diff --git a/DomainDig/IntegrationsView.swift b/DomainDig/IntegrationsView.swift
index 163d4d1..bb9abae 100644
--- a/DomainDig/IntegrationsView.swift
+++ b/DomainDig/IntegrationsView.swift
@@ -15,7 +15,7 @@ struct IntegrationsSettingsView: View {
if let statusMessage = integrationService.statusMessage {
Text(statusMessage)
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Button("Process Queue Now") {
@@ -26,7 +26,7 @@ struct IntegrationsSettingsView: View {
Section("Targets") {
if integrationService.targets.isEmpty {
Text("No integrations configured.")
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(integrationService.targets) { target in
NavigationLink {
@@ -42,12 +42,12 @@ struct IntegrationsSettingsView: View {
Text(target.name)
Spacer()
Text(target.type.title)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Text(summary(for: target))
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if !target.isEnabled {
Text("Disabled")
@@ -138,7 +138,7 @@ private struct IntegrationDetailView: View {
Section("Delivery Log") {
if integrationService.deliveryRecords(for: target.id).isEmpty {
Text("No deliveries yet.")
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(integrationService.deliveryRecords(for: target.id), id: \.id) { record in
VStack(alignment: .leading, spacing: 4) {
@@ -147,7 +147,7 @@ private struct IntegrationDetailView: View {
Spacer()
Text(record.timestamp.formatted(date: .abbreviated, time: .shortened))
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Text(record.summary)
@@ -155,7 +155,7 @@ private struct IntegrationDetailView: View {
Text(record.destination)
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if let failureReason = record.failureReason {
let failureColor: Color = record.status == .skipped ? .secondary : Color(.statusCritical)
@@ -169,7 +169,7 @@ private struct IntegrationDetailView: View {
}
} else {
Text("Integration not found.")
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
.navigationTitle(target?.name ?? "Integration")
@@ -265,7 +265,7 @@ private struct IntegrationEditorView: View {
if existingTarget != nil {
Text("Saved webhook URL remains in Keychain unless you replace it.")
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
case .slack:
@@ -278,7 +278,7 @@ private struct IntegrationEditorView: View {
if existingTarget != nil {
Text("Saved Slack webhook remains in Keychain unless you replace it.")
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
case .email:
@@ -315,7 +315,7 @@ private struct IntegrationEditorView: View {
if existingTarget != nil {
Text("Saved SMTP password remains in Keychain unless you replace it.")
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
diff --git a/DomainDig/MonitoringView.swift b/DomainDig/MonitoringView.swift
index decad35..db6f685 100644
--- a/DomainDig/MonitoringView.swift
+++ b/DomainDig/MonitoringView.swift
@@ -31,7 +31,7 @@ struct MonitoringView: View {
!monitoringStatusMessage.isEmpty {
Text(monitoringStatusMessage)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Button(viewModel.monitoringRunInProgress ? "Monitoring…" : "Run Now") {
@@ -68,12 +68,12 @@ struct MonitoringView: View {
Spacer()
Text(log.timestamp.formatted(date: .abbreviated, time: .shortened))
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Text(log.summary)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
HStack(spacing: 8) {
metricBadge(title: "\(log.domainsChecked) checked")
@@ -95,7 +95,6 @@ struct MonitoringView: View {
.scrollContentBackground(.hidden)
.background(Color(.appBackground))
.navigationTitle("Monitoring")
- .preferredColorScheme(.dark)
.onAppear {
viewModel.refreshMonitoringState()
}
@@ -152,7 +151,7 @@ struct MonitoringLogDetailView: View {
Text(result.summaryMessage)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
HStack(spacing: 10) {
Text(result.resultSource.label)
@@ -162,7 +161,7 @@ struct MonitoringLogDetailView: View {
}
}
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if let errorMessage = result.errorMessage {
Text(errorMessage)
@@ -188,7 +187,7 @@ struct MonitoringLogDetailView: View {
ForEach(log.errors, id: \.self) { error in
Text(error)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
.listRowBackground(Color(.appSurface))
@@ -197,7 +196,6 @@ struct MonitoringLogDetailView: View {
.scrollContentBackground(.hidden)
.background(Color(.appBackground))
.navigationTitle("Run Details")
- .preferredColorScheme(.dark)
}
private func color(for severity: MonitoringAlertSeverity) -> Color {
diff --git a/DomainDig/PaywallView.swift b/DomainDig/PaywallView.swift
index 27e80ee..2b66037 100644
--- a/DomainDig/PaywallView.swift
+++ b/DomainDig/PaywallView.swift
@@ -12,7 +12,7 @@ struct PaywallView: View {
Section {
Text("Pro unlocks workflows, scale, monitoring automation, and exports. Pro+ adds deeper external intelligence with no additional usage limits.")
.font(appDensity.font(.body, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.fixedSize(horizontal: false, vertical: true)
}
@@ -38,7 +38,7 @@ struct PaywallView: View {
} else if purchaseService.products.isEmpty {
Text("Pricing is unavailable right now.")
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Button("Retry") {
Task {
@@ -58,7 +58,7 @@ struct PaywallView: View {
.foregroundStyle(.primary)
Text(product.displayPrice)
.font(appDensity.font(.callout, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
.frame(maxWidth: .infinity, alignment: .leading)
}
@@ -69,7 +69,7 @@ struct PaywallView: View {
if let statusMessage = purchaseService.statusMessage {
Text(statusMessage)
.font(appDensity.font(.caption, design: .default))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if let errorMessage = purchaseService.errorMessage {
diff --git a/DomainDig/SavedDomainsView.swift b/DomainDig/SavedDomainsView.swift
index 59dcb73..125d8a0 100644
--- a/DomainDig/SavedDomainsView.swift
+++ b/DomainDig/SavedDomainsView.swift
@@ -9,7 +9,7 @@ struct SavedDomainsView: View {
if viewModel.savedDomains.isEmpty {
Text("No saved domains")
.font(.system(.callout, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.listRowBackground(Color(.appSurface))
} else {
ForEach(viewModel.savedDomains, id: \.self) { domain in
@@ -37,6 +37,5 @@ struct SavedDomainsView: View {
EditButton()
}
}
- .preferredColorScheme(.dark)
}
}
diff --git a/DomainDig/ScheduledReportsView.swift b/DomainDig/ScheduledReportsView.swift
index dffafe3..5c0a14a 100644
--- a/DomainDig/ScheduledReportsView.swift
+++ b/DomainDig/ScheduledReportsView.swift
@@ -13,7 +13,7 @@ struct ScheduledReportsView: View {
if !FeatureAccessService.hasAccess(to: .automatedMonitoring) {
Text("Scheduled reports require Pro.")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Toggle("Scheduled Reports", isOn: Binding(
@@ -59,7 +59,7 @@ struct ScheduledReportsView: View {
if let statusMessage {
Text(statusMessage)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Button(isGenerating ? "Generating…" : "Generate Now") {
@@ -91,7 +91,7 @@ struct ScheduledReportsView: View {
.foregroundStyle(.primary)
Text(log.generatedAt.formatted(date: .abbreviated, time: .shortened))
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
diff --git a/DomainDig/TimelineView.swift b/DomainDig/TimelineView.swift
index 052b088..16e2bf8 100644
--- a/DomainDig/TimelineView.swift
+++ b/DomainDig/TimelineView.swift
@@ -125,7 +125,7 @@ private struct TimelineRow: View {
Text(summary.changeSummaryMessage ?? "No change summary")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.lineLimit(2)
HStack(spacing: 8) {
@@ -137,14 +137,14 @@ private struct TimelineRow: View {
}
}
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if !entry.intelligenceTimeline.isEmpty {
VStack(alignment: .leading, spacing: 4) {
ForEach(Array(entry.intelligenceTimeline.prefix(2))) { event in
Text("\(event.title): \(event.detail)")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.lineLimit(1)
}
}
@@ -161,7 +161,7 @@ private struct TimelineRow: View {
.lineLimit(1)
}
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
diff --git a/DomainDig/WatchlistView.swift b/DomainDig/WatchlistView.swift
index 5c5609e..ddb6f4a 100644
--- a/DomainDig/WatchlistView.swift
+++ b/DomainDig/WatchlistView.swift
@@ -42,7 +42,7 @@ struct WatchlistView: View {
HStack {
Text(viewModel.batchProgressLabel)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Spacer()
if viewModel.batchLookupRunning {
Button("Cancel") {
@@ -78,7 +78,7 @@ struct WatchlistView: View {
Section {
Text(limitMessage)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
.listRowBackground(Color(.appSurface))
}
@@ -208,7 +208,7 @@ struct WatchlistView: View {
Section {
Text("Adds the domain directly to your watchlist so monitoring can run without a prior inspection.")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if let addDomainError {
@@ -267,7 +267,7 @@ struct WatchlistView: View {
.foregroundStyle(.primary)
Text([view.tag, view.filter.title, view.sort.title].compactMap { $0 }.joined(separator: " • "))
.font(.caption)
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -288,7 +288,6 @@ struct WatchlistView: View {
}
}
}
- .preferredColorScheme(.dark)
}
@ViewBuilder
@@ -450,12 +449,12 @@ struct WatchlistRowView: View {
Text("Updated \(trackedDomain.updatedAt.formatted(date: .abbreviated, time: .shortened))")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if let collaboration = trackedDomain.collaboration, collaboration.isShared {
Text("\(collaboration.ownership.title) • \(collaboration.permission.title)")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
HStack(spacing: 8) {
@@ -468,19 +467,19 @@ struct WatchlistRowView: View {
}
}
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
indicatorRow
if let note = trackedDomain.note?.trimmingCharacters(in: .whitespacesAndNewlines), !note.isEmpty {
Text(note)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.lineLimit(2)
} else if let summary = trackedDomain.lastChangeSummary {
Text(summary.message)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.lineLimit(2)
}
}
@@ -502,7 +501,7 @@ struct WatchlistRowView: View {
@ViewBuilder
private var statusBadge: some View {
if isRefreshing {
- AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: .secondary, backgroundColor: Color(.appSurfaceElevated)))
+ AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: Color(.appTextSecondary), backgroundColor: Color(.appSurfaceElevated)))
} else {
AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability))
}
@@ -700,7 +699,7 @@ struct TrackedDomainDetailView: View {
if latestSnapshots.isEmpty {
Text("No snapshots yet")
.font(.system(.caption, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(latestSnapshots) { entry in
NavigationLink {
@@ -712,7 +711,7 @@ struct TrackedDomainDetailView: View {
.foregroundStyle(.primary)
Text(entry.changeSummary?.hasChanges == true ? "Changed" : "Snapshot")
.font(.system(.caption2, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -723,7 +722,6 @@ struct TrackedDomainDetailView: View {
.scrollContentBackground(.hidden)
.background(Color(.appBackground))
.navigationTitle(liveTrackedDomain.domain)
- .preferredColorScheme(.dark)
.onChange(of: viewModel.rerunNavigationToken) { _, _ in
dismiss()
}
diff --git a/DomainDig/WorkflowsView.swift b/DomainDig/WorkflowsView.swift
index 2195af5..d628ba4 100644
--- a/DomainDig/WorkflowsView.swift
+++ b/DomainDig/WorkflowsView.swift
@@ -22,7 +22,7 @@ struct WorkflowsView: View {
Section {
Text(limitMessage)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
.listRowBackground(Color(.appSurface))
}
@@ -54,7 +54,6 @@ struct WorkflowsView: View {
.sheet(item: workflowSummaryBinding) { summary in
WorkflowRunSummaryView(viewModel: viewModel, summary: summary)
}
- .preferredColorScheme(.dark)
}
private var workflowSummaryBinding: Binding<WorkflowRunSummary?> {
@@ -78,7 +77,7 @@ struct WorkflowsView: View {
HStack {
Text(viewModel.batchProgressLabel)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Spacer()
if viewModel.batchLookupRunning {
Button("Cancel") {
@@ -142,23 +141,23 @@ private struct WorkflowRowView: View {
Spacer(minLength: 8)
Text("\(workflow.domains.count) domains")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Text("Updated \(workflow.updatedAt.formatted(date: .abbreviated, time: .shortened))")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
if let collaboration = workflow.collaboration, collaboration.isShared {
Text("\(collaboration.ownership.title) • \(collaboration.permission.title)")
.font(appDensity.font(.caption2))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if let notes = workflow.notes, !notes.isEmpty {
Text(notes)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
.lineLimit(2)
}
}
@@ -197,12 +196,12 @@ struct WorkflowDetailView: View {
if let notes = workflow.notes, !notes.isEmpty {
Text(notes)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
if let collaboration = workflow.collaboration, collaboration.isShared {
Text("\(collaboration.ownership.title) • \(collaboration.permission.title)")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
@@ -277,7 +276,7 @@ struct WorkflowDetailView: View {
if !latestSummary.workflowInsights.isEmpty {
Text(latestSummary.workflowInsights[0].description)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
Button {
@@ -306,7 +305,7 @@ struct WorkflowDetailView: View {
if workflow.domains.isEmpty {
Text("No domains in this workflow")
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(workflow.domains, id: \.self) { domain in
Button {
@@ -318,7 +317,7 @@ struct WorkflowDetailView: View {
.foregroundStyle(.primary)
Spacer()
Image(systemName: "arrow.up.right.circle")
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
.buttonStyle(.plain)
@@ -355,17 +354,16 @@ struct WorkflowDetailView: View {
} else {
Text("Workflow not found")
.font(appDensity.font(.callout))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
- .preferredColorScheme(.dark)
}
private func statRow(label: String, value: String) -> some View {
HStack {
Text(label)
.font(appDensity.font(.caption))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Spacer()
Text(value)
.font(appDensity.font(.callout))
@@ -449,7 +447,6 @@ struct WorkflowComposerView: View {
}
}
}
- .preferredColorScheme(.dark)
}
private var parsedDomains: [String] {
@@ -507,7 +504,7 @@ struct WorkflowRunSummaryView: View {
.foregroundStyle(.primary)
Text(insight.domainsInvolved.joined(separator: ", "))
.font(.system(.caption, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
}
}
}
@@ -517,7 +514,7 @@ struct WorkflowRunSummaryView: View {
if visibleResults.isEmpty {
Text("No domains with meaningful changes or warnings")
.font(.system(.caption, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
} else {
ForEach(visibleResults) { result in
if let entry = viewModel.historyEntry(for: result) {
@@ -578,14 +575,13 @@ struct WorkflowRunSummaryView: View {
}
}
}
- .preferredColorScheme(.dark)
}
private func statRow(label: String, value: String) -> some View {
HStack {
Text(label)
.font(.system(.caption, design: .monospaced))
- .foregroundStyle(.secondary)
+ .foregroundStyle(Color(.appTextSecondary))
Spacer()
Text(value)
.font(.system(.callout, design: .monospaced))
@@ -687,7 +683,6 @@ struct WorkflowBulkAddSheet: View {
}
}
}
- .preferredColorScheme(.dark)
}
private var selectedDomainList: [String] {