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