aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-20 16:22:28 -0500
committerChristian Cleberg <[email protected]>2026-07-20 16:27:59 -0500
commitbb18197860ffdb491f010648ec903003b6e1bb65 (patch)
treee569d7cdeb1c14c7eab889cb7fac1ea9813e6efe
parent083dfa723739eedd409ea8a6ae27332e48515210 (diff)
downloaddomain-dig-bb18197860ffdb491f010648ec903003b6e1bb65.tar.gz
domain-dig-bb18197860ffdb491f010648ec903003b6e1bb65.tar.bz2
domain-dig-bb18197860ffdb491f010648ec903003b6e1bb65.zip
v4.8.3: Clear SonarCloud new-code issues
Fixes the 4 reported bugs and ~97 code smells flagged in the new-code period. No behavior changes. Bugs (swift:S3923) — DomainInspectionService's confidenceFor* helpers each returned `error == nil ? .low : .low`, an inert conditional. Simplified to `return .low` and dropped the now-unused `error` parameter. Smells: - Merged 14 identical `.empty`/`.error` switch branches in DomainViewModel - Consolidated duplicate implementations (clearPresentedResults/reset, String.nonEmpty/nilIfEmpty, ExportFormat.id/fileExtension) - Extracted nested ternaries into TLSGrade.tone, EmailSecurityGrade.tone, and ChangeImpactClassification.color; removed ContentView.impactColor and the duplicate mapping in BatchResultsView - Documented empty closures and singleton inits - Marked unused protocol-conformance parameters `_` - Renamed CloudSyncTrigger.`import` to `imported` (raw value preserved) and SSLSessionDelegate's _serverTrust/_tlsMetadata - Merged nested ifs in the DER parser; flattened closure nesting in PortScanService and IntegrationService - Replaced two-case switches with if/else Left open: S107 (init parameter counts), S115 (constants mirroring DoH and ipapi JSON keys), S1075 (false positives on https:// literals), and two S117 hits on SwiftUI $binding shorthand. These want a Won't Fix resolution in SonarCloud, not a code change.
-rw-r--r--DomainDig.xcodeproj/project.pbxproj24
-rw-r--r--DomainDig/AppVersion.swift2
-rw-r--r--DomainDig/AuditModeView.swift2
-rw-r--r--DomainDig/BatchResultsView.swift2
-rw-r--r--DomainDig/CloudSharingLifecycle.swift8
-rw-r--r--DomainDig/CloudSharingSheet.swift4
-rw-r--r--DomainDig/CloudSyncService.swift4
-rw-r--r--DomainDig/ContentView.swift87
-rw-r--r--DomainDig/DashboardView.swift6
-rw-r--r--DomainDig/DomainAvailabilityService.swift1
-rw-r--r--DomainDig/DomainDigIntents.swift2
-rw-r--r--DomainDig/DomainInsightEngine.swift11
-rw-r--r--DomainDig/DomainMonitoringService.swift6
-rw-r--r--DomainDig/DomainViewModel.swift98
-rw-r--r--DomainDig/HistoryView.swift6
-rw-r--r--DomainDig/IntegrationService.swift10
-rw-r--r--DomainDig/LocalNotificationService.swift13
-rw-r--r--DomainDig/Models.swift14
-rw-r--r--DomainDig/PortScanService.swift39
-rw-r--r--DomainDig/PurchaseService.swift6
-rw-r--r--DomainDig/SSLCheckService.swift60
-rw-r--r--DomainDig/ScheduledReportService.swift6
-rw-r--r--DomainDig/SweepActivityController.swift2
-rw-r--r--DomainDig/WatchlistView.swift12
-rw-r--r--DomainDig/WorkflowsView.swift16
-rw-r--r--DomainDigWidget/DomainDigPortfolioWidget.swift9
-rw-r--r--DomainInspectionService.swift24
-rw-r--r--DomainReportExporter.swift2
-rw-r--r--RELEASE_ROADMAP.md33
29 files changed, 258 insertions, 251 deletions
diff --git a/DomainDig.xcodeproj/project.pbxproj b/DomainDig.xcodeproj/project.pbxproj
index 7aa48ed..8889f33 100644
--- a/DomainDig.xcodeproj/project.pbxproj
+++ b/DomainDig.xcodeproj/project.pbxproj
@@ -497,7 +497,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = DomainDig/DomainDig.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 42;
+ CURRENT_PROJECT_VERSION = 43;
DEVELOPMENT_TEAM = ZCNAX3VL9D;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -514,7 +514,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 4.8.2;
+ MARKETING_VERSION = 4.8.3;
PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -534,7 +534,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = DomainDig/DomainDig.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 42;
+ CURRENT_PROJECT_VERSION = 43;
DEVELOPMENT_TEAM = ZCNAX3VL9D;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -551,7 +551,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 4.8.2;
+ MARKETING_VERSION = 4.8.3;
PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -570,7 +570,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = DomainDigWidget/DomainDigWidget.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 42;
+ CURRENT_PROJECT_VERSION = 43;
DEVELOPMENT_TEAM = ZCNAX3VL9D;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = DomainDigWidget/Info.plist;
@@ -581,7 +581,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 4.8.2;
+ MARKETING_VERSION = 4.8.3;
PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -599,7 +599,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = DomainDigWidget/DomainDigWidget.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 42;
+ CURRENT_PROJECT_VERSION = 43;
DEVELOPMENT_TEAM = ZCNAX3VL9D;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = DomainDigWidget/Info.plist;
@@ -610,7 +610,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 4.8.2;
+ MARKETING_VERSION = 4.8.3;
PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -628,7 +628,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = DomainDigShareExtension/DomainDigShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 42;
+ CURRENT_PROJECT_VERSION = 43;
DEVELOPMENT_TEAM = ZCNAX3VL9D;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = DomainDigShareExtension/Info.plist;
@@ -639,7 +639,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 4.8.2;
+ MARKETING_VERSION = 4.8.3;
PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -657,7 +657,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_ENTITLEMENTS = DomainDigShareExtension/DomainDigShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 42;
+ CURRENT_PROJECT_VERSION = 43;
DEVELOPMENT_TEAM = ZCNAX3VL9D;
GENERATE_INFOPLIST_FILE = NO;
INFOPLIST_FILE = DomainDigShareExtension/Info.plist;
@@ -668,7 +668,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 4.8.2;
+ MARKETING_VERSION = 4.8.3;
PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
diff --git a/DomainDig/AppVersion.swift b/DomainDig/AppVersion.swift
index e8402b8..a0956df 100644
--- a/DomainDig/AppVersion.swift
+++ b/DomainDig/AppVersion.swift
@@ -2,6 +2,6 @@ import Foundation
enum AppVersion {
nonisolated static var current: String {
- "4.8.2"
+ "4.8.3"
}
}
diff --git a/DomainDig/AuditModeView.swift b/DomainDig/AuditModeView.swift
index fcd4083..b3e363e 100644
--- a/DomainDig/AuditModeView.swift
+++ b/DomainDig/AuditModeView.swift
@@ -57,7 +57,7 @@ struct AuditModeView: View {
}
}
.alert("Audit Export", isPresented: Binding(get: { exportMessage != nil }, set: { if !$0 { exportMessage = nil } })) {
- Button("OK", role: .cancel) {}
+ Button("OK", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text(exportMessage ?? "")
}
diff --git a/DomainDig/BatchResultsView.swift b/DomainDig/BatchResultsView.swift
index e97899e..027d074 100644
--- a/DomainDig/BatchResultsView.swift
+++ b/DomainDig/BatchResultsView.swift
@@ -104,7 +104,7 @@ struct BatchResultRowView: View {
if let changeClassification = result.changeClassification {
Text("Impact: \(changeClassification.title)")
.font(appDensity.font(.caption2))
- .foregroundStyle(changeClassification == .critical ? .red : (changeClassification == .warning ? .yellow : .secondary))
+ .foregroundStyle(changeClassification.color)
}
if let errorMessage = result.errorMessage {
diff --git a/DomainDig/CloudSharingLifecycle.swift b/DomainDig/CloudSharingLifecycle.swift
index fc5de80..26425e5 100644
--- a/DomainDig/CloudSharingLifecycle.swift
+++ b/DomainDig/CloudSharingLifecycle.swift
@@ -3,9 +3,9 @@ import UIKit
final class DomainDigAppDelegate: NSObject, UIApplicationDelegate {
func application(
- _ application: UIApplication,
+ _: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
- options: UIScene.ConnectionOptions
+ options _: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
configuration.delegateClass = DomainDigSceneDelegate.self
@@ -14,12 +14,12 @@ final class DomainDigAppDelegate: NSObject, UIApplicationDelegate {
}
final class DomainDigSceneDelegate: NSObject, UIWindowSceneDelegate {
- func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+ func scene(_: UIScene, willConnectTo _: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let metadata = connectionOptions.cloudKitShareMetadata else { return }
accept(metadata: metadata)
}
- func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
+ func windowScene(_: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
accept(metadata: cloudKitShareMetadata)
}
diff --git a/DomainDig/CloudSharingSheet.swift b/DomainDig/CloudSharingSheet.swift
index 3d524ef..8318e2a 100644
--- a/DomainDig/CloudSharingSheet.swift
+++ b/DomainDig/CloudSharingSheet.swift
@@ -13,7 +13,7 @@ struct CloudSharingSheet: UIViewControllerRepresentable {
Coordinator(dismiss: dismiss, title: title)
}
- func makeUIViewController(context: Context) -> UIViewController {
+ func makeUIViewController(context _: Context) -> UIViewController {
UIViewController()
}
@@ -77,7 +77,7 @@ struct CloudSharingSheet: UIViewControllerRepresentable {
}
}
- func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
+ func presentationControllerDidDismiss(_: UIPresentationController) {
dismissAction()
}
}
diff --git a/DomainDig/CloudSyncService.swift b/DomainDig/CloudSyncService.swift
index 2cb4cfe..985d2a4 100644
--- a/DomainDig/CloudSyncService.swift
+++ b/DomainDig/CloudSyncService.swift
@@ -40,7 +40,7 @@ enum CloudSyncTrigger: String {
case launch
case automatic
case manual
- case `import`
+ case imported = "import"
}
enum ShareableEntity: Identifiable, Hashable {
@@ -1186,7 +1186,7 @@ final class CloudSyncService {
}
private func collaborationMetadata(
- for record: CKRecord,
+ for _: CKRecord,
share: CKShare?,
scope: SyncDatabaseScope
) -> CollaborationMetadata {
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift
index 3e1015d..40c97d1 100644
--- a/DomainDig/ContentView.swift
+++ b/DomainDig/ContentView.swift
@@ -569,13 +569,13 @@ struct ContentView: View {
shareSingleResults(format: .pdf)
}
} else {
- Button("CSV Export • Available in Pro") {}
+ Button("CSV Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("JSON Export • Available in Pro") {}
+ Button("JSON Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("Markdown Export • Available in Pro") {}
+ Button("Markdown Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("PDF Export • Available in Pro") {}
+ Button("PDF Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
}
} label: {
@@ -621,13 +621,13 @@ struct ContentView: View {
shareBatchResults(format: .pdf)
}
} else {
- Button("Batch CSV • Available in Pro") {}
+ Button("Batch CSV • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("Batch JSON • Available in Pro") {}
+ Button("Batch JSON • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("Batch Markdown • Available in Pro") {}
+ Button("Batch Markdown • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("Batch PDF • Available in Pro") {}
+ Button("Batch PDF • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
}
} label: {
@@ -1040,10 +1040,10 @@ struct DomainChangeSummaryView: View {
.clipShape(Capsule())
Text(summary.impactClassification.title.uppercased())
.font(appDensity.font(.caption2))
- .foregroundStyle(impactColor(summary.impactClassification))
+ .foregroundStyle(summary.impactClassification.color)
.padding(.horizontal, 8)
.padding(.vertical, 4)
- .background(impactColor(summary.impactClassification).opacity(0.16))
+ .background(summary.impactClassification.color.opacity(0.16))
.clipShape(Capsule())
Text(summary.generatedAt, style: .time)
.font(appDensity.font(.caption2))
@@ -1107,16 +1107,6 @@ struct DomainChangeSummaryView: View {
}
}
- private func impactColor(_ impact: ChangeImpactClassification) -> Color {
- switch impact {
- case .informational:
- return .secondary
- case .warning:
- return .yellow
- case .critical:
- return .red
- }
- }
}
struct DomainDiffView: View {
@@ -2008,7 +1998,7 @@ struct WebSectionView: View {
}
SectionTrustMetadataView(provenance: tlsProvenance, confidence: nil)
if !sslLoading, let tlsSummary {
- LabeledValueRow(row: InfoRowViewData(label: "TLS Grade", value: tlsSummary.tlsGrade.rawValue, tone: tlsSummary.tlsGrade == .a ? .success : (tlsSummary.tlsGrade == .f ? .failure : .warning)))
+ LabeledValueRow(row: InfoRowViewData(label: "TLS Grade", value: tlsSummary.tlsGrade.rawValue, tone: tlsSummary.tlsGrade.tone))
ForEach(Array(tlsSummary.tlsHighlights.enumerated()), id: \.offset) { _, highlight in
MessageRowView(text: highlight, isError: isTLSHighlightError(highlight))
}
@@ -2153,7 +2143,7 @@ struct EmailSectionView: View {
.opacity(loading ? 0 : 1)
}
if let assessment, let grade = assessment.grade {
- LabeledValueRow(row: InfoRowViewData(label: "Grade", value: grade.rawValue, tone: grade == .a ? .success : (grade == .f ? .failure : .warning)))
+ LabeledValueRow(row: InfoRowViewData(label: "Grade", value: grade.rawValue, tone: grade.tone))
if !assessment.reasons.isEmpty {
Text(assessment.reasons.joined(separator: " | "))
.font(appDensity.font(.caption2))
@@ -2897,6 +2887,11 @@ private struct LocalAPISettingsView: View {
@State private var localAPIService = LocalAPIService.shared
@State private var portText = ""
+ private var statusText: String {
+ if localAPIService.isRunning { return "Running" }
+ return localAPIService.config.isEnabled ? "Stopped" : "Disabled"
+ }
+
var body: some View {
Form {
Section("Local API") {
@@ -2923,7 +2918,7 @@ private struct LocalAPISettingsView: View {
.keyboardType(.numberPad)
LabeledContent("Address", value: localAPIService.address)
- LabeledContent("Status", value: localAPIService.isRunning ? "Running" : (localAPIService.config.isEnabled ? "Stopped" : "Disabled"))
+ LabeledContent("Status", value: statusText)
LabeledContent("Token", value: localAPIService.maskedToken)
if let statusMessage = localAPIService.statusMessage {
@@ -3339,7 +3334,7 @@ private struct DataPortabilitySettingsView: View {
Button("Replace", role: .destructive) {
applyPendingImport()
}
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text("Replace mode overwrites local data covered by the imported file and may remove items that are only on this device.")
}
@@ -3347,7 +3342,7 @@ private struct DataPortabilitySettingsView: View {
get: { pendingImportError != nil },
set: { if !$0 { pendingImportError = nil } }
)) {
- Button("OK", role: .cancel) {}
+ Button("OK", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text(pendingImportError ?? "The import could not be completed.")
}
@@ -3585,7 +3580,7 @@ private struct DataManagementSettingsView: View {
Button("Clear", role: .destructive) {
viewModel.clearHistory()
}
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text("This removes saved lookup snapshots and clears monitoring run history on this device.")
}
@@ -3593,7 +3588,7 @@ private struct DataManagementSettingsView: View {
Button("Clear", role: .destructive) {
viewModel.clearLookupCache()
}
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text("This clears the in-memory lookup cache and cancels any cached in-flight work.")
}
@@ -3601,7 +3596,7 @@ private struct DataManagementSettingsView: View {
Button("Clear", role: .destructive) {
viewModel.clearWorkflows()
}
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text("This removes saved workflows only. History, tracked domains, and saved reports stay intact.")
}
@@ -3609,12 +3604,12 @@ private struct DataManagementSettingsView: View {
Button("Clear", role: .destructive) {
viewModel.clearTrackedDomains()
}
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text("This removes the watchlist and clears monitoring run history. History and workflows stay intact.")
}
.alert("Delete All Data?", isPresented: $showDeleteAllConfirmation) {
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
Button("Delete All Data", role: .destructive) {
deleteAllData()
}
@@ -3625,7 +3620,7 @@ private struct DataManagementSettingsView: View {
get: { deleteAllErrorMessage != nil },
set: { if !$0 { deleteAllErrorMessage = nil } }
)) {
- Button("OK", role: .cancel) {}
+ Button("OK", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text(deleteAllErrorMessage ?? "The local data reset could not be completed.")
}
@@ -3745,6 +3740,36 @@ private struct DataImportPreviewSheet: View {
}
}
+extension ChangeImpactClassification {
+ var color: Color {
+ switch self {
+ case .informational: return .secondary
+ case .warning: return .yellow
+ case .critical: return .red
+ }
+ }
+}
+
+extension TLSGrade {
+ var tone: ResultTone {
+ switch self {
+ case .a: return .success
+ case .f: return .failure
+ default: return .warning
+ }
+ }
+}
+
+extension EmailSecurityGrade {
+ var tone: ResultTone {
+ switch self {
+ case .a: return .success
+ case .f: return .failure
+ default: return .warning
+ }
+ }
+}
+
#Preview {
ContentView(viewModel: DomainViewModel())
}
diff --git a/DomainDig/DashboardView.swift b/DomainDig/DashboardView.swift
index 087f080..398ff3e 100644
--- a/DomainDig/DashboardView.swift
+++ b/DomainDig/DashboardView.swift
@@ -239,22 +239,26 @@ struct DashboardView: View {
let warningCount = states.filter { $0.health == .warning }.count
let title: String
let color: Color
+ let systemImage: String
if criticalCount > 0 {
title = "\(criticalCount) critical"
color = .red
+ systemImage = "exclamationmark.octagon.fill"
} else if warningCount > 0 {
title = "\(warningCount) warning"
color = .yellow
+ systemImage = "exclamationmark.triangle.fill"
} else {
title = "Healthy"
color = .green
+ systemImage = "checkmark.circle.fill"
}
return AppStatusBadgeView(
model: .init(
title: title,
- systemImage: criticalCount > 0 ? "exclamationmark.octagon.fill" : (warningCount > 0 ? "exclamationmark.triangle.fill" : "checkmark.circle.fill"),
+ systemImage: systemImage,
foregroundColor: color,
backgroundColor: color.opacity(0.16)
)
diff --git a/DomainDig/DomainAvailabilityService.swift b/DomainDig/DomainAvailabilityService.swift
index b671fbc..1d112e1 100644
--- a/DomainDig/DomainAvailabilityService.swift
+++ b/DomainDig/DomainAvailabilityService.swift
@@ -47,6 +47,7 @@ struct DomainAvailabilityService {
return .registered
}
} catch {
+ // No A record (or the lookup failed); fall through to the NS probe.
}
do {
diff --git a/DomainDig/DomainDigIntents.swift b/DomainDig/DomainDigIntents.swift
index c0c73cc..8b46aa7 100644
--- a/DomainDig/DomainDigIntents.swift
+++ b/DomainDig/DomainDigIntents.swift
@@ -149,7 +149,7 @@ struct RunSweepIntent: AppIntent {
final class DomainDigIntentRouter {
static let shared = DomainDigIntentRouter()
var pendingAction: DomainDigDeepLink.Action?
- private init() {}
+ private init() { /* Singleton; use the shared instance. */ }
}
/// Exposes DomainDig intents to Spotlight and Siri with invocation phrases.
diff --git a/DomainDig/DomainInsightEngine.swift b/DomainDig/DomainInsightEngine.swift
index f1de9c0..6f8edeb 100644
--- a/DomainDig/DomainInsightEngine.swift
+++ b/DomainDig/DomainInsightEngine.swift
@@ -412,7 +412,7 @@ enum DomainInsightEngine {
if hasMXRecords(snapshot), snapshot.emailSecurity == nil {
patterns.append("MX records exist without corresponding email security records")
}
- if snapshot.sslInfo != nil && !(snapshot.dnsSections.first(where: { $0.recordType == .CAA })?.records.isEmpty == false) {
+ if snapshot.sslInfo != nil, snapshot.dnsSections.first(where: { $0.recordType == .CAA })?.records.isEmpty != false {
patterns.append("TLS is active but no CAA record was found")
}
@@ -427,9 +427,16 @@ enum DomainInsightEngine {
let dmarcFound = result.dmarc.found
let dmarcStrict = isStrictDMARC(result.dmarc.value)
+ let dmarcReason: String
+ if !dmarcFound {
+ dmarcReason = "DMARC missing"
+ } else {
+ dmarcReason = dmarcStrict ? "DMARC policy is strict" : "DMARC policy is not strict"
+ }
+
let reasons = [
spfFound ? "SPF present" : "SPF missing",
- dmarcFound ? (dmarcStrict ? "DMARC policy is strict" : "DMARC policy is not strict") : "DMARC missing",
+ dmarcReason,
dkimFound ? "DKIM present" : "DKIM not detected"
]
diff --git a/DomainDig/DomainMonitoringService.swift b/DomainDig/DomainMonitoringService.swift
index 917ede0..eb6a581 100644
--- a/DomainDig/DomainMonitoringService.swift
+++ b/DomainDig/DomainMonitoringService.swift
@@ -85,7 +85,7 @@ final class DomainMonitoringScheduler {
private var isRegistered = false
- private init() {}
+ private init() { /* Singleton; use the shared instance. */ }
func registerBackgroundTask() {
#if canImport(BackgroundTasks)
@@ -613,8 +613,8 @@ final class DomainMonitoringService {
private func processAdaptiveMonitoringState(
trackedDomain: TrackedDomain,
- snapshot: LookupSnapshot,
- previousSnapshot: LookupSnapshot?,
+ snapshot _: LookupSnapshot,
+ previousSnapshot _: LookupSnapshot?,
alertDescriptor: MonitoringAlertDescriptor?,
settings: MonitoringSettings,
notificationsAuthorized: Bool,
diff --git a/DomainDig/DomainViewModel.swift b/DomainDig/DomainViewModel.swift
index 674efdf..256c1e1 100644
--- a/DomainDig/DomainViewModel.swift
+++ b/DomainDig/DomainViewModel.swift
@@ -1280,19 +1280,7 @@ final class DomainViewModel {
}
func clearPresentedResults() {
- lookupTask?.cancel()
- customPortScanTask?.cancel()
- batchTask?.cancel()
- hasRun = false
- searchedDomain = ""
- lastLookupDurationMs = nil
- currentDiffSections = []
- currentChangeSummary = nil
- ownershipDiff = []
- currentReport = nil
- refreshingTrackedDomainID = nil
- clearBatchState()
- clearLookupState()
+ reset()
}
func run() {
@@ -1857,7 +1845,7 @@ final class DomainViewModel {
portabilityStatusMessage = result.summary
CloudSyncService.shared.markAppSettingsChanged()
CloudSyncService.shared.markMonitoringSettingsChanged(localActivationConfirmed: monitoringSettings.isEnabled)
- CloudSyncService.shared.scheduleSyncIfNeeded(trigger: .import)
+ CloudSyncService.shared.scheduleSyncIfNeeded(trigger: .imported)
return result
}
@@ -2174,10 +2162,7 @@ final class DomainViewModel {
case let .success(sections):
dnsSections = sections
dnsError = nil
- case let .empty(message):
- dnsSections = []
- dnsError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
dnsSections = []
dnsError = message
}
@@ -2199,10 +2184,7 @@ final class DomainViewModel {
case let .success(info):
sslInfo = info
sslError = nil
- case let .empty(message):
- sslInfo = nil
- sslError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
sslInfo = nil
sslError = message
}
@@ -2228,15 +2210,7 @@ final class DomainViewModel {
httpProtocol = headersResult.httpProtocol
http3Advertised = headersResult.http3Advertised
httpHeadersError = nil
- case let .empty(message):
- httpHeaders = []
- httpSecurityGrade = nil
- httpStatusCode = nil
- httpResponseTimeMs = nil
- httpProtocol = nil
- http3Advertised = false
- httpHeadersError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
httpHeaders = []
httpSecurityGrade = nil
httpStatusCode = nil
@@ -2255,10 +2229,7 @@ final class DomainViewModel {
case let .success(results):
reachabilityResults = results
reachabilityError = nil
- case let .empty(message):
- reachabilityResults = []
- reachabilityError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
reachabilityResults = []
reachabilityError = message
}
@@ -2272,10 +2243,7 @@ final class DomainViewModel {
case let .success(emailResult):
emailSecurity = emailResult
emailSecurityError = nil
- case let .empty(message):
- emailSecurity = nil
- emailSecurityError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
emailSecurity = nil
emailSecurityError = message
}
@@ -2289,10 +2257,7 @@ final class DomainViewModel {
case let .success(ownership):
ownershipResult = ownership
ownershipError = nil
- case let .empty(message):
- ownershipResult = nil
- ownershipError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
ownershipResult = nil
ownershipError = message
}
@@ -2306,10 +2271,7 @@ final class DomainViewModel {
case let .success(record):
ptrRecord = record
ptrError = nil
- case let .empty(message):
- ptrRecord = nil
- ptrError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
ptrRecord = nil
ptrError = message
}
@@ -2323,10 +2285,7 @@ final class DomainViewModel {
case let .success(hops):
redirectChain = hops
redirectChainError = nil
- case let .empty(message):
- redirectChain = []
- redirectChainError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
redirectChain = []
redirectChainError = message
}
@@ -2340,10 +2299,7 @@ final class DomainViewModel {
case let .success(results):
subdomains = results
subdomainsError = nil
- case let .empty(message):
- subdomains = []
- subdomainsError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
subdomains = []
subdomainsError = message
}
@@ -2358,11 +2314,7 @@ final class DomainViewModel {
guard !Task.isCancelled, isCurrentLookup(lookupID) else { return }
portScanResults = enrichedResults
portScanError = nil
- case let .empty(message):
- guard !Task.isCancelled, isCurrentLookup(lookupID) else { return }
- portScanResults = []
- portScanError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
guard !Task.isCancelled, isCurrentLookup(lookupID) else { return }
portScanResults = []
portScanError = message
@@ -2377,10 +2329,7 @@ final class DomainViewModel {
case let .success(geolocation):
ipGeolocation = geolocation
ipGeolocationError = nil
- case let .empty(message):
- ipGeolocation = nil
- ipGeolocationError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
ipGeolocation = nil
ipGeolocationError = message
}
@@ -2408,10 +2357,7 @@ final class DomainViewModel {
customPortResults = results
customPortScanError = nil
_ = saveHistoryEntry(replaceLatest: true)
- case let .empty(message):
- customPortResults = []
- customPortScanError = message
- case let .error(message):
+ case let .empty(message), let .error(message):
customPortResults = []
customPortScanError = message
}
@@ -4024,7 +3970,7 @@ final class DomainViewModel {
static func summaryFields(from snapshot: LookupSnapshot) -> [SummaryFieldViewData] {
[
- SummaryFieldViewData(label: "Domain", value: snapshot.domain.nonEmpty ?? "Unavailable", tone: .primary),
+ SummaryFieldViewData(label: "Domain", value: snapshot.domain.nilIfEmpty ?? "Unavailable", tone: .primary),
SummaryFieldViewData(label: "Observed IP", value: primaryIPAddress(from: snapshot) ?? "Unavailable", tone: .primary),
SummaryFieldViewData(label: "Observed Redirect", value: finalRedirectTarget(from: snapshot) ?? "Unavailable", tone: .secondary),
SummaryFieldViewData(label: "Inference", value: availabilityInference(from: snapshot), tone: availabilityTone(snapshot.availabilityResult?.status)),
@@ -4780,10 +4726,7 @@ final class DomainViewModel {
case let .success(pricing):
domainPricing = pricing
domainPricingError = nil
- case let .empty(message):
- domainPricing = nil
- domainPricingError = conciseExternalMessage(message, fallback: "External pricing unavailable")
- case let .error(message):
+ case let .empty(message), let .error(message):
domainPricing = nil
domainPricingError = conciseExternalMessage(message, fallback: "External pricing unavailable")
}
@@ -4803,10 +4746,7 @@ final class DomainViewModel {
case let .success(result):
reputation = result
reputationError = nil
- case let .empty(message):
- reputation = nil
- reputationError = conciseExternalMessage(message, fallback: "Reputation check unavailable")
- case let .error(message):
+ case let .empty(message), let .error(message):
reputation = nil
reputationError = conciseExternalMessage(message, fallback: "Reputation check unavailable")
}
@@ -4865,10 +4805,6 @@ final class DomainViewModel {
}
private extension String {
- var nonEmpty: String? {
- isEmpty ? nil : self
- }
-
var nilIfEmpty: String? {
isEmpty ? nil : self
}
diff --git a/DomainDig/HistoryView.swift b/DomainDig/HistoryView.swift
index 316fcce..5350168 100644
--- a/DomainDig/HistoryView.swift
+++ b/DomainDig/HistoryView.swift
@@ -115,7 +115,7 @@ struct HistoryView: View {
Button("Clear All", role: .destructive) {
viewModel.clearHistory()
}
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text("This will delete all saved history entries. This cannot be undone.")
}
@@ -314,7 +314,7 @@ struct HistoryDetailView: View {
isCloudflareProxied: snapshot.httpHeaders.contains(where: { $0.name.lowercased() == "cf-ray" }),
customPortsExpanded: .constant(false),
customPortInput: .constant(""),
- onScanCustomPorts: {}
+ onScanCustomPorts: { /* Historical snapshot; re-scanning is not offered here. */ }
)
.padding(.top, appDensity.metrics.sectionSpacing)
}
@@ -344,7 +344,7 @@ struct HistoryDetailView: View {
Button("Run with Snapshot Resolver") {
viewModel.rerunLookup(from: entry, useSnapshotResolver: true)
}
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text(viewModel.resolverMismatchNote(for: entry) ?? "Choose how to reproduce this snapshot.")
}
diff --git a/DomainDig/IntegrationService.swift b/DomainDig/IntegrationService.swift
index 23085d3..de24c23 100644
--- a/DomainDig/IntegrationService.swift
+++ b/DomainDig/IntegrationService.swift
@@ -768,9 +768,7 @@ private final class SMTPChannel {
connection.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
- DispatchQueue.global(qos: .utility).async {
- self?.startReceiveLoop()
- }
+ self?.scheduleReceiveLoop()
continuation.resume()
case .failed(let error):
continuation.resume(throwing: error)
@@ -782,6 +780,12 @@ private final class SMTPChannel {
}
}
+ private func scheduleReceiveLoop() {
+ DispatchQueue.global(qos: .utility).async { [weak self] in
+ self?.startReceiveLoop()
+ }
+ }
+
func cancel() {
connection.cancel()
}
diff --git a/DomainDig/LocalNotificationService.swift b/DomainDig/LocalNotificationService.swift
index d3474e3..3648cb8 100644
--- a/DomainDig/LocalNotificationService.swift
+++ b/DomainDig/LocalNotificationService.swift
@@ -5,7 +5,7 @@ import UserNotifications
final class LocalNotificationService {
static let shared = LocalNotificationService()
- private init() {}
+ private init() { /* Singleton; use the shared instance. */ }
static let domainUserInfoKey = "domain"
static let domainCategoryIdentifier = "domain-event"
@@ -188,14 +188,14 @@ private final class NotificationCenterDelegate: NSObject, UNUserNotificationCent
static let shared = NotificationCenterDelegate()
func userNotificationCenter(
- _ center: UNUserNotificationCenter,
- willPresent notification: UNNotification
+ _: UNUserNotificationCenter,
+ willPresent _: UNNotification
) async -> UNNotificationPresentationOptions {
[.banner, .list, .sound]
}
func userNotificationCenter(
- _ center: UNUserNotificationCenter,
+ _: UNUserNotificationCenter,
didReceive response: UNNotificationResponse
) async {
let userInfo = response.notification.request.content.userInfo
@@ -204,10 +204,9 @@ private final class NotificationCenterDelegate: NSObject, UNUserNotificationCent
else { return }
let action: DomainDigDeepLink.Action
- switch response.actionIdentifier {
- case LocalNotificationService.reinspectActionIdentifier:
+ if response.actionIdentifier == LocalNotificationService.reinspectActionIdentifier {
action = .inspect(domain)
- default:
+ } else {
// Default tap: open the tracked domain's detail.
action = .detail(domain)
}
diff --git a/DomainDig/Models.swift b/DomainDig/Models.swift
index 6989cba..d83dc79 100644
--- a/DomainDig/Models.swift
+++ b/DomainDig/Models.swift
@@ -244,8 +244,14 @@ struct DomainChangeSummary: Codable, Equatable {
changedSections = try container.decodeIfPresent([String].self, forKey: .changedSections) ?? []
generatedAt = try container.decode(Date.self, forKey: .generatedAt)
severity = try container.decodeIfPresent(ChangeSeverity.self, forKey: .severity) ?? (hasChanges ? .medium : .low)
+ let defaultImpact: ChangeImpactClassification
+ if severity == .high {
+ defaultImpact = .critical
+ } else {
+ defaultImpact = hasChanges ? .warning : .informational
+ }
impactClassification = try container.decodeIfPresent(ChangeImpactClassification.self, forKey: .impactClassification)
- ?? (severity == .high ? .critical : (hasChanges ? .warning : .informational))
+ ?? defaultImpact
message = try container.decodeIfPresent(String.self, forKey: .message)
?? (changedSections.isEmpty ? "No meaningful changes" : changedSections.joined(separator: " • "))
observedFacts = try container.decodeIfPresent([String].self, forKey: .observedFacts) ?? []
@@ -562,12 +568,10 @@ enum DomainClassificationKind: String, Codable, CaseIterable, Sendable {
case unknown
var title: String {
- switch self {
- case .staticSite:
+ if self == .staticSite {
return "Static"
- default:
- return rawValue.capitalized
}
+ return rawValue.capitalized
}
}
diff --git a/DomainDig/PortScanService.swift b/DomainDig/PortScanService.swift
index 1f3e649..808b38a 100644
--- a/DomainDig/PortScanService.swift
+++ b/DomainDig/PortScanService.swift
@@ -89,24 +89,7 @@ struct PortScanService {
switch state {
case .ready:
connection.receive(minimumIncompleteLength: 1, maximumLength: 256) { data, _, _, error in
- guard error == nil,
- let data,
- !data.isEmpty,
- let rawBanner = String(data: data, encoding: .utf8) else {
- context.finish(with: nil)
- return
- }
-
- let printableBanner = rawBanner.filter { character in
- guard let scalar = character.unicodeScalars.first,
- character.unicodeScalars.count == 1 else {
- return false
- }
- return (32...126).contains(scalar.value)
- }
-
- let banner = String(printableBanner.prefix(80))
- context.finish(with: banner.isEmpty ? nil : banner)
+ context.finish(with: printableBanner(from: data, error: error))
}
case .failed, .cancelled:
context.finish(with: nil)
@@ -123,6 +106,26 @@ struct PortScanService {
}
}
+ private static func printableBanner(from data: Data?, error: Error?) -> String? {
+ guard error == nil,
+ let data,
+ !data.isEmpty,
+ let rawBanner = String(data: data, encoding: .utf8) else {
+ return nil
+ }
+
+ let printable = rawBanner.filter { character in
+ guard let scalar = character.unicodeScalars.first,
+ character.unicodeScalars.count == 1 else {
+ return false
+ }
+ return (32...126).contains(scalar.value)
+ }
+
+ let banner = String(printable.prefix(80))
+ return banner.isEmpty ? nil : banner
+ }
+
private static func probe(domain: String, port: UInt16) async -> PortProbeResult {
await probe(domain: domain, port: port, timeout: 1.5)
}
diff --git a/DomainDig/PurchaseService.swift b/DomainDig/PurchaseService.swift
index d31afc7..4ab8ef4 100644
--- a/DomainDig/PurchaseService.swift
+++ b/DomainDig/PurchaseService.swift
@@ -288,12 +288,10 @@ final class PurchaseService {
private func storeMessage(for error: Error, fallback: String) -> String {
if let storeKitError = error as? StoreKitError {
- switch storeKitError {
- case .networkError:
+ if case .networkError = storeKitError {
return "The App Store is offline right now."
- default:
- return fallback
}
+ return fallback
}
let message = error.localizedDescription.trimmingCharacters(in: .whitespacesAndNewlines)
diff --git a/DomainDig/SSLCheckService.swift b/DomainDig/SSLCheckService.swift
index 73c00bd..b1affa8 100644
--- a/DomainDig/SSLCheckService.swift
+++ b/DomainDig/SSLCheckService.swift
@@ -156,10 +156,9 @@ private enum DERCertificateParser {
var offset = tbsContent.contentStart
// Skip version (explicit tag [0]) if present
- if offset < bytes.count && (bytes[offset] & 0xE0) == 0xA0 {
- if let tagLen = readTagAndLength(bytes, offset: offset) {
- offset = tagLen.contentStart + tagLen.length
- }
+ if offset < bytes.count, (bytes[offset] & 0xE0) == 0xA0,
+ let tagLen = readTagAndLength(bytes, offset: offset) {
+ offset = tagLen.contentStart + tagLen.length
}
// Skip serialNumber
@@ -199,11 +198,10 @@ private enum DERCertificateParser {
// Extensions are in an explicit tag [3]
while offset < tbsContent.contentStart + tbsContent.length {
if bytes[offset] == 0xA3 {
- if let extWrapper = readTagAndLength(bytes, offset: offset) {
- // Inside is a SEQUENCE of SEQUENCE extensions
- if let extsSeq = readTagAndLength(bytes, offset: extWrapper.contentStart) {
- result.subjectAltNames = extractSANs(bytes, sequenceStart: extsSeq.contentStart, length: extsSeq.length)
- }
+ // Inside the wrapper is a SEQUENCE of SEQUENCE extensions
+ if let extWrapper = readTagAndLength(bytes, offset: offset),
+ let extsSeq = readTagAndLength(bytes, offset: extWrapper.contentStart) {
+ result.subjectAltNames = extractSANs(bytes, sequenceStart: extsSeq.contentStart, length: extsSeq.length)
}
break
}
@@ -267,27 +265,25 @@ private enum DERCertificateParser {
if oidBytes == sanOID {
var valuePos = oidTL.contentStart + oidTL.length
// Skip optional critical BOOLEAN
- if valuePos < extEnd && bytes[valuePos] == 0x01 {
- if let boolTL = readTagAndLength(bytes, offset: valuePos) {
- valuePos = boolTL.contentStart + boolTL.length
- }
+ if valuePos < extEnd, bytes[valuePos] == 0x01,
+ let boolTL = readTagAndLength(bytes, offset: valuePos) {
+ valuePos = boolTL.contentStart + boolTL.length
}
// The value is an OCTET STRING wrapping a SEQUENCE of GeneralNames
- if let octetTL = readTagAndLength(bytes, offset: valuePos) {
- if let sanSeq = readTagAndLength(bytes, offset: octetTL.contentStart) {
- let sanEnd = sanSeq.contentStart + sanSeq.length
- var sanPos = sanSeq.contentStart
- while sanPos < sanEnd {
- guard let nameTL = readTagAndLength(bytes, offset: sanPos) else { break }
- // Context tag [2] = dNSName (IA5String)
- if (bytes[sanPos] & 0x1F) == 2 {
- let nameBytes = bytes[nameTL.contentStart..<nameTL.contentStart + nameTL.length]
- if let name = String(bytes: nameBytes, encoding: .ascii) {
- sans.append(name)
- }
+ if let octetTL = readTagAndLength(bytes, offset: valuePos),
+ let sanSeq = readTagAndLength(bytes, offset: octetTL.contentStart) {
+ let sanEnd = sanSeq.contentStart + sanSeq.length
+ var sanPos = sanSeq.contentStart
+ while sanPos < sanEnd {
+ guard let nameTL = readTagAndLength(bytes, offset: sanPos) else { break }
+ // Context tag [2] = dNSName (IA5String)
+ if (bytes[sanPos] & 0x1F) == 2 {
+ let nameBytes = bytes[nameTL.contentStart..<nameTL.contentStart + nameTL.length]
+ if let name = String(bytes: nameBytes, encoding: .ascii) {
+ sans.append(name)
}
- sanPos = nameTL.contentStart + nameTL.length
}
+ sanPos = nameTL.contentStart + nameTL.length
}
}
}
@@ -386,19 +382,19 @@ enum SSLError: LocalizedError {
final class SSLSessionDelegate: NSObject, URLSessionDelegate, @unchecked Sendable {
private let lock = NSLock()
- private var _serverTrust: SecTrust?
- private var _tlsMetadata: TLSMetadata?
+ private var storedServerTrust: SecTrust?
+ private var storedTLSMetadata: TLSMetadata?
var serverTrust: SecTrust? {
lock.lock()
defer { lock.unlock() }
- return _serverTrust
+ return storedServerTrust
}
fileprivate var tlsMetadata: TLSMetadata? {
lock.lock()
defer { lock.unlock() }
- return _tlsMetadata
+ return storedTLSMetadata
}
func urlSession(
@@ -413,7 +409,7 @@ final class SSLSessionDelegate: NSObject, URLSessionDelegate, @unchecked Sendabl
}
lock.lock()
- _serverTrust = trust
+ storedServerTrust = trust
lock.unlock()
let credential = URLCredential(trust: trust)
@@ -439,7 +435,7 @@ extension SSLSessionDelegate: URLSessionTaskDelegate {
}
lock.lock()
- _tlsMetadata = TLSMetadata(tlsVersion: tlsVersion, cipherSuite: cipherSuite)
+ storedTLSMetadata = TLSMetadata(tlsVersion: tlsVersion, cipherSuite: cipherSuite)
lock.unlock()
}
diff --git a/DomainDig/ScheduledReportService.swift b/DomainDig/ScheduledReportService.swift
index c1db3de..3aee834 100644
--- a/DomainDig/ScheduledReportService.swift
+++ b/DomainDig/ScheduledReportService.swift
@@ -106,10 +106,10 @@ struct ScheduledReportOutcome {
final class ScheduledReportService {
static let shared = ScheduledReportService()
- private init() {}
+ private init() { /* Singleton; use the shared instance. */ }
@discardableResult
- func generateReport(trigger: MonitoringRunTrigger, requireEnabledSetting: Bool) async -> ScheduledReportOutcome {
+ func generateReport(trigger _: MonitoringRunTrigger, requireEnabledSetting: Bool) async -> ScheduledReportOutcome {
var settings = ScheduledReportStorage.loadSettings()
guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else {
@@ -191,7 +191,7 @@ final class ScheduledReportScheduler {
private var isRegistered = false
- private init() {}
+ private init() { /* Singleton; use the shared instance. */ }
func registerBackgroundTask() {
#if canImport(BackgroundTasks)
diff --git a/DomainDig/SweepActivityController.swift b/DomainDig/SweepActivityController.swift
index 26fcb2d..a00bb2f 100644
--- a/DomainDig/SweepActivityController.swift
+++ b/DomainDig/SweepActivityController.swift
@@ -8,7 +8,7 @@ final class SweepActivityController {
private var activity: Activity<SweepActivityAttributes>?
- private init() {}
+ private init() { /* Singleton; use the shared instance. */ }
func begin(title: String, total: Int) {
guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
diff --git a/DomainDig/WatchlistView.swift b/DomainDig/WatchlistView.swift
index 5670e5e..389ae91 100644
--- a/DomainDig/WatchlistView.swift
+++ b/DomainDig/WatchlistView.swift
@@ -169,13 +169,13 @@ struct WatchlistView: View {
shareTrackedDomains(format: .pdf)
}
} else {
- Button("CSV Export • Available in Pro") {}
+ Button("CSV Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("JSON Export • Available in Pro") {}
+ Button("JSON Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("Markdown Export • Available in Pro") {}
+ Button("Markdown Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("PDF Export • Available in Pro") {}
+ Button("PDF Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
}
} label: {
@@ -250,7 +250,7 @@ struct WatchlistView: View {
Button("Save") {
viewModel.saveCurrentWatchlistView(name: newSavedViewName)
}
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text("Saves the current tag, filter, and sort as a reusable preset.")
}
@@ -736,7 +736,7 @@ struct TrackedDomainDetailView: View {
viewModel.rerunInspection(for: liveTrackedDomain, useSnapshotResolver: true)
}
}
- Button("Cancel", role: .cancel) {}
+ Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ }
} message: {
Text(viewModel.resolverMismatchNote(for: liveTrackedDomain) ?? "Choose how to reproduce the most recent snapshot.")
}
diff --git a/DomainDig/WorkflowsView.swift b/DomainDig/WorkflowsView.swift
index 3cfca42..ce889fb 100644
--- a/DomainDig/WorkflowsView.swift
+++ b/DomainDig/WorkflowsView.swift
@@ -253,13 +253,13 @@ struct WorkflowDetailView: View {
shareWorkflowResults(format: .pdf)
}
} else {
- Button("CSV Export • Available in Pro") {}
+ Button("CSV Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("JSON Export • Available in Pro") {}
+ Button("JSON Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("Markdown Export • Available in Pro") {}
+ Button("Markdown Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("PDF Export • Available in Pro") {}
+ Button("PDF Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
}
} label: {
@@ -554,13 +554,13 @@ struct WorkflowRunSummaryView: View {
share(format: .pdf)
}
} else {
- Button("CSV Export • Available in Pro") {}
+ Button("CSV Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("JSON Export • Available in Pro") {}
+ Button("JSON Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("Markdown Export • Available in Pro") {}
+ Button("Markdown Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
- Button("PDF Export • Available in Pro") {}
+ Button("PDF Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ }
.disabled(true)
}
} label: {
diff --git a/DomainDigWidget/DomainDigPortfolioWidget.swift b/DomainDigWidget/DomainDigPortfolioWidget.swift
index f26964c..bc88ac0 100644
--- a/DomainDigWidget/DomainDigPortfolioWidget.swift
+++ b/DomainDigWidget/DomainDigPortfolioWidget.swift
@@ -7,7 +7,7 @@ struct DomainDigEntry: TimelineEntry {
}
struct DomainDigProvider: TimelineProvider {
- func placeholder(in context: Context) -> DomainDigEntry {
+ func placeholder(in _: Context) -> DomainDigEntry {
DomainDigEntry(date: Date(), data: .placeholder)
}
@@ -16,7 +16,7 @@ struct DomainDigProvider: TimelineProvider {
completion(DomainDigEntry(date: Date(), data: data))
}
- func getTimeline(in context: Context, completion: @escaping (Timeline<DomainDigEntry>) -> Void) {
+ func getTimeline(in _: Context, completion: @escaping (Timeline<DomainDigEntry>) -> Void) {
let data = DomainDigWidgetStore.read() ?? .empty
let entry = DomainDigEntry(date: Date(), data: data)
// The app reloads timelines on foreground and on watchlist changes; this
@@ -49,10 +49,9 @@ struct DomainDigWidgetView: View {
if data.totalDomains == 0 {
emptyState
} else {
- switch family {
- case .systemSmall:
+ if family == .systemSmall {
smallView
- default:
+ } else {
mediumOrLargeView
}
}
diff --git a/DomainInspectionService.swift b/DomainInspectionService.swift
index 3cdb21c..3ebfe09 100644
--- a/DomainInspectionService.swift
+++ b/DomainInspectionService.swift
@@ -295,10 +295,10 @@ struct DomainInspectionService {
let ptrRecord = mapOptionalServiceResult(ptrOutcome.map { normalizeErrors(in: $0.value) }, missingMessage: "No A record available")
let geolocation = mapOptionalServiceResult(geoOutcome.map { normalizeErrors(in: $0.value) }, missingMessage: "No A record available")
let availabilityConfidence = confidenceForAvailability(result: availability.value, provenance: provenanceBySection[.availability])
- let ownershipConfidence = confidenceForOwnership(result: ownership.value, error: ownership.message)
- let subdomainConfidence = confidenceForSubdomains(results: subdomains.value, error: subdomains.message)
- let emailConfidence = confidenceForEmail(result: emailSecurity.value, error: emailSecurity.message)
- let geolocationConfidence = confidenceForGeolocation(result: geolocation.value, error: geolocation.message)
+ let ownershipConfidence = confidenceForOwnership(result: ownership.value)
+ let subdomainConfidence = confidenceForSubdomains(results: subdomains.value)
+ let emailConfidence = confidenceForEmail(result: emailSecurity.value)
+ let geolocationConfidence = confidenceForGeolocation(result: geolocation.value)
let validationIssues = validationIssues(for: normalizedDomain, snapshotTimestamp: startedAt, availability: availability.value, dnsSections: dnsSections.value, provenanceBySection: provenanceBySection)
let snapshot = LookupSnapshot(
@@ -580,21 +580,21 @@ struct DomainInspectionService {
return .low
}
- private func confidenceForOwnership(result: DomainOwnership?, error: String?) -> ConfidenceLevel {
- guard let result else { return error == nil ? .low : .low }
+ private func confidenceForOwnership(result: DomainOwnership?) -> ConfidenceLevel {
+ guard let result else { return .low }
let hasDirectRegistrationData = result.registrar != nil || result.createdDate != nil || result.expirationDate != nil
return hasDirectRegistrationData ? .high : .medium
}
- private func confidenceForSubdomains(results: [DiscoveredSubdomain], error: String?) -> ConfidenceLevel {
+ private func confidenceForSubdomains(results: [DiscoveredSubdomain]) -> ConfidenceLevel {
if !results.isEmpty {
return .medium
}
- return error == nil ? .low : .low
+ return .low
}
- private func confidenceForEmail(result: EmailSecurityResult?, error: String?) -> ConfidenceLevel {
- guard let result else { return error == nil ? .low : .low }
+ private func confidenceForEmail(result: EmailSecurityResult?) -> ConfidenceLevel {
+ guard let result else { return .low }
let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found, result.bimi.found, result.mtaSts?.txtFound == true]
.filter { $0 }
.count
@@ -607,8 +607,8 @@ struct DomainInspectionService {
return .low
}
- private func confidenceForGeolocation(result: IPGeolocation?, error: String?) -> ConfidenceLevel {
- guard let result else { return error == nil ? .low : .low }
+ private func confidenceForGeolocation(result: IPGeolocation?) -> ConfidenceLevel {
+ guard let result else { return .low }
if result.city != nil && result.country_name != nil && result.latitude != nil && result.longitude != nil {
return .high
}
diff --git a/DomainReportExporter.swift b/DomainReportExporter.swift
index a8e2ee9..232cd88 100644
--- a/DomainReportExporter.swift
+++ b/DomainReportExporter.swift
@@ -11,8 +11,8 @@ enum DomainExportFormat: String, CaseIterable, Identifiable, Codable {
case markdown = "md"
case pdf = "pdf"
- var id: String { rawValue }
var fileExtension: String { rawValue }
+ var id: String { fileExtension }
var title: String {
switch self {
diff --git a/RELEASE_ROADMAP.md b/RELEASE_ROADMAP.md
index c09639f..30158e7 100644
--- a/RELEASE_ROADMAP.md
+++ b/RELEASE_ROADMAP.md
@@ -6,7 +6,7 @@ ports, geolocation, subdomains, availability). The next several releases invest
in *reach and surfacing* — getting that data onto more iOS surfaces and into more
workflows — rather than adding raw protocol checks.
-Current version: `v4.8.2`.
+Current version: `v4.8.3`.
## v4.4.1 Patch: Release Readiness — ✅ shipped
@@ -154,6 +154,37 @@ Goal: close the UAT follow-ups and make failures legible instead of silent.
synced against App Store Connect. Registered in the project without target
membership so it is not bundled into shipping builds.
+## v4.8.3 Patch: Static Analysis Cleanup — ✅ shipped
+
+Goal: clear the SonarCloud new-code backlog without changing behavior.
+
+- **Dead confidence conditionals fixed** (4 bugs) —
+ `DomainInspectionService`'s `confidenceFor*` helpers each returned
+ `error == nil ? .low : .low`. The conditional was inert, so the unused `error`
+ parameter was dropped alongside it.
+- **Identical switch branches merged** — 14 sites in `DomainViewModel` handled
+ `.empty(message)` and `.error(message)` with byte-identical bodies; they now
+ share one `case let .empty(message), let .error(message):`.
+- **Duplicate implementations consolidated** — `clearPresentedResults()` now
+ delegates to `reset()`, `String.nonEmpty` was folded into `nilIfEmpty`, and
+ `ExportFormat.id` derives from `fileExtension`.
+- **Nested ternaries extracted** — grade-to-tone and impact-to-color mappings
+ became `TLSGrade.tone`, `EmailSecurityGrade.tone`, and
+ `ChangeImpactClassification.color`, replacing `ContentView`'s private
+ `impactColor` and the duplicate mapping in `BatchResultsView`.
+- **Remaining smells** — empty closures and singleton inits documented, unused
+ protocol-conformance parameters marked `_`, `CloudSyncTrigger.import` renamed
+ to `imported` (raw value preserved), `_serverTrust`/`_tlsMetadata` renamed,
+ nested `if`s merged in the DER parser, and deep closure nesting flattened in
+ `PortScanService` and `IntegrationService`.
+
+Left open deliberately: `swift:S107` (initializer parameter counts on model
+memberwise inits), `swift:S115` (constants mirroring DoH/ipapi JSON keys),
+`swift:S1075` (false positives on `https://` literals), and two `swift:S117`
+hits on SwiftUI `$binding` shorthand in `AuditModeView`, which cannot be
+renamed. These want a *Won't Fix* / *Safe* resolution in SonarCloud rather than
+a code change.
+
## v5.0.0 Major: Contract Stabilization & Engineering Health
Goal: earn long-term compatibility promises — and pay down the debt that the