Commit 2c608ed9cb
Unsigned
Layout: unified · split
Docs/ACCESSIBILITY.md +26
| @@ -29,6 +29,12 @@ worst of those three is shown: | |||
| 29 | | `StatusWarning` | `#7A5600` | `#FFD60A` | 4.74 | 9.61 | | 29 | | `StatusWarning` | `#7A5600` | `#FFD60A` | 4.74 | 9.61 | |
| 30 | | `StatusCritical` | `#B3261E` | `#FF6961` | 4.51 | 5.54 | | 30 | | `StatusCritical` | `#B3261E` | `#FF6961` | 4.51 | 5.54 | |
| 31 | | `StatusNeutral` | `#5A5A5F` | `#A1A1A6` | 4.92 | 5.88 | | 31 | | `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` | ||
| 35 | is only **3.29:1** on a light card — below AA — which never showed while the app | ||
| 36 | was locked to dark, where the same colour reads 6.32:1. Unlocking light mode | ||
| 37 | exposed it across 191 sites. | ||
| 32 | 38 | ||
| 33 | High Contrast variants push further in the same direction. Surfaces | 39 | High Contrast variants push further in the same direction. Surfaces |
| 34 | (`AppBackground`, `AppSurface`, `AppSurfaceElevated`, `AppSeparator`) carry no | 40 | (`AppBackground`, `AppSurface`, `AppSurfaceElevated`, `AppSeparator`) carry no |
| @@ -58,6 +64,26 @@ So there are two colours: | |||
| 58 | `AppOnAccent` is the label colour for a solid accent fill and flips by scheme — | 64 | `AppOnAccent` is the label colour for a solid accent fill and flips by scheme — |
| 59 | white on the light accent, black on the dark one. | 65 | white on the light accent, black on the dark one. |
| 60 | 66 | ||
| 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 | ||
| 71 | app previously carried 16 separate `.preferredColorScheme(.dark)` calls scattered | ||
| 72 | through view bodies, which is how light mode became unreachable without anyone | ||
| 73 | noticing; re-applying per view is what let the lock spread. | ||
| 74 | |||
| 75 | Users 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 | |||
| 84 | Dark mode reports 18 findings and light mode 21; the three extra are the section | ||
| 85 | headers above. Everything the app actually controls passes in both schemes. | ||
| 86 | |||
| 61 | ## Findings are reported, not enforced | 87 | ## Findings are reported, not enforced |
| 62 | 88 | ||
| 63 | The audit surfaces violations that exist today, so failing on all of them would | 89 | The 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 { | |||
| 25 | Section("Audit Timeline") { | 25 | Section("Audit Timeline") { |
| 26 | if auditStore.sessions.isEmpty { | 26 | if auditStore.sessions.isEmpty { |
| 27 | Text("No audits yet.") | 27 | Text("No audits yet.") |
| 28 | .foregroundStyle(.secondary) | 28 | .foregroundStyle(Color(.appTextSecondary)) |
| 29 | } else { | 29 | } else { |
| 30 | ForEach(auditStore.sessions) { session in | 30 | ForEach(auditStore.sessions) { session in |
| 31 | Button { | 31 | Button { |
| @@ -35,7 +35,7 @@ struct AuditModeView: View { | |||
| 35 | Text(session.domain).font(.headline) | 35 | Text(session.domain).font(.headline) |
| 36 | Text("\(session.createdAt.formatted(date: .abbreviated, time: .shortened)) • \(session.status.title)") | 36 | Text("\(session.createdAt.formatted(date: .abbreviated, time: .shortened)) • \(session.status.title)") |
| 37 | .font(.caption) | 37 | .font(.caption) |
| 38 | .foregroundStyle(.secondary) | 38 | .foregroundStyle(Color(.appTextSecondary)) |
| 39 | } | 39 | } |
| 40 | } | 40 | } |
| 41 | } | 41 | } |
DomainDig/BatchResultsView.swift +8 −8
| @@ -17,12 +17,12 @@ struct BatchResultsView: View { | |||
| 17 | .frame(width: 120) | 17 | .frame(width: 120) |
| 18 | Text(viewModel.batchProgressLabel) | 18 | Text(viewModel.batchProgressLabel) |
| 19 | .font(appDensity.font(.caption2)) | 19 | .font(appDensity.font(.caption2)) |
| 20 | .foregroundStyle(.secondary) | 20 | .foregroundStyle(Color(.appTextSecondary)) |
| 21 | } | 21 | } |
| 22 | } else if !viewModel.batchResults.isEmpty { | 22 | } else if !viewModel.batchResults.isEmpty { |
| 23 | Text("\(viewModel.batchResults.count) domains") | 23 | Text("\(viewModel.batchResults.count) domains") |
| 24 | .font(appDensity.font(.caption2)) | 24 | .font(appDensity.font(.caption2)) |
| 25 | .foregroundStyle(.secondary) | 25 | .foregroundStyle(Color(.appTextSecondary)) |
| 26 | } | 26 | } |
| 27 | } | 27 | } |
| 28 | 28 | ||
| @@ -67,7 +67,7 @@ struct BatchResultRowView: View { | |||
| 67 | Spacer(minLength: 8) | 67 | Spacer(minLength: 8) |
| 68 | Text(result.resultSource.label.lowercased()) | 68 | Text(result.resultSource.label.lowercased()) |
| 69 | .font(appDensity.font(.caption2)) | 69 | .font(appDensity.font(.caption2)) |
| 70 | .foregroundStyle(.secondary) | 70 | .foregroundStyle(Color(.appTextSecondary)) |
| 71 | AppStatusBadgeView(model: quickStatusBadge) | 71 | AppStatusBadgeView(model: quickStatusBadge) |
| 72 | } | 72 | } |
| 73 | 73 | ||
| @@ -80,7 +80,7 @@ struct BatchResultRowView: View { | |||
| 80 | } | 80 | } |
| 81 | } | 81 | } |
| 82 | .font(appDensity.font(.caption2)) | 82 | .font(appDensity.font(.caption2)) |
| 83 | .foregroundStyle(.secondary) | 83 | .foregroundStyle(Color(.appTextSecondary)) |
| 84 | 84 | ||
| 85 | HStack(spacing: 10) { | 85 | HStack(spacing: 10) { |
| 86 | Text(result.primaryIP ?? "No IP") | 86 | Text(result.primaryIP ?? "No IP") |
| @@ -93,12 +93,12 @@ struct BatchResultRowView: View { | |||
| 93 | .lineLimit(1) | 93 | .lineLimit(1) |
| 94 | } | 94 | } |
| 95 | .font(appDensity.font(.caption2)) | 95 | .font(appDensity.font(.caption2)) |
| 96 | .foregroundStyle(.secondary) | 96 | .foregroundStyle(Color(.appTextSecondary)) |
| 97 | 97 | ||
| 98 | if let summaryMessage = result.summaryMessage { | 98 | if let summaryMessage = result.summaryMessage { |
| 99 | Text(summaryMessage) | 99 | Text(summaryMessage) |
| 100 | .font(appDensity.font(.caption2)) | 100 | .font(appDensity.font(.caption2)) |
| 101 | .foregroundStyle(.secondary) | 101 | .foregroundStyle(Color(.appTextSecondary)) |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | if let changeClassification = result.changeClassification { | 104 | if let changeClassification = result.changeClassification { |
| @@ -137,7 +137,7 @@ struct BatchResultRowView: View { | |||
| 137 | return .init( | 137 | return .init( |
| 138 | title: availabilityText, | 138 | title: availabilityText, |
| 139 | systemImage: "exclamationmark.circle", | 139 | systemImage: "exclamationmark.circle", |
| 140 | foregroundColor: .secondary, | 140 | foregroundColor: Color(.appTextSecondary), |
| 141 | backgroundColor: Color(.appSurfaceElevated) | 141 | backgroundColor: Color(.appSurfaceElevated) |
| 142 | ) | 142 | ) |
| 143 | } | 143 | } |
| @@ -145,7 +145,7 @@ struct BatchResultRowView: View { | |||
| 145 | private var quickStatusBadge: AppStatusBadgeModel { | 145 | private var quickStatusBadge: AppStatusBadgeModel { |
| 146 | switch result.status { | 146 | switch result.status { |
| 147 | case .pending: | 147 | 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)) |
| 149 | case .running: | 149 | case .running: |
| 150 | return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) | 150 | return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) |
| 151 | case .completed: | 151 | case .completed: |
DomainDig/BatchSweepSummaryView.swift +2 −2
| @@ -32,7 +32,7 @@ struct BatchSweepSummaryView: View { | |||
| 32 | if visibleResults.isEmpty { | 32 | if visibleResults.isEmpty { |
| 33 | Text("No domains with changes or warnings") | 33 | Text("No domains with changes or warnings") |
| 34 | .font(.system(.caption, design: .monospaced)) | 34 | .font(.system(.caption, design: .monospaced)) |
| 35 | .foregroundStyle(.secondary) | 35 | .foregroundStyle(Color(.appTextSecondary)) |
| 36 | } else { | 36 | } else { |
| 37 | ForEach(visibleResults) { result in | 37 | ForEach(visibleResults) { result in |
| 38 | if let entry = viewModel.historyEntry(for: result) { | 38 | if let entry = viewModel.historyEntry(for: result) { |
| @@ -56,7 +56,7 @@ struct BatchSweepSummaryView: View { | |||
| 56 | HStack { | 56 | HStack { |
| 57 | Text(label) | 57 | Text(label) |
| 58 | .font(.system(.caption, design: .monospaced)) | 58 | .font(.system(.caption, design: .monospaced)) |
| 59 | .foregroundStyle(.secondary) | 59 | .foregroundStyle(Color(.appTextSecondary)) |
| 60 | Spacer() | 60 | Spacer() |
| 61 | Text(value) | 61 | Text(value) |
| 62 | .font(.system(.callout, design: .monospaced)) | 62 | .font(.system(.callout, design: .monospaced)) |
DomainDig/ContentView.swift +77 −72
| @@ -173,8 +173,6 @@ struct ContentView: View { | |||
| 173 | ) | 173 | ) |
| 174 | ) | 174 | ) |
| 175 | .navigationTitle("DomainDig") | 175 | .navigationTitle("DomainDig") |
| 176 | .toolbarColorScheme(.dark, for: .navigationBar) | ||
| 177 | .preferredColorScheme(.dark) | ||
| 178 | .toolbar { | 176 | .toolbar { |
| 179 | ToolbarItem(placement: .topBarTrailing) { | 177 | ToolbarItem(placement: .topBarTrailing) { |
| 180 | if viewModel.hasRun { | 178 | if viewModel.hasRun { |
| @@ -182,7 +180,7 @@ struct ContentView: View { | |||
| 182 | viewModel.reset() | 180 | viewModel.reset() |
| 183 | } label: { | 181 | } label: { |
| 184 | Image(systemName: "xmark.circle") | 182 | Image(systemName: "xmark.circle") |
| 185 | .foregroundStyle(.secondary) | 183 | .foregroundStyle(Color(.appTextSecondary)) |
| 186 | } | 184 | } |
| 187 | } | 185 | } |
| 188 | } | 186 | } |
| @@ -327,12 +325,12 @@ struct ContentView: View { | |||
| 327 | VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) { | 325 | VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) { |
| 328 | Text("Paste domains separated by new lines or commas.") | 326 | Text("Paste domains separated by new lines or commas.") |
| 329 | .font(appDensity.font(.caption)) | 327 | .font(appDensity.font(.caption)) |
| 330 | .foregroundStyle(.secondary) | 328 | .foregroundStyle(Color(.appTextSecondary)) |
| 331 | 329 | ||
| 332 | if let batchAllowanceSummary = FeatureAccessService.batchAllowanceSummary() { | 330 | if let batchAllowanceSummary = FeatureAccessService.batchAllowanceSummary() { |
| 333 | Text(batchAllowanceSummary) | 331 | Text(batchAllowanceSummary) |
| 334 | .font(appDensity.font(.caption2)) | 332 | .font(appDensity.font(.caption2)) |
| 335 | .foregroundStyle(.secondary) | 333 | .foregroundStyle(Color(.appTextSecondary)) |
| 336 | } | 334 | } |
| 337 | 335 | ||
| 338 | TextField( | 336 | TextField( |
| @@ -544,7 +542,7 @@ struct ContentView: View { | |||
| 544 | } label: { | 542 | } label: { |
| 545 | Image(systemName: "bolt.circle") | 543 | Image(systemName: "bolt.circle") |
| 546 | .font(appDensity.font(.body, design: .default)) | 544 | .font(appDensity.font(.body, design: .default)) |
| 547 | .foregroundStyle(.secondary) | 545 | .foregroundStyle(Color(.appTextSecondary)) |
| 548 | } | 546 | } |
| 549 | Button { | 547 | Button { |
| 550 | viewModel.toggleSavedDomain() | 548 | viewModel.toggleSavedDomain() |
| @@ -583,7 +581,7 @@ struct ContentView: View { | |||
| 583 | } label: { | 581 | } label: { |
| 584 | Image(systemName: "square.and.arrow.up") | 582 | Image(systemName: "square.and.arrow.up") |
| 585 | .font(appDensity.font(.body, design: .default)) | 583 | .font(appDensity.font(.body, design: .default)) |
| 586 | .foregroundStyle(.secondary) | 584 | .foregroundStyle(Color(.appTextSecondary)) |
| 587 | } | 585 | } |
| 588 | } | 586 | } |
| 589 | } | 587 | } |
| @@ -652,13 +650,13 @@ struct ContentView: View { | |||
| 652 | HStack { | 650 | HStack { |
| 653 | Text("RECENT") | 651 | Text("RECENT") |
| 654 | .font(appDensity.font(.caption2)) | 652 | .font(appDensity.font(.caption2)) |
| 655 | .foregroundStyle(.secondary) | 653 | .foregroundStyle(Color(.appTextSecondary)) |
| 656 | Spacer() | 654 | Spacer() |
| 657 | Button("Clear") { | 655 | Button("Clear") { |
| 658 | viewModel.clearRecentSearches() | 656 | viewModel.clearRecentSearches() |
| 659 | } | 657 | } |
| 660 | .font(appDensity.font(.caption2)) | 658 | .font(appDensity.font(.caption2)) |
| 661 | .foregroundStyle(.secondary) | 659 | .foregroundStyle(Color(.appTextSecondary)) |
| 662 | } | 660 | } |
| 663 | 661 | ||
| 664 | ForEach(viewModel.recentSearches, id: \.self) { domain in | 662 | ForEach(viewModel.recentSearches, id: \.self) { domain in |
| @@ -694,7 +692,7 @@ struct ContentView: View { | |||
| 694 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) | 692 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) |
| 695 | Text(message) | 693 | Text(message) |
| 696 | .font(appDensity.font(.callout, design: .default)) | 694 | .font(appDensity.font(.callout, design: .default)) |
| 697 | .foregroundStyle(.secondary) | 695 | .foregroundStyle(Color(.appTextSecondary)) |
| 698 | } | 696 | } |
| 699 | .frame(maxWidth: .infinity, alignment: .leading) | 697 | .frame(maxWidth: .infinity, alignment: .leading) |
| 700 | .padding(appDensity.metrics.cardPadding) | 698 | .padding(appDensity.metrics.cardPadding) |
| @@ -784,7 +782,7 @@ struct SummaryView: View { | |||
| 784 | VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing) { | 782 | VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing) { |
| 785 | Text(field.label) | 783 | Text(field.label) |
| 786 | .font(appDensity.font(.caption2)) | 784 | .font(appDensity.font(.caption2)) |
| 787 | .foregroundStyle(.secondary) | 785 | .foregroundStyle(Color(.appTextSecondary)) |
| 788 | Text(field.value) | 786 | Text(field.value) |
| 789 | .font(appDensity.font(.caption)) | 787 | .font(appDensity.font(.caption)) |
| 790 | .foregroundStyle(ResultColors.color(for: field.tone)) | 788 | .foregroundStyle(ResultColors.color(for: field.tone)) |
| @@ -826,13 +824,13 @@ struct RiskSummaryCardView: View { | |||
| 826 | Spacer() | 824 | Spacer() |
| 827 | Text("Deterministic") | 825 | Text("Deterministic") |
| 828 | .font(appDensity.font(.caption2)) | 826 | .font(appDensity.font(.caption2)) |
| 829 | .foregroundStyle(.secondary) | 827 | .foregroundStyle(Color(.appTextSecondary)) |
| 830 | } | 828 | } |
| 831 | 829 | ||
| 832 | if topFactors.isEmpty { | 830 | if topFactors.isEmpty { |
| 833 | Text("No major risk factors identified") | 831 | Text("No major risk factors identified") |
| 834 | .font(appDensity.font(.caption)) | 832 | .font(appDensity.font(.caption)) |
| 835 | .foregroundStyle(.secondary) | 833 | .foregroundStyle(Color(.appTextSecondary)) |
| 836 | } else { | 834 | } else { |
| 837 | ForEach(Array(topFactors.enumerated()), id: \.offset) { _, factor in | 835 | ForEach(Array(topFactors.enumerated()), id: \.offset) { _, factor in |
| 838 | HStack(alignment: .top, spacing: 8) { | 836 | HStack(alignment: .top, spacing: 8) { |
| @@ -884,7 +882,7 @@ struct InsightsSummaryCardView: View { | |||
| 884 | if insights.isEmpty { | 882 | if insights.isEmpty { |
| 885 | Text("No deterministic insights triggered") | 883 | Text("No deterministic insights triggered") |
| 886 | .font(appDensity.font(.caption)) | 884 | .font(appDensity.font(.caption)) |
| 887 | .foregroundStyle(.secondary) | 885 | .foregroundStyle(Color(.appTextSecondary)) |
| 888 | } else { | 886 | } else { |
| 889 | ForEach(Array(insights.enumerated()), id: \.offset) { _, insight in | 887 | ForEach(Array(insights.enumerated()), id: \.offset) { _, insight in |
| 890 | HStack(alignment: .top, spacing: 8) { | 888 | HStack(alignment: .top, spacing: 8) { |
| @@ -940,7 +938,7 @@ struct StickyLookupSummaryView: View { | |||
| 940 | HStack(spacing: 8) { | 938 | HStack(spacing: 8) { |
| 941 | Label(primaryIP, systemImage: "network") | 939 | Label(primaryIP, systemImage: "network") |
| 942 | .font(appDensity.font(.caption)) | 940 | .font(appDensity.font(.caption)) |
| 943 | .foregroundStyle(.secondary) | 941 | .foregroundStyle(Color(.appTextSecondary)) |
| 944 | Spacer(minLength: 6) | 942 | Spacer(minLength: 6) |
| 945 | AppCopyButton(value: primaryIP, label: "Copy IP") | 943 | AppCopyButton(value: primaryIP, label: "Copy IP") |
| 946 | } | 944 | } |
| @@ -966,7 +964,7 @@ struct LookupProgressOverviewView: View { | |||
| 966 | .foregroundStyle(.primary) | 964 | .foregroundStyle(.primary) |
| 967 | Text(steps.isEmpty ? "Preparing requests" : steps.joined(separator: " • ")) | 965 | Text(steps.isEmpty ? "Preparing requests" : steps.joined(separator: " • ")) |
| 968 | .font(appDensity.font(.caption2)) | 966 | .font(appDensity.font(.caption2)) |
| 969 | .foregroundStyle(.secondary) | 967 | .foregroundStyle(Color(.appTextSecondary)) |
| 970 | } | 968 | } |
| 971 | Spacer() | 969 | Spacer() |
| 972 | } | 970 | } |
| @@ -1049,13 +1047,13 @@ struct DomainChangeSummaryView: View { | |||
| 1049 | .clipShape(Capsule()) | 1047 | .clipShape(Capsule()) |
| 1050 | Text(summary.generatedAt, style: .time) | 1048 | Text(summary.generatedAt, style: .time) |
| 1051 | .font(appDensity.font(.caption2)) | 1049 | .font(appDensity.font(.caption2)) |
| 1052 | .foregroundStyle(.secondary) | 1050 | .foregroundStyle(Color(.appTextSecondary)) |
| 1053 | } | 1051 | } |
| 1054 | 1052 | ||
| 1055 | VStack(alignment: .leading, spacing: 4) { | 1053 | VStack(alignment: .leading, spacing: 4) { |
| 1056 | Text("Inference") | 1054 | Text("Inference") |
| 1057 | .font(appDensity.font(.caption2)) | 1055 | .font(appDensity.font(.caption2)) |
| 1058 | .foregroundStyle(.secondary) | 1056 | .foregroundStyle(Color(.appTextSecondary)) |
| 1059 | Text(summary.message) | 1057 | Text(summary.message) |
| 1060 | .font(appDensity.font(.caption)) | 1058 | .font(appDensity.font(.caption)) |
| 1061 | .foregroundStyle(.primary) | 1059 | .foregroundStyle(.primary) |
| @@ -1069,7 +1067,7 @@ struct DomainChangeSummaryView: View { | |||
| 1069 | VStack(alignment: .leading, spacing: 4) { | 1067 | VStack(alignment: .leading, spacing: 4) { |
| 1070 | Text("Observed") | 1068 | Text("Observed") |
| 1071 | .font(appDensity.font(.caption2)) | 1069 | .font(appDensity.font(.caption2)) |
| 1072 | .foregroundStyle(.secondary) | 1070 | .foregroundStyle(Color(.appTextSecondary)) |
| 1073 | ForEach(Array(summary.observedFacts.enumerated()), id: \.offset) { _, fact in | 1071 | ForEach(Array(summary.observedFacts.enumerated()), id: \.offset) { _, fact in |
| 1074 | Text(fact) | 1072 | Text(fact) |
| 1075 | .font(appDensity.font(.caption)) | 1073 | .font(appDensity.font(.caption)) |
| @@ -1171,7 +1169,7 @@ struct DomainDiffView: View { | |||
| 1171 | HStack { | 1169 | HStack { |
| 1172 | Text(item.label) | 1170 | Text(item.label) |
| 1173 | .font(.system(.caption, design: .monospaced)) | 1171 | .font(.system(.caption, design: .monospaced)) |
| 1174 | .foregroundStyle(.secondary) | 1172 | .foregroundStyle(Color(.appTextSecondary)) |
| 1175 | Spacer() | 1173 | Spacer() |
| 1176 | Text("\(item.changeType.marker) \(item.severity.title) • \(changeLabel(for: item.changeType))") | 1174 | Text("\(item.changeType.marker) \(item.severity.title) • \(changeLabel(for: item.changeType))") |
| 1177 | .font(.system(.caption2, design: .monospaced)) | 1175 | .font(.system(.caption2, design: .monospaced)) |
| @@ -1186,10 +1184,10 @@ struct DomainDiffView: View { | |||
| 1186 | VStack(alignment: .leading, spacing: 2) { | 1184 | VStack(alignment: .leading, spacing: 2) { |
| 1187 | Text("Old") | 1185 | Text("Old") |
| 1188 | .font(.system(.caption2, design: .monospaced)) | 1186 | .font(.system(.caption2, design: .monospaced)) |
| 1189 | .foregroundStyle(.secondary) | 1187 | .foregroundStyle(Color(.appTextSecondary)) |
| 1190 | Text(oldValue) | 1188 | Text(oldValue) |
| 1191 | .font(.system(.caption2, design: .monospaced)) | 1189 | .font(.system(.caption2, design: .monospaced)) |
| 1192 | .foregroundStyle(.secondary) | 1190 | .foregroundStyle(Color(.appTextSecondary)) |
| 1193 | .textSelection(.enabled) | 1191 | .textSelection(.enabled) |
| 1194 | } | 1192 | } |
| 1195 | } | 1193 | } |
| @@ -1198,7 +1196,7 @@ struct DomainDiffView: View { | |||
| 1198 | VStack(alignment: .leading, spacing: 2) { | 1196 | VStack(alignment: .leading, spacing: 2) { |
| 1199 | Text("New") | 1197 | Text("New") |
| 1200 | .font(.system(.caption2, design: .monospaced)) | 1198 | .font(.system(.caption2, design: .monospaced)) |
| 1201 | .foregroundStyle(.secondary) | 1199 | .foregroundStyle(Color(.appTextSecondary)) |
| 1202 | Text(newValue) | 1200 | Text(newValue) |
| 1203 | .font(.system(.caption, design: .monospaced)) | 1201 | .font(.system(.caption, design: .monospaced)) |
| 1204 | .foregroundStyle(item.hasChanges ? .primary : .secondary) | 1202 | .foregroundStyle(item.hasChanges ? .primary : .secondary) |
| @@ -1306,7 +1304,7 @@ struct TrackedDomainDetailHeaderView: View { | |||
| 1306 | Text("Last refresh \(trackedDomain.updatedAt.formatted(date: .abbreviated, time: .shortened))") | 1304 | Text("Last refresh \(trackedDomain.updatedAt.formatted(date: .abbreviated, time: .shortened))") |
| 1307 | } | 1305 | } |
| 1308 | .font(.system(.caption2, design: .monospaced)) | 1306 | .font(.system(.caption2, design: .monospaced)) |
| 1309 | .foregroundStyle(.secondary) | 1307 | .foregroundStyle(Color(.appTextSecondary)) |
| 1310 | } | 1308 | } |
| 1311 | } | 1309 | } |
| 1312 | } | 1310 | } |
| @@ -1394,7 +1392,7 @@ struct DomainSectionView: View { | |||
| 1394 | VStack(alignment: .leading, spacing: 8) { | 1392 | VStack(alignment: .leading, spacing: 8) { |
| 1395 | Text("Part of workflow") | 1393 | Text("Part of workflow") |
| 1396 | .font(appDensity.font(.caption)) | 1394 | .font(appDensity.font(.caption)) |
| 1397 | .foregroundStyle(.secondary) | 1395 | .foregroundStyle(Color(.appTextSecondary)) |
| 1398 | 1396 | ||
| 1399 | ForEach(workflows) { workflow in | 1397 | ForEach(workflows) { workflow in |
| 1400 | HStack { | 1398 | HStack { |
| @@ -1429,7 +1427,7 @@ struct DomainSectionView: View { | |||
| 1429 | if showSuggestions { | 1427 | if showSuggestions { |
| 1430 | Text("Suggestions") | 1428 | Text("Suggestions") |
| 1431 | .font(appDensity.font(.caption)) | 1429 | .font(appDensity.font(.caption)) |
| 1432 | .foregroundStyle(.secondary) | 1430 | .foregroundStyle(Color(.appTextSecondary)) |
| 1433 | .padding(.top, 4) | 1431 | .padding(.top, 4) |
| 1434 | if suggestionsLoading { | 1432 | if suggestionsLoading { |
| 1435 | ProgressView("Checking alternatives…") | 1433 | ProgressView("Checking alternatives…") |
| @@ -1527,7 +1525,7 @@ struct OwnershipSectionView: View { | |||
| 1527 | HStack { | 1525 | HStack { |
| 1528 | Text("History") | 1526 | Text("History") |
| 1529 | .font(appDensity.font(.caption)) | 1527 | .font(appDensity.font(.caption)) |
| 1530 | .foregroundStyle(.secondary) | 1528 | .foregroundStyle(Color(.appTextSecondary)) |
| 1531 | Spacer() | 1529 | Spacer() |
| 1532 | if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder { | 1530 | if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder { |
| 1533 | Button("Load") { | 1531 | Button("Load") { |
| @@ -1545,12 +1543,12 @@ struct OwnershipSectionView: View { | |||
| 1545 | VStack(alignment: .leading, spacing: 3) { | 1543 | VStack(alignment: .leading, spacing: 3) { |
| 1546 | Text(event.date.formatted(date: .abbreviated, time: .omitted)) | 1544 | Text(event.date.formatted(date: .abbreviated, time: .omitted)) |
| 1547 | .font(appDensity.font(.caption2)) | 1545 | .font(appDensity.font(.caption2)) |
| 1548 | .foregroundStyle(.secondary) | 1546 | .foregroundStyle(Color(.appTextSecondary)) |
| 1549 | Text(event.summary) | 1547 | Text(event.summary) |
| 1550 | .font(appDensity.font(.caption)) | 1548 | .font(appDensity.font(.caption)) |
| 1551 | Text(event.source) | 1549 | Text(event.source) |
| 1552 | .font(appDensity.font(.caption2)) | 1550 | .font(appDensity.font(.caption2)) |
| 1553 | .foregroundStyle(.secondary) | 1551 | .foregroundStyle(Color(.appTextSecondary)) |
| 1554 | } | 1552 | } |
| 1555 | } | 1553 | } |
| 1556 | } else if let historyError { | 1554 | } else if let historyError { |
| @@ -1604,7 +1602,7 @@ struct IntelligenceSectionView: View { | |||
| 1604 | .font(appDensity.font(.caption, weight: .semibold)) | 1602 | .font(appDensity.font(.caption, weight: .semibold)) |
| 1605 | Text(signal.detail) | 1603 | Text(signal.detail) |
| 1606 | .font(appDensity.font(.caption2)) | 1604 | .font(appDensity.font(.caption2)) |
| 1607 | .foregroundStyle(.secondary) | 1605 | .foregroundStyle(Color(.appTextSecondary)) |
| 1608 | } | 1606 | } |
| 1609 | } | 1607 | } |
| 1610 | } | 1608 | } |
| @@ -1645,7 +1643,7 @@ struct IntelligenceSectionView: View { | |||
| 1645 | } | 1643 | } |
| 1646 | Text("First \(item.firstSeen.formatted(date: .abbreviated, time: .omitted)) • Last \(item.lastSeen.formatted(date: .abbreviated, time: .omitted)) • Seen \(item.recurrenceCount)x") | 1644 | Text("First \(item.firstSeen.formatted(date: .abbreviated, time: .omitted)) • Last \(item.lastSeen.formatted(date: .abbreviated, time: .omitted)) • Seen \(item.recurrenceCount)x") |
| 1647 | .font(appDensity.font(.caption2)) | 1645 | .font(appDensity.font(.caption2)) |
| 1648 | .foregroundStyle(.secondary) | 1646 | .foregroundStyle(Color(.appTextSecondary)) |
| 1649 | } | 1647 | } |
| 1650 | } | 1648 | } |
| 1651 | } | 1649 | } |
| @@ -1678,7 +1676,7 @@ struct IntelligenceSectionView: View { | |||
| 1678 | VStack(alignment: .leading, spacing: 3) { | 1676 | VStack(alignment: .leading, spacing: 3) { |
| 1679 | Text(date.formatted(date: .abbreviated, time: .omitted)) | 1677 | Text(date.formatted(date: .abbreviated, time: .omitted)) |
| 1680 | .font(appDensity.font(.caption2)) | 1678 | .font(appDensity.font(.caption2)) |
| 1681 | .foregroundStyle(.secondary) | 1679 | .foregroundStyle(Color(.appTextSecondary)) |
| 1682 | Text(title) | 1680 | Text(title) |
| 1683 | .font(appDensity.font(.caption)) | 1681 | .font(appDensity.font(.caption)) |
| 1684 | } | 1682 | } |
| @@ -1755,7 +1753,7 @@ struct SubdomainsSectionView: View { | |||
| 1755 | if !groups.isEmpty { | 1753 | if !groups.isEmpty { |
| 1756 | Text("Groups") | 1754 | Text("Groups") |
| 1757 | .font(appDensity.font(.caption2)) | 1755 | .font(appDensity.font(.caption2)) |
| 1758 | .foregroundStyle(.secondary) | 1756 | .foregroundStyle(Color(.appTextSecondary)) |
| 1759 | ForEach(groups) { group in | 1757 | ForEach(groups) { group in |
| 1760 | HStack { | 1758 | HStack { |
| 1761 | Text("\(group.label).*") | 1759 | Text("\(group.label).*") |
| @@ -1764,7 +1762,7 @@ struct SubdomainsSectionView: View { | |||
| 1764 | Spacer() | 1762 | Spacer() |
| 1765 | Text("\(group.subdomains.count)") | 1763 | Text("\(group.subdomains.count)") |
| 1766 | .font(appDensity.font(.caption2)) | 1764 | .font(appDensity.font(.caption2)) |
| 1767 | .foregroundStyle(.secondary) | 1765 | .foregroundStyle(Color(.appTextSecondary)) |
| 1768 | } | 1766 | } |
| 1769 | } | 1767 | } |
| 1770 | } | 1768 | } |
| @@ -1898,7 +1896,7 @@ struct DNSSectionView: View { | |||
| 1898 | if let wildcardTitle = section.wildcardTitle { | 1896 | if let wildcardTitle = section.wildcardTitle { |
| 1899 | Text(wildcardTitle) | 1897 | Text(wildcardTitle) |
| 1900 | .font(.system(.caption, design: .monospaced)) | 1898 | .font(.system(.caption, design: .monospaced)) |
| 1901 | .foregroundStyle(.secondary) | 1899 | .foregroundStyle(Color(.appTextSecondary)) |
| 1902 | .padding(.top, 4) | 1900 | .padding(.top, 4) |
| 1903 | ForEach(section.wildcardRows) { row in | 1901 | ForEach(section.wildcardRows) { row in |
| 1904 | LabeledValueRow(row: row) | 1902 | LabeledValueRow(row: row) |
| @@ -1940,18 +1938,18 @@ struct DNSSectionView: View { | |||
| 1940 | VStack(alignment: .leading, spacing: 3) { | 1938 | VStack(alignment: .leading, spacing: 3) { |
| 1941 | Text(event.date.formatted(date: .abbreviated, time: .omitted)) | 1939 | Text(event.date.formatted(date: .abbreviated, time: .omitted)) |
| 1942 | .font(appDensity.font(.caption2)) | 1940 | .font(appDensity.font(.caption2)) |
| 1943 | .foregroundStyle(.secondary) | 1941 | .foregroundStyle(Color(.appTextSecondary)) |
| 1944 | Text(event.summary) | 1942 | Text(event.summary) |
| 1945 | .font(appDensity.font(.caption)) | 1943 | .font(appDensity.font(.caption)) |
| 1946 | if !event.aRecords.isEmpty { | 1944 | if !event.aRecords.isEmpty { |
| 1947 | Text("A: \(event.aRecords.joined(separator: ", "))") | 1945 | Text("A: \(event.aRecords.joined(separator: ", "))") |
| 1948 | .font(appDensity.font(.caption2)) | 1946 | .font(appDensity.font(.caption2)) |
| 1949 | .foregroundStyle(.secondary) | 1947 | .foregroundStyle(Color(.appTextSecondary)) |
| 1950 | } | 1948 | } |
| 1951 | if !event.nameservers.isEmpty { | 1949 | if !event.nameservers.isEmpty { |
| 1952 | Text("NS: \(event.nameservers.joined(separator: ", "))") | 1950 | Text("NS: \(event.nameservers.joined(separator: ", "))") |
| 1953 | .font(appDensity.font(.caption2)) | 1951 | .font(appDensity.font(.caption2)) |
| 1954 | .foregroundStyle(.secondary) | 1952 | .foregroundStyle(Color(.appTextSecondary)) |
| 1955 | } | 1953 | } |
| 1956 | } | 1954 | } |
| 1957 | } | 1955 | } |
| @@ -2017,7 +2015,7 @@ struct WebSectionView: View { | |||
| 2017 | if let sslInfo, !sslInfo.subjectAltNames.isEmpty { | 2015 | if let sslInfo, !sslInfo.subjectAltNames.isEmpty { |
| 2018 | Text("SANs") | 2016 | Text("SANs") |
| 2019 | .font(appDensity.font(.caption2)) | 2017 | .font(appDensity.font(.caption2)) |
| 2020 | .foregroundStyle(.secondary) | 2018 | .foregroundStyle(Color(.appTextSecondary)) |
| 2021 | ForEach(sslInfo.subjectAltNames, id: \.self) { san in | 2019 | ForEach(sslInfo.subjectAltNames, id: \.self) { san in |
| 2022 | HStack(alignment: .top, spacing: 8) { | 2020 | HStack(alignment: .top, spacing: 8) { |
| 2023 | Text(san) | 2021 | Text(san) |
| @@ -2091,7 +2089,7 @@ struct WebSectionView: View { | |||
| 2091 | HStack(alignment: .top, spacing: 6) { | 2089 | HStack(alignment: .top, spacing: 6) { |
| 2092 | Text(redirect.stepLabel) | 2090 | Text(redirect.stepLabel) |
| 2093 | .font(appDensity.font(.caption)) | 2091 | .font(appDensity.font(.caption)) |
| 2094 | .foregroundStyle(.secondary) | 2092 | .foregroundStyle(Color(.appTextSecondary)) |
| 2095 | .frame(width: 16, alignment: .trailing) | 2093 | .frame(width: 16, alignment: .trailing) |
| 2096 | Text(redirect.statusCode) | 2094 | Text(redirect.statusCode) |
| 2097 | .font(appDensity.font(.caption)) | 2095 | .font(appDensity.font(.caption)) |
| @@ -2104,7 +2102,7 @@ struct WebSectionView: View { | |||
| 2104 | if redirect.isFinal { | 2102 | if redirect.isFinal { |
| 2105 | Text("(final)") | 2103 | Text("(final)") |
| 2106 | .font(appDensity.font(.caption2)) | 2104 | .font(appDensity.font(.caption2)) |
| 2107 | .foregroundStyle(.secondary) | 2105 | .foregroundStyle(Color(.appTextSecondary)) |
| 2108 | } | 2106 | } |
| 2109 | } | 2107 | } |
| 2110 | } | 2108 | } |
| @@ -2149,7 +2147,7 @@ struct EmailSectionView: View { | |||
| 2149 | if !assessment.reasons.isEmpty { | 2147 | if !assessment.reasons.isEmpty { |
| 2150 | Text(assessment.reasons.joined(separator: " | ")) | 2148 | Text(assessment.reasons.joined(separator: " | ")) |
| 2151 | .font(appDensity.font(.caption2)) | 2149 | .font(appDensity.font(.caption2)) |
| 2152 | .foregroundStyle(.secondary) | 2150 | .foregroundStyle(Color(.appTextSecondary)) |
| 2153 | } | 2151 | } |
| 2154 | } | 2152 | } |
| 2155 | if loading { | 2153 | if loading { |
| @@ -2176,7 +2174,7 @@ struct EmailSectionView: View { | |||
| 2176 | if let auxiliaryDetail = row.auxiliaryDetail { | 2174 | if let auxiliaryDetail = row.auxiliaryDetail { |
| 2177 | Text(auxiliaryDetail) | 2175 | Text(auxiliaryDetail) |
| 2178 | .font(appDensity.font(.caption2)) | 2176 | .font(appDensity.font(.caption2)) |
| 2179 | .foregroundStyle(.secondary) | 2177 | .foregroundStyle(Color(.appTextSecondary)) |
| 2180 | } | 2178 | } |
| 2181 | } | 2179 | } |
| 2182 | } | 2180 | } |
| @@ -2192,9 +2190,9 @@ struct EmailSectionView: View { | |||
| 2192 | case .warning: | 2190 | case .warning: |
| 2193 | return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | 2191 | return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) |
| 2194 | case .failure: | 2192 | 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)) |
| 2196 | case .primary, .secondary: | 2194 | 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)) |
| 2198 | } | 2196 | } |
| 2199 | } | 2197 | } |
| 2200 | } | 2198 | } |
| @@ -2244,7 +2242,7 @@ struct NetworkSectionView: View { | |||
| 2244 | Spacer() | 2242 | Spacer() |
| 2245 | Text(row.latencyLabel) | 2243 | Text(row.latencyLabel) |
| 2246 | .font(appDensity.font(.caption2)) | 2244 | .font(appDensity.font(.caption2)) |
| 2247 | .foregroundStyle(.secondary) | 2245 | .foregroundStyle(Color(.appTextSecondary)) |
| 2248 | AppStatusBadgeView(model: reachabilityBadge(row)) | 2246 | AppStatusBadgeView(model: reachabilityBadge(row)) |
| 2249 | } | 2247 | } |
| 2250 | } | 2248 | } |
| @@ -2304,7 +2302,7 @@ struct NetworkSectionView: View { | |||
| 2304 | } else { | 2302 | } else { |
| 2305 | Text("Standard Ports") | 2303 | Text("Standard Ports") |
| 2306 | .font(.system(.caption, design: .monospaced)) | 2304 | .font(.system(.caption, design: .monospaced)) |
| 2307 | .foregroundStyle(.secondary) | 2305 | .foregroundStyle(Color(.appTextSecondary)) |
| 2308 | PortRowsView(rows: standardPortRows) | 2306 | PortRowsView(rows: standardPortRows) |
| 2309 | } | 2307 | } |
| 2310 | 2308 | ||
| @@ -2353,7 +2351,7 @@ struct NetworkSectionView: View { | |||
| 2353 | case .failure: | 2351 | case .failure: |
| 2354 | return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | 2352 | return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) |
| 2355 | case .primary, .secondary: | 2353 | 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)) |
| 2357 | } | 2355 | } |
| 2358 | } | 2356 | } |
| 2359 | } | 2357 | } |
| @@ -2379,14 +2377,14 @@ struct PortRowsView: View { | |||
| 2379 | if let durationLabel = row.durationLabel { | 2377 | if let durationLabel = row.durationLabel { |
| 2380 | Text(durationLabel) | 2378 | Text(durationLabel) |
| 2381 | .font(appDensity.font(.caption2)) | 2379 | .font(appDensity.font(.caption2)) |
| 2382 | .foregroundStyle(.secondary) | 2380 | .foregroundStyle(Color(.appTextSecondary)) |
| 2383 | } | 2381 | } |
| 2384 | AppStatusBadgeView(model: portBadge(row)) | 2382 | AppStatusBadgeView(model: portBadge(row)) |
| 2385 | } | 2383 | } |
| 2386 | if let banner = row.banner { | 2384 | if let banner = row.banner { |
| 2387 | Text(banner) | 2385 | Text(banner) |
| 2388 | .font(appDensity.font(.caption2)) | 2386 | .font(appDensity.font(.caption2)) |
| 2389 | .foregroundStyle(.secondary) | 2387 | .foregroundStyle(Color(.appTextSecondary)) |
| 2390 | .padding(.leading, 8) | 2388 | .padding(.leading, 8) |
| 2391 | } | 2389 | } |
| 2392 | } | 2390 | } |
| @@ -2404,7 +2402,7 @@ struct PortRowsView: View { | |||
| 2404 | case .failure: | 2402 | case .failure: |
| 2405 | return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) | 2403 | return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: Color(.statusCritical), backgroundColor: Color(.statusCritical).opacity(0.16)) |
| 2406 | case .primary, .secondary: | 2404 | 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)) |
| 2408 | } | 2406 | } |
| 2409 | } | 2407 | } |
| 2410 | } | 2408 | } |
| @@ -2512,15 +2510,15 @@ struct SectionTrustMetadataView: View { | |||
| 2512 | if let confidence { | 2510 | if let confidence { |
| 2513 | Text("Confidence \(confidence.title)") | 2511 | Text("Confidence \(confidence.title)") |
| 2514 | .font(appDensity.font(.caption2)) | 2512 | .font(appDensity.font(.caption2)) |
| 2515 | .foregroundStyle(.secondary) | 2513 | .foregroundStyle(Color(.appTextSecondary)) |
| 2516 | } | 2514 | } |
| 2517 | if let provenance { | 2515 | if let provenance { |
| 2518 | Text(provenance.provider ?? provenance.source) | 2516 | Text(provenance.provider ?? provenance.source) |
| 2519 | .font(appDensity.font(.caption2)) | 2517 | .font(appDensity.font(.caption2)) |
| 2520 | .foregroundStyle(.secondary) | 2518 | .foregroundStyle(Color(.appTextSecondary)) |
| 2521 | Text(provenance.resultSource.label) | 2519 | Text(provenance.resultSource.label) |
| 2522 | .font(appDensity.font(.caption2)) | 2520 | .font(appDensity.font(.caption2)) |
| 2523 | .foregroundStyle(.secondary) | 2521 | .foregroundStyle(Color(.appTextSecondary)) |
| 2524 | } | 2522 | } |
| 2525 | } | 2523 | } |
| 2526 | DisclosureGroup("Details") { | 2524 | DisclosureGroup("Details") { |
| @@ -2559,7 +2557,7 @@ struct LabeledValueRow: View { | |||
| 2559 | VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing - 1) { | 2557 | VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing - 1) { |
| 2560 | Text(row.label) | 2558 | Text(row.label) |
| 2561 | .font(appDensity.font(.caption2)) | 2559 | .font(appDensity.font(.caption2)) |
| 2562 | .foregroundStyle(.secondary) | 2560 | .foregroundStyle(Color(.appTextSecondary)) |
| 2563 | Text(row.value) | 2561 | Text(row.value) |
| 2564 | .font(appDensity.font(.caption)) | 2562 | .font(appDensity.font(.caption)) |
| 2565 | .foregroundStyle(ResultColors.color(for: row.tone)) | 2563 | .foregroundStyle(ResultColors.color(for: row.tone)) |
| @@ -2656,7 +2654,7 @@ struct SettingsView: View { | |||
| 2656 | if let statusMessage = purchaseService.statusMessage { | 2654 | if let statusMessage = purchaseService.statusMessage { |
| 2657 | Text(statusMessage) | 2655 | Text(statusMessage) |
| 2658 | .font(appDensity.font(.caption, design: .default)) | 2656 | .font(appDensity.font(.caption, design: .default)) |
| 2659 | .foregroundStyle(.secondary) | 2657 | .foregroundStyle(Color(.appTextSecondary)) |
| 2660 | } | 2658 | } |
| 2661 | 2659 | ||
| 2662 | if let errorMessage = purchaseService.errorMessage { | 2660 | if let errorMessage = purchaseService.errorMessage { |
| @@ -2732,10 +2730,17 @@ struct SettingsView: View { | |||
| 2732 | 2730 | ||
| 2733 | private struct DisplaySettingsView: View { | 2731 | private struct DisplaySettingsView: View { |
| 2734 | @AppStorage(AppDensity.userDefaultsKey) private var storedDensity = AppDensity.compact.rawValue | 2732 | @AppStorage(AppDensity.userDefaultsKey) private var storedDensity = AppDensity.compact.rawValue |
| 2733 | @AppStorage(AppAppearance.userDefaultsKey) private var storedAppearance = AppAppearance.system.rawValue | ||
| 2735 | 2734 | ||
| 2736 | var body: some View { | 2735 | var body: some View { |
| 2737 | Form { | 2736 | Form { |
| 2738 | Section("Display") { | 2737 | Section("Display") { |
| 2738 | Picker("Appearance", selection: $storedAppearance) { | ||
| 2739 | ForEach(AppAppearance.allCases) { appearance in | ||
| 2740 | Text(appearance.title).tag(appearance.rawValue) | ||
| 2741 | } | ||
| 2742 | } | ||
| 2743 | |||
| 2739 | Picker("Density", selection: $storedDensity) { | 2744 | Picker("Density", selection: $storedDensity) { |
| 2740 | ForEach(AppDensity.allCases) { density in | 2745 | ForEach(AppDensity.allCases) { density in |
| 2741 | Text(density.title).tag(density.rawValue) | 2746 | Text(density.title).tag(density.rawValue) |
| @@ -2778,7 +2783,7 @@ private struct HistoryNetworkSettingsView: View { | |||
| 2778 | 2783 | ||
| 2779 | Text("History remains local-first. Auto-prune only trims older local snapshots on this device and defaults to unlimited.") | 2784 | Text("History remains local-first. Auto-prune only trims older local snapshots on this device and defaults to unlimited.") |
| 2780 | .font(appDensity.font(.caption, design: .default)) | 2785 | .font(appDensity.font(.caption, design: .default)) |
| 2781 | .foregroundStyle(.secondary) | 2786 | .foregroundStyle(Color(.appTextSecondary)) |
| 2782 | } | 2787 | } |
| 2783 | 2788 | ||
| 2784 | Section("Network") { | 2789 | Section("Network") { |
| @@ -2870,11 +2875,11 @@ private struct CloudSyncSettingsView: View { | |||
| 2870 | 2875 | ||
| 2871 | 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.") | 2876 | 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.") |
| 2872 | .font(appDensity.font(.caption, design: .default)) | 2877 | .font(appDensity.font(.caption, design: .default)) |
| 2873 | .foregroundStyle(.secondary) | 2878 | .foregroundStyle(Color(.appTextSecondary)) |
| 2874 | 2879 | ||
| 2875 | Text(cloudSyncService.detailMessage) | 2880 | Text(cloudSyncService.detailMessage) |
| 2876 | .font(appDensity.font(.caption, design: .default)) | 2881 | .font(appDensity.font(.caption, design: .default)) |
| 2877 | .foregroundStyle(.secondary) | 2882 | .foregroundStyle(Color(.appTextSecondary)) |
| 2878 | 2883 | ||
| 2879 | if let lastErrorMessage = cloudSyncService.lastErrorMessage { | 2884 | if let lastErrorMessage = cloudSyncService.lastErrorMessage { |
| 2880 | Text(lastErrorMessage) | 2885 | Text(lastErrorMessage) |
| @@ -2932,7 +2937,7 @@ private struct LocalAPISettingsView: View { | |||
| 2932 | if let statusMessage = localAPIService.statusMessage { | 2937 | if let statusMessage = localAPIService.statusMessage { |
| 2933 | Text(statusMessage) | 2938 | Text(statusMessage) |
| 2934 | .font(appDensity.font(.caption, design: .default)) | 2939 | .font(appDensity.font(.caption, design: .default)) |
| 2935 | .foregroundStyle(.secondary) | 2940 | .foregroundStyle(Color(.appTextSecondary)) |
| 2936 | } | 2941 | } |
| 2937 | } | 2942 | } |
| 2938 | 2943 | ||
| @@ -2947,7 +2952,7 @@ private struct LocalAPISettingsView: View { | |||
| 2947 | 2952 | ||
| 2948 | 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.") | 2953 | 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.") |
| 2949 | .font(appDensity.font(.caption, design: .default)) | 2954 | .font(appDensity.font(.caption, design: .default)) |
| 2950 | .foregroundStyle(.secondary) | 2955 | .foregroundStyle(Color(.appTextSecondary)) |
| 2951 | } | 2956 | } |
| 2952 | 2957 | ||
| 2953 | Section("Request Logging") { | 2958 | Section("Request Logging") { |
| @@ -2962,7 +2967,7 @@ private struct LocalAPISettingsView: View { | |||
| 2962 | if localAPIService.requestLogs.isEmpty { | 2967 | if localAPIService.requestLogs.isEmpty { |
| 2963 | Text("No local API requests logged yet.") | 2968 | Text("No local API requests logged yet.") |
| 2964 | .font(appDensity.font(.caption, design: .default)) | 2969 | .font(appDensity.font(.caption, design: .default)) |
| 2965 | .foregroundStyle(.secondary) | 2970 | .foregroundStyle(Color(.appTextSecondary)) |
| 2966 | } else { | 2971 | } else { |
| 2967 | ForEach(localAPIService.requestLogs.prefix(25)) { log in | 2972 | ForEach(localAPIService.requestLogs.prefix(25)) { log in |
| 2968 | VStack(alignment: .leading, spacing: 4) { | 2973 | VStack(alignment: .leading, spacing: 4) { |
| @@ -2977,11 +2982,11 @@ private struct LocalAPISettingsView: View { | |||
| 2977 | 2982 | ||
| 2978 | Text(log.timestamp.formatted(date: .abbreviated, time: .standard)) | 2983 | Text(log.timestamp.formatted(date: .abbreviated, time: .standard)) |
| 2979 | .font(appDensity.font(.caption2, design: .default)) | 2984 | .font(appDensity.font(.caption2, design: .default)) |
| 2980 | .foregroundStyle(.secondary) | 2985 | .foregroundStyle(Color(.appTextSecondary)) |
| 2981 | 2986 | ||
| 2982 | Text("\(Int(log.duration * 1000)) ms") | 2987 | Text("\(Int(log.duration * 1000)) ms") |
| 2983 | .font(appDensity.font(.caption2, design: .default)) | 2988 | .font(appDensity.font(.caption2, design: .default)) |
| 2984 | .foregroundStyle(.secondary) | 2989 | .foregroundStyle(Color(.appTextSecondary)) |
| 2985 | } | 2990 | } |
| 2986 | } | 2991 | } |
| 2987 | } | 2992 | } |
| @@ -3184,13 +3189,13 @@ private struct MonitoringSettingsView: View { | |||
| 3184 | if let monitoringStatusMessage = viewModel.monitoringStatusMessage { | 3189 | if let monitoringStatusMessage = viewModel.monitoringStatusMessage { |
| 3185 | Text(monitoringStatusMessage) | 3190 | Text(monitoringStatusMessage) |
| 3186 | .font(appDensity.font(.caption, design: .default)) | 3191 | .font(appDensity.font(.caption, design: .default)) |
| 3187 | .foregroundStyle(.secondary) | 3192 | .foregroundStyle(Color(.appTextSecondary)) |
| 3188 | } | 3193 | } |
| 3189 | 3194 | ||
| 3190 | if !FeatureAccessService.hasAccess(to: .automatedMonitoring) { | 3195 | if !FeatureAccessService.hasAccess(to: .automatedMonitoring) { |
| 3191 | Text("Background monitoring and alerts are available in Pro.") | 3196 | Text("Background monitoring and alerts are available in Pro.") |
| 3192 | .font(appDensity.font(.caption, design: .default)) | 3197 | .font(appDensity.font(.caption, design: .default)) |
| 3193 | .foregroundStyle(.secondary) | 3198 | .foregroundStyle(Color(.appTextSecondary)) |
| 3194 | } | 3199 | } |
| 3195 | } | 3200 | } |
| 3196 | } | 3201 | } |
| @@ -3260,7 +3265,7 @@ private struct DataPortabilitySettingsView: View { | |||
| 3260 | 3265 | ||
| 3261 | Text(importMode.explanation) | 3266 | Text(importMode.explanation) |
| 3262 | .font(appDensity.font(.caption, design: .default)) | 3267 | .font(appDensity.font(.caption, design: .default)) |
| 3263 | .foregroundStyle(.secondary) | 3268 | .foregroundStyle(Color(.appTextSecondary)) |
| 3264 | 3269 | ||
| 3265 | Button("Export Full Backup") { | 3270 | Button("Export Full Backup") { |
| 3266 | exportFullBackup() | 3271 | exportFullBackup() |
| @@ -3317,12 +3322,12 @@ private struct DataPortabilitySettingsView: View { | |||
| 3317 | 3322 | ||
| 3318 | 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.") | 3323 | 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.") |
| 3319 | .font(appDensity.font(.caption, design: .default)) | 3324 | .font(appDensity.font(.caption, design: .default)) |
| 3320 | .foregroundStyle(.secondary) | 3325 | .foregroundStyle(Color(.appTextSecondary)) |
| 3321 | 3326 | ||
| 3322 | if let portabilityStatusMessage = viewModel.portabilityStatusMessage { | 3327 | if let portabilityStatusMessage = viewModel.portabilityStatusMessage { |
| 3323 | Text(portabilityStatusMessage) | 3328 | Text(portabilityStatusMessage) |
| 3324 | .font(appDensity.font(.caption, design: .default)) | 3329 | .font(appDensity.font(.caption, design: .default)) |
| 3325 | .foregroundStyle(.secondary) | 3330 | .foregroundStyle(Color(.appTextSecondary)) |
| 3326 | } | 3331 | } |
| 3327 | } | 3332 | } |
| 3328 | 3333 | ||
| @@ -3331,7 +3336,7 @@ private struct DataPortabilitySettingsView: View { | |||
| 3331 | Section("Import Debug") { | 3336 | Section("Import Debug") { |
| 3332 | Text(importDebugStatus) | 3337 | Text(importDebugStatus) |
| 3333 | .font(appDensity.font(.caption, design: .default)) | 3338 | .font(appDensity.font(.caption, design: .default)) |
| 3334 | .foregroundStyle(.secondary) | 3339 | .foregroundStyle(Color(.appTextSecondary)) |
| 3335 | .textSelection(.enabled) | 3340 | .textSelection(.enabled) |
| 3336 | } | 3341 | } |
| 3337 | } | 3342 | } |
| @@ -3636,7 +3641,7 @@ private struct DataManagementSettingsView: View { | |||
| 3636 | if let deleteAllSuccessMessage { | 3641 | if let deleteAllSuccessMessage { |
| 3637 | Text(deleteAllSuccessMessage) | 3642 | Text(deleteAllSuccessMessage) |
| 3638 | .font(.footnote.weight(.medium)) | 3643 | .font(.footnote.weight(.medium)) |
| 3639 | .foregroundStyle(.secondary) | 3644 | .foregroundStyle(Color(.appTextSecondary)) |
| 3640 | .padding(.horizontal, 14) | 3645 | .padding(.horizontal, 14) |
| 3641 | .padding(.vertical, 10) | 3646 | .padding(.vertical, 10) |
| 3642 | .background(.thinMaterial, in: Capsule()) | 3647 | .background(.thinMaterial, in: Capsule()) |
| @@ -3722,7 +3727,7 @@ private struct DataImportPreviewSheet: View { | |||
| 3722 | Section("Warnings") { | 3727 | Section("Warnings") { |
| 3723 | ForEach(preview.warnings, id: \.self) { warning in | 3728 | ForEach(preview.warnings, id: \.self) { warning in |
| 3724 | Text(warning) | 3729 | Text(warning) |
| 3725 | .foregroundStyle(.secondary) | 3730 | .foregroundStyle(Color(.appTextSecondary)) |
| 3726 | } | 3731 | } |
| 3727 | } | 3732 | } |
| 3728 | } | 3733 | } |
DomainDig/DashboardView.swift +7 −8
| @@ -139,7 +139,7 @@ struct DashboardView: View { | |||
| 139 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) | 139 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) |
| 140 | Text("\(group.domains.count) domain\(group.domains.count == 1 ? "" : "s")") | 140 | Text("\(group.domains.count) domain\(group.domains.count == 1 ? "" : "s")") |
| 141 | .font(appDensity.font(.caption)) | 141 | .font(appDensity.font(.caption)) |
| 142 | .foregroundStyle(.secondary) | 142 | .foregroundStyle(Color(.appTextSecondary)) |
| 143 | } | 143 | } |
| 144 | Spacer() | 144 | Spacer() |
| 145 | groupBadge(for: group.domains) | 145 | groupBadge(for: group.domains) |
| @@ -170,7 +170,6 @@ struct DashboardView: View { | |||
| 170 | } | 170 | } |
| 171 | } | 171 | } |
| 172 | } | 172 | } |
| 173 | .preferredColorScheme(.dark) | ||
| 174 | } | 173 | } |
| 175 | 174 | ||
| 176 | private func disclosureBinding(for apexDomain: String) -> Binding<Bool> { | 175 | private func disclosureBinding(for apexDomain: String) -> Binding<Bool> { |
| @@ -193,7 +192,7 @@ struct DashboardView: View { | |||
| 193 | VStack(alignment: .leading, spacing: 8) { | 192 | VStack(alignment: .leading, spacing: 8) { |
| 194 | Text(title) | 193 | Text(title) |
| 195 | .font(appDensity.font(.caption, design: .default, weight: .semibold)) | 194 | .font(appDensity.font(.caption, design: .default, weight: .semibold)) |
| 196 | .foregroundStyle(.secondary) | 195 | .foregroundStyle(Color(.appTextSecondary)) |
| 197 | Text("\(value)") | 196 | Text("\(value)") |
| 198 | .font(.system(size: 28, weight: .bold, design: .rounded)) | 197 | .font(.system(size: 28, weight: .bold, design: .rounded)) |
| 199 | .foregroundStyle(.primary) | 198 | .foregroundStyle(.primary) |
| @@ -230,7 +229,7 @@ struct DashboardView: View { | |||
| 230 | private func dashboardEmptyRow(_ message: String) -> some View { | 229 | private func dashboardEmptyRow(_ message: String) -> some View { |
| 231 | Text(message) | 230 | Text(message) |
| 232 | .font(appDensity.font(.caption)) | 231 | .font(appDensity.font(.caption)) |
| 233 | .foregroundStyle(.secondary) | 232 | .foregroundStyle(Color(.appTextSecondary)) |
| 234 | .padding(.vertical, 4) | 233 | .padding(.vertical, 4) |
| 235 | } | 234 | } |
| 236 | 235 | ||
| @@ -288,7 +287,7 @@ private struct PortfolioActivityRow: View { | |||
| 288 | Text(relativeTimestamp(item.timestamp)) | 287 | Text(relativeTimestamp(item.timestamp)) |
| 289 | } | 288 | } |
| 290 | .font(appDensity.font(.caption2)) | 289 | .font(appDensity.font(.caption2)) |
| 291 | .foregroundStyle(.secondary) | 290 | .foregroundStyle(Color(.appTextSecondary)) |
| 292 | } | 291 | } |
| 293 | } | 292 | } |
| 294 | .padding(.vertical, 4) | 293 | .padding(.vertical, 4) |
| @@ -320,10 +319,10 @@ private struct PortfolioAttentionRow: View { | |||
| 320 | .foregroundStyle(.primary) | 319 | .foregroundStyle(.primary) |
| 321 | Text(item.reason) | 320 | Text(item.reason) |
| 322 | .font(appDensity.font(.caption, design: .default)) | 321 | .font(appDensity.font(.caption, design: .default)) |
| 323 | .foregroundStyle(.secondary) | 322 | .foregroundStyle(Color(.appTextSecondary)) |
| 324 | Text(relativeTimestamp(item.timestamp)) | 323 | Text(relativeTimestamp(item.timestamp)) |
| 325 | .font(appDensity.font(.caption2)) | 324 | .font(appDensity.font(.caption2)) |
| 326 | .foregroundStyle(.secondary) | 325 | .foregroundStyle(Color(.appTextSecondary)) |
| 327 | } | 326 | } |
| 328 | } | 327 | } |
| 329 | .padding(.vertical, 4) | 328 | .padding(.vertical, 4) |
| @@ -353,7 +352,7 @@ private struct PortfolioExpiryRow: View { | |||
| 353 | .foregroundStyle(.primary) | 352 | .foregroundStyle(.primary) |
| 354 | Text(expirySubtitle) | 353 | Text(expirySubtitle) |
| 355 | .font(appDensity.font(.caption)) | 354 | .font(appDensity.font(.caption)) |
| 356 | .foregroundStyle(.secondary) | 355 | .foregroundStyle(Color(.appTextSecondary)) |
| 357 | } | 356 | } |
| 358 | Spacer() | 357 | Spacer() |
| 359 | AppStatusBadgeView(model: badgeModel) | 358 | AppStatusBadgeView(model: badgeModel) |
DomainDig/DomainCompareView.swift +1 −1
| @@ -38,7 +38,7 @@ struct DomainCompareView: View { | |||
| 38 | Section { | 38 | Section { |
| 39 | Text("Choose two different domains to compare.") | 39 | Text("Choose two different domains to compare.") |
| 40 | .font(.callout) | 40 | .font(.callout) |
| 41 | .foregroundStyle(.secondary) | 41 | .foregroundStyle(Color(.appTextSecondary)) |
| 42 | } | 42 | } |
| 43 | } | 43 | } |
| 44 | 44 | ||
DomainDig/DomainDig/AuditViews.swift +17 −19
| @@ -41,13 +41,13 @@ struct AuditListView: View { | |||
| 41 | Spacer() | 41 | Spacer() |
| 42 | Text("\(group.sessions.count) audit\(group.sessions.count == 1 ? "" : "s")") | 42 | Text("\(group.sessions.count) audit\(group.sessions.count == 1 ? "" : "s")") |
| 43 | .font(appDensity.font(.caption2)) | 43 | .font(appDensity.font(.caption2)) |
| 44 | .foregroundStyle(.secondary) | 44 | .foregroundStyle(Color(.appTextSecondary)) |
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | if let latest = group.sessions.first { | 47 | if let latest = group.sessions.first { |
| 48 | Text(latest.findings.isEmpty ? "No findings recorded yet" : latest.findings.map(\.title).prefix(2).joined(separator: " • ")) | 48 | Text(latest.findings.isEmpty ? "No findings recorded yet" : latest.findings.map(\.title).prefix(2).joined(separator: " • ")) |
| 49 | .font(appDensity.font(.caption)) | 49 | .font(appDensity.font(.caption)) |
| 50 | .foregroundStyle(.secondary) | 50 | .foregroundStyle(Color(.appTextSecondary)) |
| 51 | .lineLimit(2) | 51 | .lineLimit(2) |
| 52 | 52 | ||
| 53 | HStack(spacing: 8) { | 53 | HStack(spacing: 8) { |
| @@ -57,7 +57,7 @@ struct AuditListView: View { | |||
| 57 | } | 57 | } |
| 58 | Text("Checklist \(latest.completedChecklistCount)/\(latest.checklist.count)") | 58 | Text("Checklist \(latest.completedChecklistCount)/\(latest.checklist.count)") |
| 59 | .font(appDensity.font(.caption2)) | 59 | .font(appDensity.font(.caption2)) |
| 60 | .foregroundStyle(.secondary) | 60 | .foregroundStyle(Color(.appTextSecondary)) |
| 61 | } | 61 | } |
| 62 | } | 62 | } |
| 63 | } | 63 | } |
| @@ -85,7 +85,6 @@ struct AuditListView: View { | |||
| 85 | } | 85 | } |
| 86 | } | 86 | } |
| 87 | } | 87 | } |
| 88 | .preferredColorScheme(.dark) | ||
| 89 | } | 88 | } |
| 90 | } | 89 | } |
| 91 | 90 | ||
| @@ -116,7 +115,7 @@ struct AuditDomainTimelineView: View { | |||
| 116 | 115 | ||
| 117 | Text("Reviewer: \(session.reviewer)") | 116 | Text("Reviewer: \(session.reviewer)") |
| 118 | .font(appDensity.font(.caption)) | 117 | .font(appDensity.font(.caption)) |
| 119 | .foregroundStyle(.secondary) | 118 | .foregroundStyle(Color(.appTextSecondary)) |
| 120 | 119 | ||
| 121 | HStack(spacing: 8) { | 120 | HStack(spacing: 8) { |
| 122 | Text("\(session.findings.count) findings") | 121 | Text("\(session.findings.count) findings") |
| @@ -126,7 +125,7 @@ struct AuditDomainTimelineView: View { | |||
| 126 | } | 125 | } |
| 127 | } | 126 | } |
| 128 | .font(appDensity.font(.caption2)) | 127 | .font(appDensity.font(.caption2)) |
| 129 | .foregroundStyle(.secondary) | 128 | .foregroundStyle(Color(.appTextSecondary)) |
| 130 | } | 129 | } |
| 131 | .padding(.vertical, 4) | 130 | .padding(.vertical, 4) |
| 132 | } | 131 | } |
| @@ -143,11 +142,11 @@ struct AuditDomainTimelineView: View { | |||
| 143 | Spacer() | 142 | Spacer() |
| 144 | Text("\(point.findingCount) findings") | 143 | Text("\(point.findingCount) findings") |
| 145 | .font(appDensity.font(.caption2)) | 144 | .font(appDensity.font(.caption2)) |
| 146 | .foregroundStyle(.secondary) | 145 | .foregroundStyle(Color(.appTextSecondary)) |
| 147 | } | 146 | } |
| 148 | Text("Open high severity: \(point.openHighSeverityCount) • Repeated issues: \(point.repeatedIssueCount)") | 147 | Text("Open high severity: \(point.openHighSeverityCount) • Repeated issues: \(point.repeatedIssueCount)") |
| 149 | .font(appDensity.font(.caption2)) | 148 | .font(appDensity.font(.caption2)) |
| 150 | .foregroundStyle(.secondary) | 149 | .foregroundStyle(Color(.appTextSecondary)) |
| 151 | } | 150 | } |
| 152 | .padding(.vertical, 2) | 151 | .padding(.vertical, 2) |
| 153 | } | 152 | } |
| @@ -197,7 +196,7 @@ struct AuditSessionDetailView: View { | |||
| 197 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) | 196 | .font(appDensity.font(.headline, design: .default, weight: .semibold)) |
| 198 | Text("Reviewer: \(session.reviewer)") | 197 | Text("Reviewer: \(session.reviewer)") |
| 199 | .font(appDensity.font(.caption)) | 198 | .font(appDensity.font(.caption)) |
| 200 | .foregroundStyle(.secondary) | 199 | .foregroundStyle(Color(.appTextSecondary)) |
| 201 | } | 200 | } |
| 202 | Spacer() | 201 | Spacer() |
| 203 | auditStatusBadge(session.status) | 202 | auditStatusBadge(session.status) |
| @@ -215,7 +214,7 @@ struct AuditSessionDetailView: View { | |||
| 215 | 214 | ||
| 216 | Text("Captured \(session.evidence.capturedAt.formatted(date: .abbreviated, time: .shortened))") | 215 | Text("Captured \(session.evidence.capturedAt.formatted(date: .abbreviated, time: .shortened))") |
| 217 | .font(appDensity.font(.caption2)) | 216 | .font(appDensity.font(.caption2)) |
| 218 | .foregroundStyle(.secondary) | 217 | .foregroundStyle(Color(.appTextSecondary)) |
| 219 | } | 218 | } |
| 220 | } | 219 | } |
| 221 | .listRowBackground(Color(.appSurface)) | 220 | .listRowBackground(Color(.appSurface)) |
| @@ -256,7 +255,7 @@ struct AuditSessionDetailView: View { | |||
| 256 | .foregroundStyle(.primary) | 255 | .foregroundStyle(.primary) |
| 257 | Text(item.detail) | 256 | Text(item.detail) |
| 258 | .font(appDensity.font(.caption)) | 257 | .font(appDensity.font(.caption)) |
| 259 | .foregroundStyle(.secondary) | 258 | .foregroundStyle(Color(.appTextSecondary)) |
| 260 | } | 259 | } |
| 261 | Spacer() | 260 | Spacer() |
| 262 | } | 261 | } |
| @@ -270,7 +269,7 @@ struct AuditSessionDetailView: View { | |||
| 270 | if session.findings.isEmpty { | 269 | if session.findings.isEmpty { |
| 271 | Text("No findings recorded.") | 270 | Text("No findings recorded.") |
| 272 | .font(appDensity.font(.caption)) | 271 | .font(appDensity.font(.caption)) |
| 273 | .foregroundStyle(.secondary) | 272 | .foregroundStyle(Color(.appTextSecondary)) |
| 274 | } else { | 273 | } else { |
| 275 | ForEach(session.findings) { finding in | 274 | ForEach(session.findings) { finding in |
| 276 | Button { | 275 | Button { |
| @@ -286,11 +285,11 @@ struct AuditSessionDetailView: View { | |||
| 286 | } | 285 | } |
| 287 | Text(finding.summary) | 286 | Text(finding.summary) |
| 288 | .font(appDensity.font(.caption)) | 287 | .font(appDensity.font(.caption)) |
| 289 | .foregroundStyle(.secondary) | 288 | .foregroundStyle(Color(.appTextSecondary)) |
| 290 | .lineLimit(2) | 289 | .lineLimit(2) |
| 291 | Text("\(finding.status.title) • \(finding.evidenceReferences.count) evidence refs") | 290 | Text("\(finding.status.title) • \(finding.evidenceReferences.count) evidence refs") |
| 292 | .font(appDensity.font(.caption2)) | 291 | .font(appDensity.font(.caption2)) |
| 293 | .foregroundStyle(.secondary) | 292 | .foregroundStyle(Color(.appTextSecondary)) |
| 294 | } | 293 | } |
| 295 | .padding(.vertical, 2) | 294 | .padding(.vertical, 2) |
| 296 | } | 295 | } |
| @@ -321,7 +320,7 @@ struct AuditSessionDetailView: View { | |||
| 321 | .font(appDensity.font(.caption, weight: .semibold)) | 320 | .font(appDensity.font(.caption, weight: .semibold)) |
| 322 | Text(entry.changeSummaryMessage ?? "No change summary") | 321 | Text(entry.changeSummaryMessage ?? "No change summary") |
| 323 | .font(appDensity.font(.caption2)) | 322 | .font(appDensity.font(.caption2)) |
| 324 | .foregroundStyle(.secondary) | 323 | .foregroundStyle(Color(.appTextSecondary)) |
| 325 | } | 324 | } |
| 326 | } | 325 | } |
| 327 | } | 326 | } |
| @@ -337,11 +336,11 @@ struct AuditSessionDetailView: View { | |||
| 337 | Spacer() | 336 | Spacer() |
| 338 | Text(point.status.title) | 337 | Text(point.status.title) |
| 339 | .font(appDensity.font(.caption2)) | 338 | .font(appDensity.font(.caption2)) |
| 340 | .foregroundStyle(.secondary) | 339 | .foregroundStyle(Color(.appTextSecondary)) |
| 341 | } | 340 | } |
| 342 | Text("Findings \(point.findingCount) • Open high \(point.openHighSeverityCount) • Repeated \(point.repeatedIssueCount)") | 341 | Text("Findings \(point.findingCount) • Open high \(point.openHighSeverityCount) • Repeated \(point.repeatedIssueCount)") |
| 343 | .font(appDensity.font(.caption2)) | 342 | .font(appDensity.font(.caption2)) |
| 344 | .foregroundStyle(.secondary) | 343 | .foregroundStyle(Color(.appTextSecondary)) |
| 345 | } | 344 | } |
| 346 | } | 345 | } |
| 347 | } | 346 | } |
| @@ -401,7 +400,6 @@ struct AuditSessionDetailView: View { | |||
| 401 | .background(Color(.appBackground)) | 400 | .background(Color(.appBackground)) |
| 402 | } | 401 | } |
| 403 | } | 402 | } |
| 404 | .preferredColorScheme(.dark) | ||
| 405 | } | 403 | } |
| 406 | } | 404 | } |
| 407 | 405 | ||
| @@ -459,7 +457,7 @@ private struct AuditFindingEditorView: View { | |||
| 459 | ForEach(session.evidence.screenshots) { asset in | 457 | ForEach(session.evidence.screenshots) { asset in |
| 460 | Text("\(asset.title): \(asset.reference)") | 458 | Text("\(asset.title): \(asset.reference)") |
| 461 | .font(.caption) | 459 | .font(.caption) |
| 462 | .foregroundStyle(.secondary) | 460 | .foregroundStyle(Color(.appTextSecondary)) |
| 463 | } | 461 | } |
| 464 | } | 462 | } |
| 465 | } | 463 | } |
DomainDig/DomainDigApp.swift +3
| @@ -12,6 +12,7 @@ struct DomainDigApp: App { | |||
| 12 | @UIApplicationDelegateAdaptor(DomainDigAppDelegate.self) private var appDelegate | 12 | @UIApplicationDelegateAdaptor(DomainDigAppDelegate.self) private var appDelegate |
| 13 | @Environment(\.scenePhase) private var scenePhase | 13 | @Environment(\.scenePhase) private var scenePhase |
| 14 | @AppStorage(AppDensity.userDefaultsKey) private var density = AppDensity.compact.rawValue | 14 | @AppStorage(AppDensity.userDefaultsKey) private var density = AppDensity.compact.rawValue |
| 15 | @AppStorage(AppAppearance.userDefaultsKey) private var appearance = AppAppearance.system.rawValue | ||
| 15 | @State private var viewModel = DomainViewModel() | 16 | @State private var viewModel = DomainViewModel() |
| 16 | @State private var purchaseService = PurchaseService.shared | 17 | @State private var purchaseService = PurchaseService.shared |
| 17 | @State private var cloudSyncService = CloudSyncService.shared | 18 | @State private var cloudSyncService = CloudSyncService.shared |
| @@ -27,6 +28,8 @@ struct DomainDigApp: App { | |||
| 27 | WindowGroup { | 28 | WindowGroup { |
| 28 | RootTabView(viewModel: viewModel) | 29 | RootTabView(viewModel: viewModel) |
| 29 | .environment(\.appDensity, AppDensity(rawValue: density) ?? .compact) | 30 | .environment(\.appDensity, AppDensity(rawValue: density) ?? .compact) |
| 31 | // The single place appearance is applied. Keep it that way. | ||
| 32 | .preferredColorScheme((AppAppearance(rawValue: appearance) ?? .system).colorScheme) | ||
| 30 | .task { | 33 | .task { |
| 31 | let _ = purchaseService.currentTier | 34 | let _ = purchaseService.currentTier |
| 32 | let _ = cloudSyncService.status | 35 | let _ = cloudSyncService.status |
DomainDig/DomainDigUI.swift +47 −8
| @@ -6,6 +6,45 @@ import UIKit | |||
| 6 | import AppKit | 6 | import AppKit |
| 7 | #endif | 7 | #endif |
| 8 | 8 | ||
| 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. | ||
| 15 | enum 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 | |||
| 9 | enum AppDensity: String, CaseIterable, Identifiable { | 48 | enum AppDensity: String, CaseIterable, Identifiable { |
| 10 | case compact | 49 | case compact |
| 11 | case comfortable | 50 | case comfortable |
| @@ -96,7 +135,7 @@ enum AppStatusFactory { | |||
| 96 | case .registered: | 135 | case .registered: |
| 97 | return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | 136 | return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) |
| 98 | case .unknown, .none: | 137 | 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)) |
| 100 | } | 139 | } |
| 101 | } | 140 | } |
| 102 | 141 | ||
| @@ -112,7 +151,7 @@ enum AppStatusFactory { | |||
| 112 | 151 | ||
| 113 | static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel { | 152 | static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel { |
| 114 | guard error == nil, let result else { | 153 | 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)) |
| 116 | } | 155 | } |
| 117 | 156 | ||
| 118 | let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count | 157 | let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count |
| @@ -122,18 +161,18 @@ enum AppStatusFactory { | |||
| 122 | case 1, 2: | 161 | case 1, 2: |
| 123 | return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) | 162 | return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: Color(.statusWarning), backgroundColor: Color(.statusWarning).opacity(0.16)) |
| 124 | default: | 163 | 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)) |
| 126 | } | 165 | } |
| 127 | } | 166 | } |
| 128 | 167 | ||
| 129 | static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel { | 168 | static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel { |
| 130 | guard let summary else { | 169 | 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)) |
| 132 | } | 171 | } |
| 133 | if summary.hasChanges { | 172 | if summary.hasChanges { |
| 134 | return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) | 173 | return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: Color(.statusInfo), backgroundColor: Color(.statusInfo).opacity(0.16)) |
| 135 | } | 174 | } |
| 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)) |
| 137 | } | 176 | } |
| 138 | } | 177 | } |
| 139 | 178 | ||
| @@ -259,7 +298,7 @@ struct EmptyStateCardView: View { | |||
| 259 | 298 | ||
| 260 | Text(message) | 299 | Text(message) |
| 261 | .font(appDensity.font(.body)) | 300 | .font(appDensity.font(.body)) |
| 262 | .foregroundStyle(.secondary) | 301 | .foregroundStyle(Color(.appTextSecondary)) |
| 263 | .fixedSize(horizontal: false, vertical: true) | 302 | .fixedSize(horizontal: false, vertical: true) |
| 264 | 303 | ||
| 265 | Text(suggestion) | 304 | Text(suggestion) |
| @@ -311,14 +350,14 @@ struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View { | |||
| 311 | if let subtitle { | 350 | if let subtitle { |
| 312 | Text(subtitle) | 351 | Text(subtitle) |
| 313 | .font(appDensity.font(.caption)) | 352 | .font(appDensity.font(.caption)) |
| 314 | .foregroundStyle(.secondary) | 353 | .foregroundStyle(Color(.appTextSecondary)) |
| 315 | } | 354 | } |
| 316 | } | 355 | } |
| 317 | Spacer(minLength: 8) | 356 | Spacer(minLength: 8) |
| 318 | trailing() | 357 | trailing() |
| 319 | Image(systemName: isCollapsed ? "chevron.down" : "chevron.up") | 358 | Image(systemName: isCollapsed ? "chevron.down" : "chevron.up") |
| 320 | .font(.caption.weight(.semibold)) | 359 | .font(.caption.weight(.semibold)) |
| 321 | .foregroundStyle(.secondary) | 360 | .foregroundStyle(Color(.appTextSecondary)) |
| 322 | } | 361 | } |
| 323 | .contentShape(Rectangle()) | 362 | .contentShape(Rectangle()) |
| 324 | .frame(minHeight: appDensity.metrics.controlMinHeight, alignment: .center) | 363 | .frame(minHeight: appDensity.metrics.controlMinHeight, alignment: .center) |
DomainDig/HistoryView.swift +5 −7
| @@ -40,12 +40,12 @@ struct HistoryView: View { | |||
| 40 | Spacer() | 40 | Spacer() |
| 41 | Text("\(item.count) snapshots") | 41 | Text("\(item.count) snapshots") |
| 42 | .font(appDensity.font(.caption2)) | 42 | .font(appDensity.font(.caption2)) |
| 43 | .foregroundStyle(.secondary) | 43 | .foregroundStyle(Color(.appTextSecondary)) |
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | Text(item.latest.changeSummaryMessage ?? "No change summary") | 46 | Text(item.latest.changeSummaryMessage ?? "No change summary") |
| 47 | .font(appDensity.font(.caption)) | 47 | .font(appDensity.font(.caption)) |
| 48 | .foregroundStyle(.secondary) | 48 | .foregroundStyle(Color(.appTextSecondary)) |
| 49 | .lineLimit(2) | 49 | .lineLimit(2) |
| 50 | 50 | ||
| 51 | HStack(spacing: 8) { | 51 | HStack(spacing: 8) { |
| @@ -129,7 +129,6 @@ struct HistoryView: View { | |||
| 129 | .onChange(of: viewModel.rerunNavigationToken) { _, _ in | 129 | .onChange(of: viewModel.rerunNavigationToken) { _, _ in |
| 130 | dismiss() | 130 | dismiss() |
| 131 | } | 131 | } |
| 132 | .preferredColorScheme(.dark) | ||
| 133 | } | 132 | } |
| 134 | 133 | ||
| 135 | private func deleteFilteredHistoryEntries(at offsets: IndexSet) { | 134 | private func deleteFilteredHistoryEntries(at offsets: IndexSet) { |
| @@ -372,7 +371,6 @@ struct HistoryDetailView: View { | |||
| 372 | } | 371 | } |
| 373 | } | 372 | } |
| 374 | } | 373 | } |
| 375 | .preferredColorScheme(.dark) | ||
| 376 | } | 374 | } |
| 377 | 375 | ||
| 378 | private var snapshotBanner: some View { | 376 | private var snapshotBanner: some View { |
| @@ -385,7 +383,7 @@ struct HistoryDetailView: View { | |||
| 385 | Spacer() | 383 | Spacer() |
| 386 | Text("Live re-run available") | 384 | Text("Live re-run available") |
| 387 | .font(appDensity.font(.caption2)) | 385 | .font(appDensity.font(.caption2)) |
| 388 | .foregroundStyle(.secondary) | 386 | .foregroundStyle(Color(.appTextSecondary)) |
| 389 | } | 387 | } |
| 390 | if let mismatchNote = viewModel.resolverMismatchNote(for: entry) { | 388 | if let mismatchNote = viewModel.resolverMismatchNote(for: entry) { |
| 391 | Text(mismatchNote) | 389 | Text(mismatchNote) |
| @@ -400,10 +398,10 @@ struct HistoryDetailView: View { | |||
| 400 | if let note = entry.note, !note.isEmpty { | 398 | if let note = entry.note, !note.isEmpty { |
| 401 | Text(note) | 399 | Text(note) |
| 402 | .font(appDensity.font(.caption2)) | 400 | .font(appDensity.font(.caption2)) |
| 403 | .foregroundStyle(.secondary) | 401 | .foregroundStyle(Color(.appTextSecondary)) |
| 404 | } | 402 | } |
| 405 | } | 403 | } |
| 406 | .foregroundStyle(.secondary) | 404 | .foregroundStyle(Color(.appTextSecondary)) |
| 407 | .padding(8) | 405 | .padding(8) |
| 408 | .frame(maxWidth: .infinity, alignment: .leading) | 406 | .frame(maxWidth: .infinity, alignment: .leading) |
| 409 | .background(Color(.appSurface)) | 407 | .background(Color(.appSurface)) |
DomainDig/IntegrationsView.swift +11 −11
| @@ -15,7 +15,7 @@ struct IntegrationsSettingsView: View { | |||
| 15 | if let statusMessage = integrationService.statusMessage { | 15 | if let statusMessage = integrationService.statusMessage { |
| 16 | Text(statusMessage) | 16 | Text(statusMessage) |
| 17 | .font(.caption) | 17 | .font(.caption) |
| 18 | .foregroundStyle(.secondary) | 18 | .foregroundStyle(Color(.appTextSecondary)) |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | Button("Process Queue Now") { | 21 | Button("Process Queue Now") { |
| @@ -26,7 +26,7 @@ struct IntegrationsSettingsView: View { | |||
| 26 | Section("Targets") { | 26 | Section("Targets") { |
| 27 | if integrationService.targets.isEmpty { | 27 | if integrationService.targets.isEmpty { |
| 28 | Text("No integrations configured.") | 28 | Text("No integrations configured.") |
| 29 | .foregroundStyle(.secondary) | 29 | .foregroundStyle(Color(.appTextSecondary)) |
| 30 | } else { | 30 | } else { |
| 31 | ForEach(integrationService.targets) { target in | 31 | ForEach(integrationService.targets) { target in |
| 32 | NavigationLink { | 32 | NavigationLink { |
| @@ -42,12 +42,12 @@ struct IntegrationsSettingsView: View { | |||
| 42 | Text(target.name) | 42 | Text(target.name) |
| 43 | Spacer() | 43 | Spacer() |
| 44 | Text(target.type.title) | 44 | Text(target.type.title) |
| 45 | .foregroundStyle(.secondary) | 45 | .foregroundStyle(Color(.appTextSecondary)) |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | Text(summary(for: target)) | 48 | Text(summary(for: target)) |
| 49 | .font(.caption) | 49 | .font(.caption) |
| 50 | .foregroundStyle(.secondary) | 50 | .foregroundStyle(Color(.appTextSecondary)) |
| 51 | 51 | ||
| 52 | if !target.isEnabled { | 52 | if !target.isEnabled { |
| 53 | Text("Disabled") | 53 | Text("Disabled") |
| @@ -138,7 +138,7 @@ private struct IntegrationDetailView: View { | |||
| 138 | Section("Delivery Log") { | 138 | Section("Delivery Log") { |
| 139 | if integrationService.deliveryRecords(for: target.id).isEmpty { | 139 | if integrationService.deliveryRecords(for: target.id).isEmpty { |
| 140 | Text("No deliveries yet.") | 140 | Text("No deliveries yet.") |
| 141 | .foregroundStyle(.secondary) | 141 | .foregroundStyle(Color(.appTextSecondary)) |
| 142 | } else { | 142 | } else { |
| 143 | ForEach(integrationService.deliveryRecords(for: target.id), id: \.id) { record in | 143 | ForEach(integrationService.deliveryRecords(for: target.id), id: \.id) { record in |
| 144 | VStack(alignment: .leading, spacing: 4) { | 144 | VStack(alignment: .leading, spacing: 4) { |
| @@ -147,7 +147,7 @@ private struct IntegrationDetailView: View { | |||
| 147 | Spacer() | 147 | Spacer() |
| 148 | Text(record.timestamp.formatted(date: .abbreviated, time: .shortened)) | 148 | Text(record.timestamp.formatted(date: .abbreviated, time: .shortened)) |
| 149 | .font(.caption) | 149 | .font(.caption) |
| 150 | .foregroundStyle(.secondary) | 150 | .foregroundStyle(Color(.appTextSecondary)) |
| 151 | } | 151 | } |
| 152 | 152 | ||
| 153 | Text(record.summary) | 153 | Text(record.summary) |
| @@ -155,7 +155,7 @@ private struct IntegrationDetailView: View { | |||
| 155 | 155 | ||
| 156 | Text(record.destination) | 156 | Text(record.destination) |
| 157 | .font(.caption) | 157 | .font(.caption) |
| 158 | .foregroundStyle(.secondary) | 158 | .foregroundStyle(Color(.appTextSecondary)) |
| 159 | 159 | ||
| 160 | if let failureReason = record.failureReason { | 160 | if let failureReason = record.failureReason { |
| 161 | let failureColor: Color = record.status == .skipped ? .secondary : Color(.statusCritical) | 161 | let failureColor: Color = record.status == .skipped ? .secondary : Color(.statusCritical) |
| @@ -169,7 +169,7 @@ private struct IntegrationDetailView: View { | |||
| 169 | } | 169 | } |
| 170 | } else { | 170 | } else { |
| 171 | Text("Integration not found.") | 171 | Text("Integration not found.") |
| 172 | .foregroundStyle(.secondary) | 172 | .foregroundStyle(Color(.appTextSecondary)) |
| 173 | } | 173 | } |
| 174 | } | 174 | } |
| 175 | .navigationTitle(target?.name ?? "Integration") | 175 | .navigationTitle(target?.name ?? "Integration") |
| @@ -265,7 +265,7 @@ private struct IntegrationEditorView: View { | |||
| 265 | if existingTarget != nil { | 265 | if existingTarget != nil { |
| 266 | Text("Saved webhook URL remains in Keychain unless you replace it.") | 266 | Text("Saved webhook URL remains in Keychain unless you replace it.") |
| 267 | .font(.caption) | 267 | .font(.caption) |
| 268 | .foregroundStyle(.secondary) | 268 | .foregroundStyle(Color(.appTextSecondary)) |
| 269 | } | 269 | } |
| 270 | } | 270 | } |
| 271 | case .slack: | 271 | case .slack: |
| @@ -278,7 +278,7 @@ private struct IntegrationEditorView: View { | |||
| 278 | if existingTarget != nil { | 278 | if existingTarget != nil { |
| 279 | Text("Saved Slack webhook remains in Keychain unless you replace it.") | 279 | Text("Saved Slack webhook remains in Keychain unless you replace it.") |
| 280 | .font(.caption) | 280 | .font(.caption) |
| 281 | .foregroundStyle(.secondary) | 281 | .foregroundStyle(Color(.appTextSecondary)) |
| 282 | } | 282 | } |
| 283 | } | 283 | } |
| 284 | case .email: | 284 | case .email: |
| @@ -315,7 +315,7 @@ private struct IntegrationEditorView: View { | |||
| 315 | if existingTarget != nil { | 315 | if existingTarget != nil { |
| 316 | Text("Saved SMTP password remains in Keychain unless you replace it.") | 316 | Text("Saved SMTP password remains in Keychain unless you replace it.") |
| 317 | .font(.caption) | 317 | .font(.caption) |
| 318 | .foregroundStyle(.secondary) | 318 | .foregroundStyle(Color(.appTextSecondary)) |
| 319 | } | 319 | } |
| 320 | } | 320 | } |
| 321 | } | 321 | } |
DomainDig/MonitoringView.swift +6 −8
| @@ -31,7 +31,7 @@ struct MonitoringView: View { | |||
| 31 | !monitoringStatusMessage.isEmpty { | 31 | !monitoringStatusMessage.isEmpty { |
| 32 | Text(monitoringStatusMessage) | 32 | Text(monitoringStatusMessage) |
| 33 | .font(appDensity.font(.caption)) | 33 | .font(appDensity.font(.caption)) |
| 34 | .foregroundStyle(.secondary) | 34 | .foregroundStyle(Color(.appTextSecondary)) |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | Button(viewModel.monitoringRunInProgress ? "Monitoring…" : "Run Now") { | 37 | Button(viewModel.monitoringRunInProgress ? "Monitoring…" : "Run Now") { |
| @@ -68,12 +68,12 @@ struct MonitoringView: View { | |||
| 68 | Spacer() | 68 | Spacer() |
| 69 | Text(log.timestamp.formatted(date: .abbreviated, time: .shortened)) | 69 | Text(log.timestamp.formatted(date: .abbreviated, time: .shortened)) |
| 70 | .font(appDensity.font(.caption2)) | 70 | .font(appDensity.font(.caption2)) |
| 71 | .foregroundStyle(.secondary) | 71 | .foregroundStyle(Color(.appTextSecondary)) |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | Text(log.summary) | 74 | Text(log.summary) |
| 75 | .font(appDensity.font(.caption)) | 75 | .font(appDensity.font(.caption)) |
| 76 | .foregroundStyle(.secondary) | 76 | .foregroundStyle(Color(.appTextSecondary)) |
| 77 | 77 | ||
| 78 | HStack(spacing: 8) { | 78 | HStack(spacing: 8) { |
| 79 | metricBadge(title: "\(log.domainsChecked) checked") | 79 | metricBadge(title: "\(log.domainsChecked) checked") |
| @@ -95,7 +95,6 @@ struct MonitoringView: View { | |||
| 95 | .scrollContentBackground(.hidden) | 95 | .scrollContentBackground(.hidden) |
| 96 | .background(Color(.appBackground)) | 96 | .background(Color(.appBackground)) |
| 97 | .navigationTitle("Monitoring") | 97 | .navigationTitle("Monitoring") |
| 98 | .preferredColorScheme(.dark) | ||
| 99 | .onAppear { | 98 | .onAppear { |
| 100 | viewModel.refreshMonitoringState() | 99 | viewModel.refreshMonitoringState() |
| 101 | } | 100 | } |
| @@ -152,7 +151,7 @@ struct MonitoringLogDetailView: View { | |||
| 152 | 151 | ||
| 153 | Text(result.summaryMessage) | 152 | Text(result.summaryMessage) |
| 154 | .font(appDensity.font(.caption)) | 153 | .font(appDensity.font(.caption)) |
| 155 | .foregroundStyle(.secondary) | 154 | .foregroundStyle(Color(.appTextSecondary)) |
| 156 | 155 | ||
| 157 | HStack(spacing: 10) { | 156 | HStack(spacing: 10) { |
| 158 | Text(result.resultSource.label) | 157 | Text(result.resultSource.label) |
| @@ -162,7 +161,7 @@ struct MonitoringLogDetailView: View { | |||
| 162 | } | 161 | } |
| 163 | } | 162 | } |
| 164 | .font(appDensity.font(.caption2)) | 163 | .font(appDensity.font(.caption2)) |
| 165 | .foregroundStyle(.secondary) | 164 | .foregroundStyle(Color(.appTextSecondary)) |
| 166 | 165 | ||
| 167 | if let errorMessage = result.errorMessage { | 166 | if let errorMessage = result.errorMessage { |
| 168 | Text(errorMessage) | 167 | Text(errorMessage) |
| @@ -188,7 +187,7 @@ struct MonitoringLogDetailView: View { | |||
| 188 | ForEach(log.errors, id: \.self) { error in | 187 | ForEach(log.errors, id: \.self) { error in |
| 189 | Text(error) | 188 | Text(error) |
| 190 | .font(appDensity.font(.caption)) | 189 | .font(appDensity.font(.caption)) |
| 191 | .foregroundStyle(.secondary) | 190 | .foregroundStyle(Color(.appTextSecondary)) |
| 192 | } | 191 | } |
| 193 | } | 192 | } |
| 194 | .listRowBackground(Color(.appSurface)) | 193 | .listRowBackground(Color(.appSurface)) |
| @@ -197,7 +196,6 @@ struct MonitoringLogDetailView: View { | |||
| 197 | .scrollContentBackground(.hidden) | 196 | .scrollContentBackground(.hidden) |
| 198 | .background(Color(.appBackground)) | 197 | .background(Color(.appBackground)) |
| 199 | .navigationTitle("Run Details") | 198 | .navigationTitle("Run Details") |
| 200 | .preferredColorScheme(.dark) | ||
| 201 | } | 199 | } |
| 202 | 200 | ||
| 203 | private func color(for severity: MonitoringAlertSeverity) -> Color { | 201 | private func color(for severity: MonitoringAlertSeverity) -> Color { |
DomainDig/PaywallView.swift +4 −4
| @@ -12,7 +12,7 @@ struct PaywallView: View { | |||
| 12 | Section { | 12 | Section { |
| 13 | Text("Pro unlocks workflows, scale, monitoring automation, and exports. Pro+ adds deeper external intelligence with no additional usage limits.") | 13 | Text("Pro unlocks workflows, scale, monitoring automation, and exports. Pro+ adds deeper external intelligence with no additional usage limits.") |
| 14 | .font(appDensity.font(.body, design: .default)) | 14 | .font(appDensity.font(.body, design: .default)) |
| 15 | .foregroundStyle(.secondary) | 15 | .foregroundStyle(Color(.appTextSecondary)) |
| 16 | .fixedSize(horizontal: false, vertical: true) | 16 | .fixedSize(horizontal: false, vertical: true) |
| 17 | } | 17 | } |
| 18 | 18 | ||
| @@ -38,7 +38,7 @@ struct PaywallView: View { | |||
| 38 | } else if purchaseService.products.isEmpty { | 38 | } else if purchaseService.products.isEmpty { |
| 39 | Text("Pricing is unavailable right now.") | 39 | Text("Pricing is unavailable right now.") |
| 40 | .font(appDensity.font(.caption, design: .default)) | 40 | .font(appDensity.font(.caption, design: .default)) |
| 41 | .foregroundStyle(.secondary) | 41 | .foregroundStyle(Color(.appTextSecondary)) |
| 42 | 42 | ||
| 43 | Button("Retry") { | 43 | Button("Retry") { |
| 44 | Task { | 44 | Task { |
| @@ -58,7 +58,7 @@ struct PaywallView: View { | |||
| 58 | .foregroundStyle(.primary) | 58 | .foregroundStyle(.primary) |
| 59 | Text(product.displayPrice) | 59 | Text(product.displayPrice) |
| 60 | .font(appDensity.font(.callout, design: .default)) | 60 | .font(appDensity.font(.callout, design: .default)) |
| 61 | .foregroundStyle(.secondary) | 61 | .foregroundStyle(Color(.appTextSecondary)) |
| 62 | } | 62 | } |
| 63 | .frame(maxWidth: .infinity, alignment: .leading) | 63 | .frame(maxWidth: .infinity, alignment: .leading) |
| 64 | } | 64 | } |
| @@ -69,7 +69,7 @@ struct PaywallView: View { | |||
| 69 | if let statusMessage = purchaseService.statusMessage { | 69 | if let statusMessage = purchaseService.statusMessage { |
| 70 | Text(statusMessage) | 70 | Text(statusMessage) |
| 71 | .font(appDensity.font(.caption, design: .default)) | 71 | .font(appDensity.font(.caption, design: .default)) |
| 72 | .foregroundStyle(.secondary) | 72 | .foregroundStyle(Color(.appTextSecondary)) |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | if let errorMessage = purchaseService.errorMessage { | 75 | if let errorMessage = purchaseService.errorMessage { |
DomainDig/SavedDomainsView.swift +1 −2
| @@ -9,7 +9,7 @@ struct SavedDomainsView: View { | |||
| 9 | if viewModel.savedDomains.isEmpty { | 9 | if viewModel.savedDomains.isEmpty { |
| 10 | Text("No saved domains") | 10 | Text("No saved domains") |
| 11 | .font(.system(.callout, design: .monospaced)) | 11 | .font(.system(.callout, design: .monospaced)) |
| 12 | .foregroundStyle(.secondary) | 12 | .foregroundStyle(Color(.appTextSecondary)) |
| 13 | .listRowBackground(Color(.appSurface)) | 13 | .listRowBackground(Color(.appSurface)) |
| 14 | } else { | 14 | } else { |
| 15 | ForEach(viewModel.savedDomains, id: \.self) { domain in | 15 | ForEach(viewModel.savedDomains, id: \.self) { domain in |
| @@ -37,6 +37,5 @@ struct SavedDomainsView: View { | |||
| 37 | EditButton() | 37 | EditButton() |
| 38 | } | 38 | } |
| 39 | } | 39 | } |
| 40 | .preferredColorScheme(.dark) | ||
| 41 | } | 40 | } |
| 42 | } | 41 | } |
DomainDig/ScheduledReportsView.swift +3 −3
| @@ -13,7 +13,7 @@ struct ScheduledReportsView: View { | |||
| 13 | if !FeatureAccessService.hasAccess(to: .automatedMonitoring) { | 13 | if !FeatureAccessService.hasAccess(to: .automatedMonitoring) { |
| 14 | Text("Scheduled reports require Pro.") | 14 | Text("Scheduled reports require Pro.") |
| 15 | .font(appDensity.font(.caption)) | 15 | .font(appDensity.font(.caption)) |
| 16 | .foregroundStyle(.secondary) | 16 | .foregroundStyle(Color(.appTextSecondary)) |
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | Toggle("Scheduled Reports", isOn: Binding( | 19 | Toggle("Scheduled Reports", isOn: Binding( |
| @@ -59,7 +59,7 @@ struct ScheduledReportsView: View { | |||
| 59 | if let statusMessage { | 59 | if let statusMessage { |
| 60 | Text(statusMessage) | 60 | Text(statusMessage) |
| 61 | .font(appDensity.font(.caption)) | 61 | .font(appDensity.font(.caption)) |
| 62 | .foregroundStyle(.secondary) | 62 | .foregroundStyle(Color(.appTextSecondary)) |
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | Button(isGenerating ? "Generating…" : "Generate Now") { | 65 | Button(isGenerating ? "Generating…" : "Generate Now") { |
| @@ -91,7 +91,7 @@ struct ScheduledReportsView: View { | |||
| 91 | .foregroundStyle(.primary) | 91 | .foregroundStyle(.primary) |
| 92 | Text(log.generatedAt.formatted(date: .abbreviated, time: .shortened)) | 92 | Text(log.generatedAt.formatted(date: .abbreviated, time: .shortened)) |
| 93 | .font(.caption) | 93 | .font(.caption) |
| 94 | .foregroundStyle(.secondary) | 94 | .foregroundStyle(Color(.appTextSecondary)) |
| 95 | } | 95 | } |
| 96 | } | 96 | } |
| 97 | } | 97 | } |
DomainDig/TimelineView.swift +4 −4
| @@ -125,7 +125,7 @@ private struct TimelineRow: View { | |||
| 125 | 125 | ||
| 126 | Text(summary.changeSummaryMessage ?? "No change summary") | 126 | Text(summary.changeSummaryMessage ?? "No change summary") |
| 127 | .font(appDensity.font(.caption)) | 127 | .font(appDensity.font(.caption)) |
| 128 | .foregroundStyle(.secondary) | 128 | .foregroundStyle(Color(.appTextSecondary)) |
| 129 | .lineLimit(2) | 129 | .lineLimit(2) |
| 130 | 130 | ||
| 131 | HStack(spacing: 8) { | 131 | HStack(spacing: 8) { |
| @@ -137,14 +137,14 @@ private struct TimelineRow: View { | |||
| 137 | } | 137 | } |
| 138 | } | 138 | } |
| 139 | .font(appDensity.font(.caption2)) | 139 | .font(appDensity.font(.caption2)) |
| 140 | .foregroundStyle(.secondary) | 140 | .foregroundStyle(Color(.appTextSecondary)) |
| 141 | 141 | ||
| 142 | if !entry.intelligenceTimeline.isEmpty { | 142 | if !entry.intelligenceTimeline.isEmpty { |
| 143 | VStack(alignment: .leading, spacing: 4) { | 143 | VStack(alignment: .leading, spacing: 4) { |
| 144 | ForEach(Array(entry.intelligenceTimeline.prefix(2))) { event in | 144 | ForEach(Array(entry.intelligenceTimeline.prefix(2))) { event in |
| 145 | Text("\(event.title): \(event.detail)") | 145 | Text("\(event.title): \(event.detail)") |
| 146 | .font(appDensity.font(.caption2)) | 146 | .font(appDensity.font(.caption2)) |
| 147 | .foregroundStyle(.secondary) | 147 | .foregroundStyle(Color(.appTextSecondary)) |
| 148 | .lineLimit(1) | 148 | .lineLimit(1) |
| 149 | } | 149 | } |
| 150 | } | 150 | } |
| @@ -161,7 +161,7 @@ private struct TimelineRow: View { | |||
| 161 | .lineLimit(1) | 161 | .lineLimit(1) |
| 162 | } | 162 | } |
| 163 | .font(appDensity.font(.caption2)) | 163 | .font(appDensity.font(.caption2)) |
| 164 | .foregroundStyle(.secondary) | 164 | .foregroundStyle(Color(.appTextSecondary)) |
| 165 | } | 165 | } |
| 166 | } | 166 | } |
| 167 | } | 167 | } |
DomainDig/WatchlistView.swift +12 −14
| @@ -42,7 +42,7 @@ struct WatchlistView: View { | |||
| 42 | HStack { | 42 | HStack { |
| 43 | Text(viewModel.batchProgressLabel) | 43 | Text(viewModel.batchProgressLabel) |
| 44 | .font(appDensity.font(.caption)) | 44 | .font(appDensity.font(.caption)) |
| 45 | .foregroundStyle(.secondary) | 45 | .foregroundStyle(Color(.appTextSecondary)) |
| 46 | Spacer() | 46 | Spacer() |
| 47 | if viewModel.batchLookupRunning { | 47 | if viewModel.batchLookupRunning { |
| 48 | Button("Cancel") { | 48 | Button("Cancel") { |
| @@ -78,7 +78,7 @@ struct WatchlistView: View { | |||
| 78 | Section { | 78 | Section { |
| 79 | Text(limitMessage) | 79 | Text(limitMessage) |
| 80 | .font(appDensity.font(.caption)) | 80 | .font(appDensity.font(.caption)) |
| 81 | .foregroundStyle(.secondary) | 81 | .foregroundStyle(Color(.appTextSecondary)) |
| 82 | } | 82 | } |
| 83 | .listRowBackground(Color(.appSurface)) | 83 | .listRowBackground(Color(.appSurface)) |
| 84 | } | 84 | } |
| @@ -208,7 +208,7 @@ struct WatchlistView: View { | |||
| 208 | Section { | 208 | Section { |
| 209 | Text("Adds the domain directly to your watchlist so monitoring can run without a prior inspection.") | 209 | Text("Adds the domain directly to your watchlist so monitoring can run without a prior inspection.") |
| 210 | .font(appDensity.font(.caption)) | 210 | .font(appDensity.font(.caption)) |
| 211 | .foregroundStyle(.secondary) | 211 | .foregroundStyle(Color(.appTextSecondary)) |
| 212 | } | 212 | } |
| 213 | 213 | ||
| 214 | if let addDomainError { | 214 | if let addDomainError { |
| @@ -267,7 +267,7 @@ struct WatchlistView: View { | |||
| 267 | .foregroundStyle(.primary) | 267 | .foregroundStyle(.primary) |
| 268 | Text([view.tag, view.filter.title, view.sort.title].compactMap { $0 }.joined(separator: " • ")) | 268 | Text([view.tag, view.filter.title, view.sort.title].compactMap { $0 }.joined(separator: " • ")) |
| 269 | .font(.caption) | 269 | .font(.caption) |
| 270 | .foregroundStyle(.secondary) | 270 | .foregroundStyle(Color(.appTextSecondary)) |
| 271 | } | 271 | } |
| 272 | } | 272 | } |
| 273 | } | 273 | } |
| @@ -288,7 +288,6 @@ struct WatchlistView: View { | |||
| 288 | } | 288 | } |
| 289 | } | 289 | } |
| 290 | } | 290 | } |
| 291 | .preferredColorScheme(.dark) | ||
| 292 | } | 291 | } |
| 293 | 292 | ||
| 294 | @ViewBuilder | 293 | @ViewBuilder |
| @@ -450,12 +449,12 @@ struct WatchlistRowView: View { | |||
| 450 | 449 | ||
| 451 | Text("Updated \(trackedDomain.updatedAt.formatted(date: .abbreviated, time: .shortened))") | 450 | Text("Updated \(trackedDomain.updatedAt.formatted(date: .abbreviated, time: .shortened))") |
| 452 | .font(appDensity.font(.caption2)) | 451 | .font(appDensity.font(.caption2)) |
| 453 | .foregroundStyle(.secondary) | 452 | .foregroundStyle(Color(.appTextSecondary)) |
| 454 | 453 | ||
| 455 | if let collaboration = trackedDomain.collaboration, collaboration.isShared { | 454 | if let collaboration = trackedDomain.collaboration, collaboration.isShared { |
| 456 | Text("\(collaboration.ownership.title) • \(collaboration.permission.title)") | 455 | Text("\(collaboration.ownership.title) • \(collaboration.permission.title)") |
| 457 | .font(appDensity.font(.caption2)) | 456 | .font(appDensity.font(.caption2)) |
| 458 | .foregroundStyle(.secondary) | 457 | .foregroundStyle(Color(.appTextSecondary)) |
| 459 | } | 458 | } |
| 460 | 459 | ||
| 461 | HStack(spacing: 8) { | 460 | HStack(spacing: 8) { |
| @@ -468,19 +467,19 @@ struct WatchlistRowView: View { | |||
| 468 | } | 467 | } |
| 469 | } | 468 | } |
| 470 | .font(appDensity.font(.caption2)) | 469 | .font(appDensity.font(.caption2)) |
| 471 | .foregroundStyle(.secondary) | 470 | .foregroundStyle(Color(.appTextSecondary)) |
| 472 | 471 | ||
| 473 | indicatorRow | 472 | indicatorRow |
| 474 | 473 | ||
| 475 | if let note = trackedDomain.note?.trimmingCharacters(in: .whitespacesAndNewlines), !note.isEmpty { | 474 | if let note = trackedDomain.note?.trimmingCharacters(in: .whitespacesAndNewlines), !note.isEmpty { |
| 476 | Text(note) | 475 | Text(note) |
| 477 | .font(appDensity.font(.caption)) | 476 | .font(appDensity.font(.caption)) |
| 478 | .foregroundStyle(.secondary) | 477 | .foregroundStyle(Color(.appTextSecondary)) |
| 479 | .lineLimit(2) | 478 | .lineLimit(2) |
| 480 | } else if let summary = trackedDomain.lastChangeSummary { | 479 | } else if let summary = trackedDomain.lastChangeSummary { |
| 481 | Text(summary.message) | 480 | Text(summary.message) |
| 482 | .font(appDensity.font(.caption)) | 481 | .font(appDensity.font(.caption)) |
| 483 | .foregroundStyle(.secondary) | 482 | .foregroundStyle(Color(.appTextSecondary)) |
| 484 | .lineLimit(2) | 483 | .lineLimit(2) |
| 485 | } | 484 | } |
| 486 | } | 485 | } |
| @@ -502,7 +501,7 @@ struct WatchlistRowView: View { | |||
| 502 | @ViewBuilder | 501 | @ViewBuilder |
| 503 | private var statusBadge: some View { | 502 | private var statusBadge: some View { |
| 504 | if isRefreshing { | 503 | 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))) |
| 506 | } else { | 505 | } else { |
| 507 | AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability)) | 506 | AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability)) |
| 508 | } | 507 | } |
| @@ -700,7 +699,7 @@ struct TrackedDomainDetailView: View { | |||
| 700 | if latestSnapshots.isEmpty { | 699 | if latestSnapshots.isEmpty { |
| 701 | Text("No snapshots yet") | 700 | Text("No snapshots yet") |
| 702 | .font(.system(.caption, design: .monospaced)) | 701 | .font(.system(.caption, design: .monospaced)) |
| 703 | .foregroundStyle(.secondary) | 702 | .foregroundStyle(Color(.appTextSecondary)) |
| 704 | } else { | 703 | } else { |
| 705 | ForEach(latestSnapshots) { entry in | 704 | ForEach(latestSnapshots) { entry in |
| 706 | NavigationLink { | 705 | NavigationLink { |
| @@ -712,7 +711,7 @@ struct TrackedDomainDetailView: View { | |||
| 712 | .foregroundStyle(.primary) | 711 | .foregroundStyle(.primary) |
| 713 | Text(entry.changeSummary?.hasChanges == true ? "Changed" : "Snapshot") | 712 | Text(entry.changeSummary?.hasChanges == true ? "Changed" : "Snapshot") |
| 714 | .font(.system(.caption2, design: .monospaced)) | 713 | .font(.system(.caption2, design: .monospaced)) |
| 715 | .foregroundStyle(.secondary) | 714 | .foregroundStyle(Color(.appTextSecondary)) |
| 716 | } | 715 | } |
| 717 | } | 716 | } |
| 718 | } | 717 | } |
| @@ -723,7 +722,6 @@ struct TrackedDomainDetailView: View { | |||
| 723 | .scrollContentBackground(.hidden) | 722 | .scrollContentBackground(.hidden) |
| 724 | .background(Color(.appBackground)) | 723 | .background(Color(.appBackground)) |
| 725 | .navigationTitle(liveTrackedDomain.domain) | 724 | .navigationTitle(liveTrackedDomain.domain) |
| 726 | .preferredColorScheme(.dark) | ||
| 727 | .onChange(of: viewModel.rerunNavigationToken) { _, _ in | 725 | .onChange(of: viewModel.rerunNavigationToken) { _, _ in |
| 728 | dismiss() | 726 | dismiss() |
| 729 | } | 727 | } |
DomainDig/WorkflowsView.swift +16 −21
| @@ -22,7 +22,7 @@ struct WorkflowsView: View { | |||
| 22 | Section { | 22 | Section { |
| 23 | Text(limitMessage) | 23 | Text(limitMessage) |
| 24 | .font(appDensity.font(.caption)) | 24 | .font(appDensity.font(.caption)) |
| 25 | .foregroundStyle(.secondary) | 25 | .foregroundStyle(Color(.appTextSecondary)) |
| 26 | } | 26 | } |
| 27 | .listRowBackground(Color(.appSurface)) | 27 | .listRowBackground(Color(.appSurface)) |
| 28 | } | 28 | } |
| @@ -54,7 +54,6 @@ struct WorkflowsView: View { | |||
| 54 | .sheet(item: workflowSummaryBinding) { summary in | 54 | .sheet(item: workflowSummaryBinding) { summary in |
| 55 | WorkflowRunSummaryView(viewModel: viewModel, summary: summary) | 55 | WorkflowRunSummaryView(viewModel: viewModel, summary: summary) |
| 56 | } | 56 | } |
| 57 | .preferredColorScheme(.dark) | ||
| 58 | } | 57 | } |
| 59 | 58 | ||
| 60 | private var workflowSummaryBinding: Binding<WorkflowRunSummary?> { | 59 | private var workflowSummaryBinding: Binding<WorkflowRunSummary?> { |
| @@ -78,7 +77,7 @@ struct WorkflowsView: View { | |||
| 78 | HStack { | 77 | HStack { |
| 79 | Text(viewModel.batchProgressLabel) | 78 | Text(viewModel.batchProgressLabel) |
| 80 | .font(appDensity.font(.caption)) | 79 | .font(appDensity.font(.caption)) |
| 81 | .foregroundStyle(.secondary) | 80 | .foregroundStyle(Color(.appTextSecondary)) |
| 82 | Spacer() | 81 | Spacer() |
| 83 | if viewModel.batchLookupRunning { | 82 | if viewModel.batchLookupRunning { |
| 84 | Button("Cancel") { | 83 | Button("Cancel") { |
| @@ -142,23 +141,23 @@ private struct WorkflowRowView: View { | |||
| 142 | Spacer(minLength: 8) | 141 | Spacer(minLength: 8) |
| 143 | Text("\(workflow.domains.count) domains") | 142 | Text("\(workflow.domains.count) domains") |
| 144 | .font(appDensity.font(.caption2)) | 143 | .font(appDensity.font(.caption2)) |
| 145 | .foregroundStyle(.secondary) | 144 | .foregroundStyle(Color(.appTextSecondary)) |
| 146 | } | 145 | } |
| 147 | 146 | ||
| 148 | Text("Updated \(workflow.updatedAt.formatted(date: .abbreviated, time: .shortened))") | 147 | Text("Updated \(workflow.updatedAt.formatted(date: .abbreviated, time: .shortened))") |
| 149 | .font(appDensity.font(.caption2)) | 148 | .font(appDensity.font(.caption2)) |
| 150 | .foregroundStyle(.secondary) | 149 | .foregroundStyle(Color(.appTextSecondary)) |
| 151 | 150 | ||
| 152 | if let collaboration = workflow.collaboration, collaboration.isShared { | 151 | if let collaboration = workflow.collaboration, collaboration.isShared { |
| 153 | Text("\(collaboration.ownership.title) • \(collaboration.permission.title)") | 152 | Text("\(collaboration.ownership.title) • \(collaboration.permission.title)") |
| 154 | .font(appDensity.font(.caption2)) | 153 | .font(appDensity.font(.caption2)) |
| 155 | .foregroundStyle(.secondary) | 154 | .foregroundStyle(Color(.appTextSecondary)) |
| 156 | } | 155 | } |
| 157 | 156 | ||
| 158 | if let notes = workflow.notes, !notes.isEmpty { | 157 | if let notes = workflow.notes, !notes.isEmpty { |
| 159 | Text(notes) | 158 | Text(notes) |
| 160 | .font(appDensity.font(.caption)) | 159 | .font(appDensity.font(.caption)) |
| 161 | .foregroundStyle(.secondary) | 160 | .foregroundStyle(Color(.appTextSecondary)) |
| 162 | .lineLimit(2) | 161 | .lineLimit(2) |
| 163 | } | 162 | } |
| 164 | } | 163 | } |
| @@ -197,12 +196,12 @@ struct WorkflowDetailView: View { | |||
| 197 | if let notes = workflow.notes, !notes.isEmpty { | 196 | if let notes = workflow.notes, !notes.isEmpty { |
| 198 | Text(notes) | 197 | Text(notes) |
| 199 | .font(appDensity.font(.caption)) | 198 | .font(appDensity.font(.caption)) |
| 200 | .foregroundStyle(.secondary) | 199 | .foregroundStyle(Color(.appTextSecondary)) |
| 201 | } | 200 | } |
| 202 | if let collaboration = workflow.collaboration, collaboration.isShared { | 201 | if let collaboration = workflow.collaboration, collaboration.isShared { |
| 203 | Text("\(collaboration.ownership.title) • \(collaboration.permission.title)") | 202 | Text("\(collaboration.ownership.title) • \(collaboration.permission.title)") |
| 204 | .font(appDensity.font(.caption)) | 203 | .font(appDensity.font(.caption)) |
| 205 | .foregroundStyle(.secondary) | 204 | .foregroundStyle(Color(.appTextSecondary)) |
| 206 | } | 205 | } |
| 207 | } | 206 | } |
| 208 | 207 | ||
| @@ -277,7 +276,7 @@ struct WorkflowDetailView: View { | |||
| 277 | if !latestSummary.workflowInsights.isEmpty { | 276 | if !latestSummary.workflowInsights.isEmpty { |
| 278 | Text(latestSummary.workflowInsights[0].description) | 277 | Text(latestSummary.workflowInsights[0].description) |
| 279 | .font(appDensity.font(.caption)) | 278 | .font(appDensity.font(.caption)) |
| 280 | .foregroundStyle(.secondary) | 279 | .foregroundStyle(Color(.appTextSecondary)) |
| 281 | } | 280 | } |
| 282 | 281 | ||
| 283 | Button { | 282 | Button { |
| @@ -306,7 +305,7 @@ struct WorkflowDetailView: View { | |||
| 306 | if workflow.domains.isEmpty { | 305 | if workflow.domains.isEmpty { |
| 307 | Text("No domains in this workflow") | 306 | Text("No domains in this workflow") |
| 308 | .font(appDensity.font(.caption)) | 307 | .font(appDensity.font(.caption)) |
| 309 | .foregroundStyle(.secondary) | 308 | .foregroundStyle(Color(.appTextSecondary)) |
| 310 | } else { | 309 | } else { |
| 311 | ForEach(workflow.domains, id: \.self) { domain in | 310 | ForEach(workflow.domains, id: \.self) { domain in |
| 312 | Button { | 311 | Button { |
| @@ -318,7 +317,7 @@ struct WorkflowDetailView: View { | |||
| 318 | .foregroundStyle(.primary) | 317 | .foregroundStyle(.primary) |
| 319 | Spacer() | 318 | Spacer() |
| 320 | Image(systemName: "arrow.up.right.circle") | 319 | Image(systemName: "arrow.up.right.circle") |
| 321 | .foregroundStyle(.secondary) | 320 | .foregroundStyle(Color(.appTextSecondary)) |
| 322 | } | 321 | } |
| 323 | } | 322 | } |
| 324 | .buttonStyle(.plain) | 323 | .buttonStyle(.plain) |
| @@ -355,17 +354,16 @@ struct WorkflowDetailView: View { | |||
| 355 | } else { | 354 | } else { |
| 356 | Text("Workflow not found") | 355 | Text("Workflow not found") |
| 357 | .font(appDensity.font(.callout)) | 356 | .font(appDensity.font(.callout)) |
| 358 | .foregroundStyle(.secondary) | 357 | .foregroundStyle(Color(.appTextSecondary)) |
| 359 | } | 358 | } |
| 360 | } | 359 | } |
| 361 | .preferredColorScheme(.dark) | ||
| 362 | } | 360 | } |
| 363 | 361 | ||
| 364 | private func statRow(label: String, value: String) -> some View { | 362 | private func statRow(label: String, value: String) -> some View { |
| 365 | HStack { | 363 | HStack { |
| 366 | Text(label) | 364 | Text(label) |
| 367 | .font(appDensity.font(.caption)) | 365 | .font(appDensity.font(.caption)) |
| 368 | .foregroundStyle(.secondary) | 366 | .foregroundStyle(Color(.appTextSecondary)) |
| 369 | Spacer() | 367 | Spacer() |
| 370 | Text(value) | 368 | Text(value) |
| 371 | .font(appDensity.font(.callout)) | 369 | .font(appDensity.font(.callout)) |
| @@ -449,7 +447,6 @@ struct WorkflowComposerView: View { | |||
| 449 | } | 447 | } |
| 450 | } | 448 | } |
| 451 | } | 449 | } |
| 452 | .preferredColorScheme(.dark) | ||
| 453 | } | 450 | } |
| 454 | 451 | ||
| 455 | private var parsedDomains: [String] { | 452 | private var parsedDomains: [String] { |
| @@ -507,7 +504,7 @@ struct WorkflowRunSummaryView: View { | |||
| 507 | .foregroundStyle(.primary) | 504 | .foregroundStyle(.primary) |
| 508 | Text(insight.domainsInvolved.joined(separator: ", ")) | 505 | Text(insight.domainsInvolved.joined(separator: ", ")) |
| 509 | .font(.system(.caption, design: .monospaced)) | 506 | .font(.system(.caption, design: .monospaced)) |
| 510 | .foregroundStyle(.secondary) | 507 | .foregroundStyle(Color(.appTextSecondary)) |
| 511 | } | 508 | } |
| 512 | } | 509 | } |
| 513 | } | 510 | } |
| @@ -517,7 +514,7 @@ struct WorkflowRunSummaryView: View { | |||
| 517 | if visibleResults.isEmpty { | 514 | if visibleResults.isEmpty { |
| 518 | Text("No domains with meaningful changes or warnings") | 515 | Text("No domains with meaningful changes or warnings") |
| 519 | .font(.system(.caption, design: .monospaced)) | 516 | .font(.system(.caption, design: .monospaced)) |
| 520 | .foregroundStyle(.secondary) | 517 | .foregroundStyle(Color(.appTextSecondary)) |
| 521 | } else { | 518 | } else { |
| 522 | ForEach(visibleResults) { result in | 519 | ForEach(visibleResults) { result in |
| 523 | if let entry = viewModel.historyEntry(for: result) { | 520 | if let entry = viewModel.historyEntry(for: result) { |
| @@ -578,14 +575,13 @@ struct WorkflowRunSummaryView: View { | |||
| 578 | } | 575 | } |
| 579 | } | 576 | } |
| 580 | } | 577 | } |
| 581 | .preferredColorScheme(.dark) | ||
| 582 | } | 578 | } |
| 583 | 579 | ||
| 584 | private func statRow(label: String, value: String) -> some View { | 580 | private func statRow(label: String, value: String) -> some View { |
| 585 | HStack { | 581 | HStack { |
| 586 | Text(label) | 582 | Text(label) |
| 587 | .font(.system(.caption, design: .monospaced)) | 583 | .font(.system(.caption, design: .monospaced)) |
| 588 | .foregroundStyle(.secondary) | 584 | .foregroundStyle(Color(.appTextSecondary)) |
| 589 | Spacer() | 585 | Spacer() |
| 590 | Text(value) | 586 | Text(value) |
| 591 | .font(.system(.callout, design: .monospaced)) | 587 | .font(.system(.callout, design: .monospaced)) |
| @@ -687,7 +683,6 @@ struct WorkflowBulkAddSheet: View { | |||
| 687 | } | 683 | } |
| 688 | } | 684 | } |
| 689 | } | 685 | } |
| 690 | .preferredColorScheme(.dark) | ||
| 691 | } | 686 | } |
| 692 | 687 | ||
| 693 | private var selectedDomainList: [String] { | 688 | private var selectedDomainList: [String] { |
DomainDigWidget/DomainDigPortfolioWidget.swift +9 −9
| @@ -61,10 +61,10 @@ struct DomainDigWidgetView: View { | |||
| 61 | VStack(spacing: 6) { | 61 | VStack(spacing: 6) { |
| 62 | Image(systemName: "magnifyingglass") | 62 | Image(systemName: "magnifyingglass") |
| 63 | .font(.title2) | 63 | .font(.title2) |
| 64 | .foregroundStyle(.secondary) | 64 | .foregroundStyle(Color(.appTextSecondary)) |
| 65 | Text("No tracked domains") | 65 | Text("No tracked domains") |
| 66 | .font(.caption) | 66 | .font(.caption) |
| 67 | .foregroundStyle(.secondary) | 67 | .foregroundStyle(Color(.appTextSecondary)) |
| 68 | .multilineTextAlignment(.center) | 68 | .multilineTextAlignment(.center) |
| 69 | } | 69 | } |
| 70 | } | 70 | } |
| @@ -80,13 +80,13 @@ struct DomainDigWidgetView: View { | |||
| 80 | Spacer() | 80 | Spacer() |
| 81 | } | 81 | } |
| 82 | .font(.caption2) | 82 | .font(.caption2) |
| 83 | .foregroundStyle(.secondary) | 83 | .foregroundStyle(Color(.appTextSecondary)) |
| 84 | 84 | ||
| 85 | Text("\(data.totalDomains)") | 85 | Text("\(data.totalDomains)") |
| 86 | .font(.system(size: 34, weight: .bold, design: .rounded)) | 86 | .font(.system(size: 34, weight: .bold, design: .rounded)) |
| 87 | Text("tracked") | 87 | Text("tracked") |
| 88 | .font(.caption2) | 88 | .font(.caption2) |
| 89 | .foregroundStyle(.secondary) | 89 | .foregroundStyle(Color(.appTextSecondary)) |
| 90 | 90 | ||
| 91 | Spacer(minLength: 0) | 91 | Spacer(minLength: 0) |
| 92 | 92 | ||
| @@ -113,11 +113,11 @@ struct DomainDigWidgetView: View { | |||
| 113 | Label("Domain Portfolio", systemImage: "shield.lefthalf.filled") | 113 | Label("Domain Portfolio", systemImage: "shield.lefthalf.filled") |
| 114 | .font(.caption) | 114 | .font(.caption) |
| 115 | .fontWeight(.semibold) | 115 | .fontWeight(.semibold) |
| 116 | .foregroundStyle(.secondary) | 116 | .foregroundStyle(Color(.appTextSecondary)) |
| 117 | Spacer() | 117 | Spacer() |
| 118 | Text("\(data.totalDomains) tracked") | 118 | Text("\(data.totalDomains) tracked") |
| 119 | .font(.caption2) | 119 | .font(.caption2) |
| 120 | .foregroundStyle(.secondary) | 120 | .foregroundStyle(Color(.appTextSecondary)) |
| 121 | } | 121 | } |
| 122 | 122 | ||
| 123 | HStack(spacing: 12) { | 123 | HStack(spacing: 12) { |
| @@ -147,7 +147,7 @@ struct DomainDigWidgetView: View { | |||
| 147 | .foregroundStyle(color) | 147 | .foregroundStyle(color) |
| 148 | Text(label) | 148 | Text(label) |
| 149 | .font(.system(size: 9)) | 149 | .font(.system(size: 9)) |
| 150 | .foregroundStyle(.secondary) | 150 | .foregroundStyle(Color(.appTextSecondary)) |
| 151 | } | 151 | } |
| 152 | .frame(maxWidth: .infinity, alignment: .leading) | 152 | .frame(maxWidth: .infinity, alignment: .leading) |
| 153 | } | 153 | } |
| @@ -160,7 +160,7 @@ struct DomainDigWidgetView: View { | |||
| 160 | if domain.isPinned { | 160 | if domain.isPinned { |
| 161 | Image(systemName: "pin.fill") | 161 | Image(systemName: "pin.fill") |
| 162 | .font(.system(size: 8)) | 162 | .font(.system(size: 8)) |
| 163 | .foregroundStyle(.secondary) | 163 | .foregroundStyle(Color(.appTextSecondary)) |
| 164 | } | 164 | } |
| 165 | Text(domain.domain) | 165 | Text(domain.domain) |
| 166 | .font(.caption) | 166 | .font(.caption) |
| @@ -168,7 +168,7 @@ struct DomainDigWidgetView: View { | |||
| 168 | Spacer(minLength: 4) | 168 | Spacer(minLength: 4) |
| 169 | Text(certLabel(for: domain)) | 169 | Text(certLabel(for: domain)) |
| 170 | .font(.caption2) | 170 | .font(.caption2) |
| 171 | .foregroundStyle(.secondary) | 171 | .foregroundStyle(Color(.appTextSecondary)) |
| 172 | } | 172 | } |
| 173 | } | 173 | } |
| 174 | 174 | ||
DomainDigWidget/SweepLiveActivity.swift +5 −5
| @@ -14,7 +14,7 @@ struct SweepLiveActivity: Widget { | |||
| 14 | DynamicIslandExpandedRegion(.leading) { | 14 | DynamicIslandExpandedRegion(.leading) { |
| 15 | Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise") | 15 | Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise") |
| 16 | .font(.caption) | 16 | .font(.caption) |
| 17 | .foregroundStyle(.secondary) | 17 | .foregroundStyle(Color(.appTextSecondary)) |
| 18 | } | 18 | } |
| 19 | DynamicIslandExpandedRegion(.trailing) { | 19 | DynamicIslandExpandedRegion(.trailing) { |
| 20 | Text("\(context.state.completed)/\(context.state.total)") | 20 | Text("\(context.state.completed)/\(context.state.total)") |
| @@ -28,7 +28,7 @@ struct SweepLiveActivity: Widget { | |||
| 28 | if let current = context.state.currentDomain { | 28 | if let current = context.state.currentDomain { |
| 29 | Text(current) | 29 | Text(current) |
| 30 | .font(.caption2) | 30 | .font(.caption2) |
| 31 | .foregroundStyle(.secondary) | 31 | .foregroundStyle(Color(.appTextSecondary)) |
| 32 | .lineLimit(1) | 32 | .lineLimit(1) |
| 33 | } | 33 | } |
| 34 | } | 34 | } |
| @@ -58,7 +58,7 @@ private struct SweepActivityBannerView: View { | |||
| 58 | Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise") | 58 | Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise") |
| 59 | .font(.caption) | 59 | .font(.caption) |
| 60 | .fontWeight(.semibold) | 60 | .fontWeight(.semibold) |
| 61 | .foregroundStyle(.secondary) | 61 | .foregroundStyle(Color(.appTextSecondary)) |
| 62 | Spacer() | 62 | Spacer() |
| 63 | Text("\(context.state.completed) of \(context.state.total)") | 63 | Text("\(context.state.completed) of \(context.state.total)") |
| 64 | .font(.caption) | 64 | .font(.caption) |
| @@ -72,12 +72,12 @@ private struct SweepActivityBannerView: View { | |||
| 72 | if let current = context.state.currentDomain { | 72 | if let current = context.state.currentDomain { |
| 73 | Text(current) | 73 | Text(current) |
| 74 | .font(.caption2) | 74 | .font(.caption2) |
| 75 | .foregroundStyle(.secondary) | 75 | .foregroundStyle(Color(.appTextSecondary)) |
| 76 | .lineLimit(1) | 76 | .lineLimit(1) |
| 77 | } else { | 77 | } else { |
| 78 | Text(context.state.completed >= context.state.total ? "Finished" : "Working…") | 78 | Text(context.state.completed >= context.state.total ? "Finished" : "Working…") |
| 79 | .font(.caption2) | 79 | .font(.caption2) |
| 80 | .foregroundStyle(.secondary) | 80 | .foregroundStyle(Color(.appTextSecondary)) |
| 81 | } | 81 | } |
| 82 | Spacer() | 82 | Spacer() |
| 83 | if context.state.changed > 0 { | 83 | 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 | } | ||