krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

3f0b7746b1bee4c2f626eb805e3fd13c5f2f5d22

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-04-24T20:04:54Z

DomainDig v3.8.0 — Smart Monitoring

* Stable domain intervals
* Frequent changes decrease intervals
* Duplicate states do not trigger alerts
* Quiet hours suppress alerts
* Quiet hours across midnight work correctly
* Sensitivity levels alter behavior as expected
 DomainDataPortabilityService.swift                 |  30 +-
 DomainDig.xcodeproj/project.pbxproj                |   8 +-
 .../xcshareddata/xcschemes/DomainDig.xcscheme      |  85 +++
 .../xcschemes/xcschememanagement.plist             |   8 +
 DomainDig/BatchResultsView.swift                   |  28 +-
 DomainDig/CloudSyncService.swift                   |  22 +
 DomainDig/ContentView.swift                        | 771 ++++++++++++++-------
 DomainDig/DomainDigUI.swift                        |  17 +-
 DomainDig/DomainMonitoringService.swift            | 378 ++++++++--
 DomainDig/DomainViewModel.swift                    | 138 ++--
 DomainDig/FeatureAccessService.swift               |  14 +-
 DomainDig/HistoryView.swift                        |   3 +-
 DomainDig/LocalNotificationService.swift           |  29 +
 DomainDig/Models.swift                             | 292 +++++++-
 DomainDig/MonitoringView.swift                     |  19 +-
 DomainDig/PaywallView.swift                        |  12 +-
 DomainDig/PurchaseService.swift                    |  77 +-
 DomainDig/RootTabView.swift                        |   9 +-
 DomainDig/TimelineView.swift                       |  60 +-
 DomainDig/WatchlistView.swift                      |  96 ++-
 DomainDigCLI.swift                                 |  49 +-
 21 files changed, 1647 insertions(+), 498 deletions(-)

diff --git a/DomainDataPortabilityService.swift b/DomainDataPortabilityService.swift
index fabdfdd..ab0bc0b 100644
--- a/DomainDataPortabilityService.swift
+++ b/DomainDataPortabilityService.swift
@@ -231,7 +231,9 @@ enum DataPortabilityCSV {
                 certificateWarningLevel: certificateLevel,
                 certificateDaysRemaining: Int(row["certificateDaysRemaining"] ?? ""),
                 lastMonitoredAt: parseDate(row["lastMonitoredAt"]),
-                lastAlertAt: parseDate(row["lastAlertAt"])
+                lastAlertAt: parseDate(row["lastAlertAt"]),
+                monitoringState: MonitoringState(),
+                pendingMonitoringAlerts: []
             )
         }
     }
@@ -1104,7 +1106,9 @@ enum DomainDataPortabilityService {
             certificateWarningLevel: higherCertificateWarningLevel(existing.certificateWarningLevel, incoming.certificateWarningLevel),
             certificateDaysRemaining: winner.certificateDaysRemaining ?? existing.certificateDaysRemaining ?? incoming.certificateDaysRemaining,
             lastMonitoredAt: [existing.lastMonitoredAt, incoming.lastMonitoredAt].compactMap { $0 }.max(),
-            lastAlertAt: [existing.lastAlertAt, incoming.lastAlertAt].compactMap { $0 }.max()
+            lastAlertAt: [existing.lastAlertAt, incoming.lastAlertAt].compactMap { $0 }.max(),
+            monitoringState: preferredMonitoringState(existing.monitoringState, incoming.monitoringState),
+            pendingMonitoringAlerts: deduplicatedPendingAlerts(existing.pendingMonitoringAlerts + incoming.pendingMonitoringAlerts)
         )
     }
 
@@ -1272,10 +1276,30 @@ enum DomainDataPortabilityService {
             certificateWarningLevel: trackedDomain.certificateWarningLevel,
             certificateDaysRemaining: trackedDomain.certificateDaysRemaining,
             lastMonitoredAt: trackedDomain.lastMonitoredAt,
-            lastAlertAt: trackedDomain.lastAlertAt
+            lastAlertAt: trackedDomain.lastAlertAt,
+            monitoringState: trackedDomain.monitoringState,
+            pendingMonitoringAlerts: trackedDomain.pendingMonitoringAlerts
         )
     }
 
+    private static func preferredMonitoringState(_ lhs: MonitoringState, _ rhs: MonitoringState) -> MonitoringState {
+        let lhsDate = [lhs.lastCheck, lhs.lastAlertDate, lhs.lastChangeDate].compactMap { $0 }.max() ?? .distantPast
+        let rhsDate = [rhs.lastCheck, rhs.lastAlertDate, rhs.lastChangeDate].compactMap { $0 }.max() ?? .distantPast
+        return lhsDate >= rhsDate ? lhs : rhs
+    }
+
+    private static func deduplicatedPendingAlerts(_ alerts: [MonitoringPendingAlert]) -> [MonitoringPendingAlert] {
+        var uniqueByHash: [String: MonitoringPendingAlert] = [:]
+        for alert in alerts {
+            if let existing = uniqueByHash[alert.changeHash] {
+                uniqueByHash[alert.changeHash] = existing.detectedAt >= alert.detectedAt ? existing : alert
+            } else {
+                uniqueByHash[alert.changeHash] = alert
+            }
+        }
+        return uniqueByHash.values.sorted { $0.detectedAt < $1.detectedAt }
+    }
+
     private static func normalizedWorkflow(_ workflow: DomainWorkflow) -> DomainWorkflow {
         DomainWorkflow(
             id: workflow.id,
diff --git a/DomainDig.xcodeproj/project.pbxproj b/DomainDig.xcodeproj/project.pbxproj
index 3b1e311..d015b63 100644
--- a/DomainDig.xcodeproj/project.pbxproj
+++ b/DomainDig.xcodeproj/project.pbxproj
@@ -366,7 +366,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_ENTITLEMENTS = DomainDig/DomainDig.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 29;
+				CURRENT_PROJECT_VERSION = 30;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -383,7 +383,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.7.0;
+				MARKETING_VERSION = 3.8.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -403,7 +403,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_ENTITLEMENTS = DomainDig/DomainDig.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 29;
+				CURRENT_PROJECT_VERSION = 30;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -420,7 +420,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.7.0;
+				MARKETING_VERSION = 3.8.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
diff --git a/DomainDig.xcodeproj/xcshareddata/xcschemes/DomainDig.xcscheme b/DomainDig.xcodeproj/xcshareddata/xcschemes/DomainDig.xcscheme
new file mode 100644
index 0000000..fffe4f2
--- /dev/null
+++ b/DomainDig.xcodeproj/xcshareddata/xcschemes/DomainDig.xcscheme
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "2640"
+   version = "1.7">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES"
+      buildArchitectures = "Automatic">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "8B7800682F6090E300933221"
+               BuildableName = "DomainDig.app"
+               BlueprintName = "DomainDig"
+               ReferencedContainer = "container:DomainDig.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      shouldAutocreateTestPlan = "YES">
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES"
+      queueDebuggingEnableBacktraceRecording = "Yes">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "8B7800682F6090E300933221"
+            BuildableName = "DomainDig.app"
+            BlueprintName = "DomainDig"
+            ReferencedContainer = "container:DomainDig.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <CommandLineArguments>
+         <CommandLineArgument
+            argument = "DOMAIN_DIG_FORCE_PRO_PLUS"
+            isEnabled = "YES">
+         </CommandLineArgument>
+      </CommandLineArguments>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "8B7800682F6090E300933221"
+            BuildableName = "DomainDig.app"
+            BlueprintName = "DomainDig"
+            ReferencedContainer = "container:DomainDig.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/DomainDig.xcodeproj/xcuserdata/cmc.xcuserdatad/xcschemes/xcschememanagement.plist b/DomainDig.xcodeproj/xcuserdata/cmc.xcuserdatad/xcschemes/xcschememanagement.plist
index 06d0111..b7ae4b0 100644
--- a/DomainDig.xcodeproj/xcuserdata/cmc.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/DomainDig.xcodeproj/xcuserdata/cmc.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -15,5 +15,13 @@
 			<integer>0</integer>
 		</dict>
 	</dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>8B7800682F6090E300933221</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
 </dict>
 </plist>
diff --git a/DomainDig/BatchResultsView.swift b/DomainDig/BatchResultsView.swift
index c8dde42..e97899e 100644
--- a/DomainDig/BatchResultsView.swift
+++ b/DomainDig/BatchResultsView.swift
@@ -72,12 +72,25 @@ struct BatchResultRowView: View {
             }
 
             HStack(spacing: 10) {
-                AppStatusBadgeView(model: AppStatusFactory.availability(result.availability))
+                AppStatusBadgeView(model: availabilityBadgeModel)
                 if let riskScore = result.riskScore, let riskLevel = result.riskLevel {
                     Text("Risk \(riskScore) \(riskLevel.title)")
+                        .lineLimit(1)
+                        .minimumScaleFactor(0.85)
                 }
+            }
+            .font(appDensity.font(.caption2))
+            .foregroundStyle(.secondary)
+
+            HStack(spacing: 10) {
                 Text(result.primaryIP ?? "No IP")
+                    .lineLimit(1)
+                    .truncationMode(.middle)
+
+                Spacer(minLength: 8)
+
                 Text(result.timestamp.formatted(date: .abbreviated, time: .shortened))
+                    .lineLimit(1)
             }
             .font(appDensity.font(.caption2))
             .foregroundStyle(.secondary)
@@ -116,6 +129,19 @@ struct BatchResultRowView: View {
         }
     }
 
+    private var availabilityBadgeModel: AppStatusBadgeModel {
+        guard result.status == .failed else {
+            return AppStatusFactory.availability(result.availability)
+        }
+
+        return .init(
+            title: availabilityText,
+            systemImage: "exclamationmark.circle",
+            foregroundColor: .secondary,
+            backgroundColor: Color(.systemGray5).opacity(0.55)
+        )
+    }
+
     private var quickStatusBadge: AppStatusBadgeModel {
         switch result.status {
         case .pending:
diff --git a/DomainDig/CloudSyncService.swift b/DomainDig/CloudSyncService.swift
index 9947e08..1cf41d9 100644
--- a/DomainDig/CloudSyncService.swift
+++ b/DomainDig/CloudSyncService.swift
@@ -1453,6 +1453,8 @@ final class CloudSyncService {
             certificateDaysRemaining: trackedDomain.certificateDaysRemaining,
             lastMonitoredAt: trackedDomain.lastMonitoredAt,
             lastAlertAt: trackedDomain.lastAlertAt,
+            monitoringState: trackedDomain.monitoringState,
+            pendingMonitoringAlerts: trackedDomain.pendingMonitoringAlerts,
             collaboration: trackedDomain.collaboration
         )
     }
@@ -1476,6 +1478,8 @@ final class CloudSyncService {
             certificateDaysRemaining: winner.certificateDaysRemaining ?? lhs.certificateDaysRemaining ?? rhs.certificateDaysRemaining,
             lastMonitoredAt: [lhs.lastMonitoredAt, rhs.lastMonitoredAt].compactMap { $0 }.max(),
             lastAlertAt: [lhs.lastAlertAt, rhs.lastAlertAt].compactMap { $0 }.max(),
+            monitoringState: preferredMonitoringState(lhs.monitoringState, rhs.monitoringState),
+            pendingMonitoringAlerts: deduplicatedPendingAlerts(lhs.pendingMonitoringAlerts + rhs.pendingMonitoringAlerts),
             collaboration: preferredCollaboration(lhs.collaboration, rhs.collaboration)
         )
     }
@@ -1501,6 +1505,24 @@ final class CloudSyncService {
         return lhs.updatedAt >= rhs.updatedAt ? lhs : rhs
     }
 
+    private func preferredMonitoringState(_ lhs: MonitoringState, _ rhs: MonitoringState) -> MonitoringState {
+        let lhsDate = [lhs.lastCheck, lhs.lastAlertDate, lhs.lastChangeDate].compactMap { $0 }.max() ?? .distantPast
+        let rhsDate = [rhs.lastCheck, rhs.lastAlertDate, rhs.lastChangeDate].compactMap { $0 }.max() ?? .distantPast
+        return lhsDate >= rhsDate ? lhs : rhs
+    }
+
+    private func deduplicatedPendingAlerts(_ alerts: [MonitoringPendingAlert]) -> [MonitoringPendingAlert] {
+        var uniqueByHash: [String: MonitoringPendingAlert] = [:]
+        for alert in alerts {
+            if let existing = uniqueByHash[alert.changeHash] {
+                uniqueByHash[alert.changeHash] = existing.detectedAt >= alert.detectedAt ? existing : alert
+            } else {
+                uniqueByHash[alert.changeHash] = alert
+            }
+        }
+        return uniqueByHash.values.sorted { $0.detectedAt < $1.detectedAt }
+    }
+
     private func preferredWorkflow(_ lhs: DomainWorkflow, _ rhs: DomainWorkflow) -> DomainWorkflow {
         let lhsRank = collaborationRank(lhs.collaboration)
         let rhsRank = collaborationRank(rhs.collaboration)
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift
index 76817cd..5f119ca 100644
--- a/DomainDig/ContentView.swift
+++ b/DomainDig/ContentView.swift
@@ -19,6 +19,11 @@ enum ResultSection: String, Hashable {
     case subdomains
 }
 
+private enum LookupInputField: Hashable {
+    case singleDomain
+    case bulkDomains
+}
+
 private struct WorkflowNavigationTarget: Hashable {
     let workflowID: UUID
 }
@@ -28,7 +33,7 @@ struct ContentView: View {
     @Bindable var viewModel: DomainViewModel
     @State private var purchaseService = PurchaseService.shared
     @State private var navigationPath = NavigationPath()
-    @FocusState private var domainFieldFocused: Bool
+    @FocusState private var focusedInputField: LookupInputField?
     @State private var customPortInput = ""
     @State private var customPortsExpanded = false
     @State private var trackingNoteDraft = ""
@@ -174,13 +179,20 @@ struct ContentView: View {
                         }
                     }
                 }
+
+                ToolbarItemGroup(placement: .keyboard) {
+                    Spacer()
+                    Button("Dismiss Keyboard") {
+                        focusedInputField = nil
+                    }
+                }
             }
             .navigationDestination(for: WorkflowNavigationTarget.self) { target in
                 WorkflowDetailView(viewModel: viewModel, workflowID: target.workflowID)
             }
         }
         .onAppear {
-            domainFieldFocused = true
+            focusedInputField = .singleDomain
         }
         .task {
             await viewModel.refreshUsageCredits()
@@ -190,7 +202,24 @@ struct ContentView: View {
         }
         .onChange(of: viewModel.rerunNavigationToken) { _, _ in
             navigationPath = NavigationPath()
-            domainFieldFocused = false
+            focusedInputField = nil
+        }
+        .onChange(of: inputMode) { _, newValue in
+            viewModel.clearPresentedResults()
+            focusedInputField = newValue == .single ? .singleDomain : .bulkDomains
+        }
+        .onChange(of: viewModel.domain) { _, newValue in
+            guard inputMode == .single else { return }
+            let normalized = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
+            guard normalized != viewModel.searchedDomain else { return }
+            guard viewModel.hasRun || !viewModel.batchResults.isEmpty else { return }
+            viewModel.clearPresentedResults()
+        }
+        .onChange(of: viewModel.bulkInput) { _, newValue in
+            guard inputMode == .bulk else { return }
+            let normalized = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
+            guard !normalized.isEmpty || viewModel.hasRun || !viewModel.batchResults.isEmpty else { return }
+            viewModel.clearPresentedResults()
         }
         .sheet(item: $editingTrackedDomain) { trackedDomain in
             NavigationStack {
@@ -257,11 +286,14 @@ struct ContentView: View {
                     .padding(.vertical, appDensity.metrics.controlVerticalPadding)
                     .background(Color(.systemGray6))
                     .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
-                    .focused($domainFieldFocused)
-                    .onSubmit { viewModel.run() }
+                    .focused($focusedInputField, equals: .singleDomain)
+                    .onSubmit {
+                        focusedInputField = nil
+                        viewModel.run()
+                    }
 
                 Button {
-                    domainFieldFocused = false
+                    focusedInputField = nil
                     viewModel.run()
                 } label: {
                     Text("Run")
@@ -298,9 +330,10 @@ struct ContentView: View {
                         .padding(.vertical, appDensity.metrics.controlVerticalPadding)
                         .background(Color(.systemGray6))
                         .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
+                        .focused($focusedInputField, equals: .bulkDomains)
 
                         Button {
-                            domainFieldFocused = false
+                            focusedInputField = nil
                             viewModel.runBulkLookup()
                         } label: {
                             Text(viewModel.batchLookupRunning ? "Running Batch…" : "Run Batch")
@@ -568,7 +601,7 @@ struct ContentView: View {
             ForEach(viewModel.recentSearches, id: \.self) { domain in
                 Button {
                     viewModel.domain = domain
-                    domainFieldFocused = false
+                    focusedInputField = nil
                     viewModel.run()
                 } label: {
                     Text(domain)
@@ -822,7 +855,7 @@ struct InsightsSummaryCardView: View {
                 } else {
                     ForEach(Array(insights.enumerated()), id: \.offset) { _, insight in
                         HStack(alignment: .top, spacing: 8) {
-                            Image(systemName: "sparkline")
+                            Image(systemName: "chart.line.uptrend.xyaxis")
                                 .font(appDensity.font(.caption2))
                                 .foregroundStyle(.cyan)
                                 .padding(.top, 2)
@@ -1401,7 +1434,7 @@ struct DomainSectionView: View {
                     MessageRowView(text: pricingError, isError: false)
                         .padding(.top, 4)
                 } else if showsPricingPlaceholder {
-                    MessageRowView(text: "Pricing signals available in Data+", isError: false)
+                    MessageRowView(text: "Pricing signals available in Pro+", isError: false)
                         .padding(.top, 4)
                 }
             }
@@ -1473,8 +1506,8 @@ struct OwnershipSectionView: View {
                                 .font(appDensity.font(.caption))
                                 .foregroundStyle(.secondary)
                             Spacer()
-                            if let historyCreditStatus, let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder {
-                                Button("Load (\(historyCreditStatus.remaining) left)") {
+                            if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder {
+                                Button("Load") {
                                     onLoadHistory()
                                 }
                                 .buttonStyle(.bordered)
@@ -1500,7 +1533,7 @@ struct OwnershipSectionView: View {
                         } else if let historyError {
                             MessageRowView(text: historyError, isError: false)
                         } else if showsHistoryPlaceholder {
-                            MessageRowView(text: "Ownership history available in Data+", isError: false)
+                            MessageRowView(text: "Ownership history available in Pro+", isError: false)
                         }
                     }
                 }
@@ -1565,12 +1598,12 @@ struct SubdomainsSectionView: View {
                 } else if rows.isEmpty {
                     MessageRowView(text: error ?? "No passive subdomains found", isError: false)
                     if showsExtendedPlaceholder {
-                        MessageRowView(text: "Extended subdomain discovery available in Data+", isError: false)
+                        MessageRowView(text: "Extended subdomain discovery available in Pro+", isError: false)
                             .padding(.top, 4)
                     }
                 } else {
-                    if let extendedCreditStatus, let onLoadExtended, extendedCount == 0, !extendedLoading, !showsExtendedPlaceholder {
-                        Button("Load extended results (\(extendedCreditStatus.remaining) left)") {
+                    if let onLoadExtended, extendedCount == 0, !extendedLoading, !showsExtendedPlaceholder {
+                        Button("Load extended results") {
                             onLoadExtended()
                         }
                         .buttonStyle(.bordered)
@@ -1621,7 +1654,7 @@ struct SubdomainsSectionView: View {
                         MessageRowView(text: extendedError, isError: false)
                             .padding(.top, 4)
                     } else if showsExtendedPlaceholder {
-                        MessageRowView(text: "Extended subdomain discovery available in Data+", isError: false)
+                        MessageRowView(text: "Extended subdomain discovery available in Pro+", isError: false)
                             .padding(.top, 4)
                     }
                 }
@@ -1748,8 +1781,8 @@ struct DNSSectionView: View {
                             .font(appDensity.font(.subheadline, weight: .semibold))
                             .foregroundStyle(.cyan)
                         Spacer()
-                        if let historyCreditStatus, let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder {
-                            Button("Load (\(historyCreditStatus.remaining) left)") {
+                        if let onLoadHistory, history.isEmpty, !historyLoading, !showsHistoryPlaceholder {
+                            Button("Load") {
                                 onLoadHistory()
                             }
                             .buttonStyle(.bordered)
@@ -1782,7 +1815,7 @@ struct DNSSectionView: View {
                     } else if let historyError {
                         MessageRowView(text: historyError, isError: false)
                     } else if showsHistoryPlaceholder {
-                        MessageRowView(text: "DNS history available in Data+", isError: false)
+                        MessageRowView(text: "DNS history available in Pro+", isError: false)
                     }
                 }
             }
@@ -2444,36 +2477,94 @@ struct SettingsView: View {
     @Environment(\.appDensity) private var appDensity
     @Bindable var viewModel: DomainViewModel
     @State private var purchaseService = PurchaseService.shared
-    @State private var cloudSyncService = CloudSyncService.shared
-    @AppStorage(DNSResolverOption.userDefaultsKey)
-    private var storedResolverURL = DNSResolverOption.defaultURLString
-    @AppStorage(AppDensity.userDefaultsKey)
-    private var storedDensity = AppDensity.compact.rawValue
 
-    @State private var resolverOption: DNSResolverOption = .cloudflare
-    @State private var customResolverURL = DNSResolverOption.defaultURLString
-    @State private var showClearHistoryConfirmation = false
-    @State private var showClearCacheConfirmation = false
-    @State private var showClearWorkflowsConfirmation = false
-    @State private var showClearTrackedDomainsConfirmation = false
-    @State private var importMode: DataPortabilityImportMode = .merge
-    @State private var showBackupImporter = false
-    @State private var showTrackedDomainsImporter = false
-    @State private var showWorkflowsImporter = false
-    @State private var pendingImportPreview: DataImportPreview?
-    @State private var pendingImportError: String?
-    @State private var showReplaceImportConfirmation = false
+    var body: some View {
+        let _ = purchaseService.currentTier
 
-    private var customResolverError: String? {
-        guard resolverOption == .custom else {
-            return nil
+        List {
+            Section("Tier") {
+                LabeledContent("Status", value: purchaseService.currentTier.title)
+
+                if purchaseService.currentTier == .free {
+                    Button("Upgrade") {
+                        viewModel.isPaywallPresented = true
+                    }
+                } else {
+                    Button("Manage Subscription") {
+                        Task {
+                            await purchaseService.manageSubscription()
+                        }
+                    }
+                }
+
+                Button(purchaseService.isRestoring ? "Restoring…" : "Restore Purchases") {
+                    Task {
+                        await purchaseService.restorePurchases()
+                    }
+                }
+                .disabled(purchaseService.isRestoring || purchaseService.isPurchasing)
+
+                if let statusMessage = purchaseService.statusMessage {
+                    Text(statusMessage)
+                        .font(appDensity.font(.caption, design: .default))
+                        .foregroundStyle(.secondary)
+                }
+
+                if let errorMessage = purchaseService.errorMessage {
+                    Text(errorMessage)
+                        .font(appDensity.font(.caption, design: .default))
+                        .foregroundStyle(.red)
+                }
+            }
+
+            Section("Preferences") {
+                NavigationLink("Workflows") {
+                    WorkflowsView(viewModel: viewModel)
+                }
+
+                NavigationLink("Display") {
+                    DisplaySettingsView()
+                }
+
+                NavigationLink("History & Network") {
+                    HistoryNetworkSettingsView(viewModel: viewModel)
+                }
+            }
+
+            Section("Services") {
+                NavigationLink("iCloud Sync") {
+                    CloudSyncSettingsView()
+                }
+
+                NavigationLink("Monitoring") {
+                    MonitoringSettingsView(viewModel: viewModel)
+                }
+            }
+
+            Section("Data") {
+                NavigationLink("Import & Export") {
+                    DataPortabilitySettingsView(viewModel: viewModel)
+                }
+
+                NavigationLink("Data Management") {
+                    DataManagementSettingsView(viewModel: viewModel)
+                }
+            }
+
+            Section("About") {
+                NavigationLink("App Info") {
+                    AboutSettingsView()
+                }
+            }
         }
-        return DNSResolverOption.isValidCustomURL(customResolverURL) ? nil : "Resolver URL must start with https://"
+        .navigationTitle("More")
     }
+}
 
-    var body: some View {
-        let _ = purchaseService.currentTier
+private struct DisplaySettingsView: View {
+    @AppStorage(AppDensity.userDefaultsKey) private var storedDensity = AppDensity.compact.rawValue
 
+    var body: some View {
         Form {
             Section("Display") {
                 Picker("Density", selection: $storedDensity) {
@@ -2482,7 +2573,27 @@ struct SettingsView: View {
                     }
                 }
             }
+        }
+        .navigationTitle("Display")
+    }
+}
+
+private struct HistoryNetworkSettingsView: View {
+    @Environment(\.appDensity) private var appDensity
+    @Bindable var viewModel: DomainViewModel
+    @AppStorage(DNSResolverOption.userDefaultsKey) private var storedResolverURL = DNSResolverOption.defaultURLString
+    @AppStorage(AppDensity.userDefaultsKey) private var storedDensity = AppDensity.compact.rawValue
+
+    @State private var resolverOption: DNSResolverOption = .cloudflare
+    @State private var customResolverURL = DNSResolverOption.defaultURLString
+
+    private var customResolverError: String? {
+        guard resolverOption == .custom else { return nil }
+        return DNSResolverOption.isValidCustomURL(customResolverURL) ? nil : "Resolver URL must start with https://"
+    }
 
+    var body: some View {
+        Form {
             Section("History") {
                 Picker(
                     "Auto-prune",
@@ -2521,7 +2632,51 @@ struct SettingsView: View {
                     }
                 }
             }
+        }
+        .navigationTitle("History & Network")
+        .onAppear {
+            let currentResolverURL = storedResolverURL.trimmingCharacters(in: .whitespacesAndNewlines)
+            resolverOption = DNSResolverOption.option(for: currentResolverURL)
+            customResolverURL = resolverOption == .custom ? currentResolverURL : DNSResolverOption.defaultURLString
+        }
+        .onChange(of: resolverOption) { _, newValue in
+            guard let presetURL = newValue.urlString else {
+                storedResolverURL = customResolverURL.trimmingCharacters(in: .whitespacesAndNewlines)
+                viewModel.persistCurrentAppSettings(
+                    resolverURLString: storedResolverURL,
+                    appDensityRawValue: storedDensity
+                )
+                return
+            }
+            storedResolverURL = presetURL
+            viewModel.persistCurrentAppSettings(
+                resolverURLString: storedResolverURL,
+                appDensityRawValue: storedDensity
+            )
+        }
+        .onChange(of: customResolverURL) { _, newValue in
+            guard resolverOption == .custom else { return }
+            storedResolverURL = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
+            viewModel.persistCurrentAppSettings(
+                resolverURLString: storedResolverURL,
+                appDensityRawValue: storedDensity
+            )
+        }
+        .onChange(of: storedDensity) { _, newValue in
+            viewModel.persistCurrentAppSettings(
+                resolverURLString: storedResolverURL,
+                appDensityRawValue: newValue
+            )
+        }
+    }
+}
+
+private struct CloudSyncSettingsView: View {
+    @Environment(\.appDensity) private var appDensity
+    @State private var cloudSyncService = CloudSyncService.shared
 
+    var body: some View {
+        Form {
             Section("iCloud Sync") {
                 Toggle(
                     "Enable iCloud Sync",
@@ -2558,7 +2713,33 @@ struct SettingsView: View {
                         .foregroundStyle(.red)
                 }
             }
+        }
+        .navigationTitle("iCloud Sync")
+        .task {
+            await cloudSyncService.refreshAvailability()
+        }
+    }
+}
+
+private struct MonitoringSettingsView: View {
+    @Environment(\.appDensity) private var appDensity
+    @Bindable var viewModel: DomainViewModel
 
+    private var notificationAuthorizationLabel: String {
+        switch viewModel.monitoringNotificationStatus {
+        case .authorized, .provisional, .ephemeral:
+            return "Allowed"
+        case .denied:
+            return "Denied"
+        case .notDetermined:
+            return "Not Requested"
+        @unknown default:
+            return "Unknown"
+        }
+    }
+
+    var body: some View {
+        Form {
             Section("Monitoring") {
                 Toggle(
                     "Enable Background Monitoring",
@@ -2569,14 +2750,88 @@ struct SettingsView: View {
                 )
 
                 Picker(
-                    "Frequency",
+                    "Base Interval",
                     selection: Binding(
-                        get: { viewModel.monitoringSettings.frequency },
-                        set: { viewModel.setMonitoringFrequency($0) }
+                        get: { MonitoringBaseInterval.nearest(to: viewModel.monitoringSettings.baseInterval) },
+                        set: { viewModel.setMonitoringBaseInterval($0) }
                     )
                 ) {
-                    ForEach(MonitoringFrequency.allCases) { frequency in
-                        Text(frequency.title).tag(frequency)
+                    ForEach(MonitoringBaseInterval.allCases) { interval in
+                        Text(interval.title).tag(interval)
+                    }
+                }
+
+                Toggle(
+                    "Adaptive Monitoring",
+                    isOn: Binding(
+                        get: { viewModel.monitoringSettings.adaptiveEnabled },
+                        set: { viewModel.setMonitoringAdaptiveEnabled($0) }
+                    )
+                )
+
+                Picker(
+                    "Sensitivity",
+                    selection: Binding(
+                        get: { viewModel.monitoringSettings.sensitivity },
+                        set: { viewModel.setMonitoringSensitivity($0) }
+                    )
+                ) {
+                    ForEach(MonitoringSensitivity.allCases) { sensitivity in
+                        Text(sensitivity.title).tag(sensitivity)
+                    }
+                }
+
+                let quietHoursStart = viewModel.monitoringSettings.quietHours?.startHour ?? 22
+                let quietHoursEnd = viewModel.monitoringSettings.quietHours?.endHour ?? 7
+                Toggle(
+                    "Quiet Hours",
+                    isOn: Binding(
+                        get: { viewModel.monitoringSettings.quietHours != nil },
+                        set: { isEnabled in
+                            viewModel.setMonitoringQuietHours(
+                                startHour: quietHoursStart,
+                                endHour: quietHoursEnd,
+                                isEnabled: isEnabled
+                            )
+                        }
+                    )
+                )
+
+                if viewModel.monitoringSettings.quietHours != nil {
+                    Picker(
+                        "Quiet Starts",
+                        selection: Binding(
+                            get: { quietHoursStart },
+                            set: { startHour in
+                                viewModel.setMonitoringQuietHours(
+                                    startHour: startHour,
+                                    endHour: quietHoursEnd,
+                                    isEnabled: true
+                                )
+                            }
+                        )
+                    ) {
+                        ForEach(0..<24, id: \.self) { hour in
+                            Text(Self.monitoringHourLabel(for: hour)).tag(hour)
+                        }
+                    }
+
+                    Picker(
+                        "Quiet Ends",
+                        selection: Binding(
+                            get: { quietHoursEnd },
+                            set: { endHour in
+                                viewModel.setMonitoringQuietHours(
+                                    startHour: quietHoursStart,
+                                    endHour: endHour,
+                                    isEnabled: true
+                                )
+                            }
+                        )
+                    ) {
+                        ForEach(0..<24, id: \.self) { hour in
+                            Text(Self.monitoringHourLabel(for: hour)).tag(hour)
+                        }
                     }
                 }
 
@@ -2647,8 +2902,65 @@ struct SettingsView: View {
                         .foregroundStyle(.secondary)
                 }
             }
+        }
+        .navigationTitle("Monitoring")
+        .onAppear {
+            viewModel.refreshMonitoringState()
+            Task {
+                await viewModel.refreshMonitoringAuthorizationStatus()
+            }
+        }
+    }
 
-            Section("Data Portability") {
+    private static func monitoringHourLabel(for hour: Int) -> String {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "h a"
+        let components = DateComponents(calendar: .current, hour: hour)
+        return components.date.map(formatter.string(from:)) ?? "\(hour):00"
+    }
+}
+
+private struct DataPortabilitySettingsView: View {
+    private enum ImportTarget {
+        case backup
+        case trackedDomains
+        case workflows
+
+        var expectedKind: DataPortabilityImportKind {
+            switch self {
+            case .backup:
+                return .backup
+            case .trackedDomains:
+                return .trackedDomains
+            case .workflows:
+                return .workflows
+            }
+        }
+
+        var allowedContentTypes: [UTType] {
+            switch self {
+            case .backup:
+                return [UTType.json]
+            case .trackedDomains, .workflows:
+                return [UTType.json, UTType.commaSeparatedText]
+            }
+        }
+    }
+
+    @Environment(\.appDensity) private var appDensity
+    @Bindable var viewModel: DomainViewModel
+
+    @State private var importMode: DataPortabilityImportMode = .merge
+    @State private var activeImportTarget: ImportTarget?
+    @State private var pendingImportTarget: ImportTarget?
+    @State private var importDebugStatus: String?
+    @State private var pendingImportPreview: DataImportPreview?
+    @State private var pendingImportError: String?
+    @State private var showReplaceImportConfirmation = false
+
+    var body: some View {
+        Form {
+            Section("Import & Export") {
                 Picker("Import Mode", selection: $importMode) {
                     ForEach(DataPortabilityImportMode.allCases) { mode in
                         Text(mode.title).tag(mode)
@@ -2664,7 +2976,9 @@ struct SettingsView: View {
                 }
 
                 Button("Import Backup") {
-                    showBackupImporter = true
+                    recordImportDebugStatus("Tapped Import Backup")
+                    pendingImportTarget = .backup
+                    activeImportTarget = .backup
                 }
 
                 Menu("Export Tracked Domains") {
@@ -2677,7 +2991,9 @@ struct SettingsView: View {
                 }
 
                 Button("Import Tracked Domains") {
-                    showTrackedDomainsImporter = true
+                    recordImportDebugStatus("Tapped Import Tracked Domains")
+                    pendingImportTarget = .trackedDomains
+                    activeImportTarget = .trackedDomains
                 }
 
                 Menu("Export Workflows") {
@@ -2690,13 +3006,17 @@ struct SettingsView: View {
                 }
 
                 Button("Import Workflows") {
-                    showWorkflowsImporter = true
+                    recordImportDebugStatus("Tapped Import Workflows")
+                    pendingImportTarget = .workflows
+                    activeImportTarget = .workflows
                 }
 
                 Button("Export History") {
                     exportPortableHistoryJSON()
                 }
+            }
 
+            Section("Local Data") {
                 LabeledContent("Tracked Domains", value: "\(viewModel.dataLifecycleSummary.trackedDomains)")
                 LabeledContent("History Snapshots", value: "\(viewModel.dataLifecycleSummary.historySnapshots)")
                 LabeledContent("Workflows", value: "\(viewModel.dataLifecycleSummary.workflows)")
@@ -2714,121 +3034,18 @@ struct SettingsView: View {
                 }
             }
 
-            Section("Data") {
-                Button("Clear History", role: .destructive) {
-                    showClearHistoryConfirmation = true
-                }
-
-                Button("Clear Cache", role: .destructive) {
-                    showClearCacheConfirmation = true
-                }
-
-                Button("Clear Workflows", role: .destructive) {
-                    showClearWorkflowsConfirmation = true
-                }
-
-                Button("Clear Tracked Domains", role: .destructive) {
-                    showClearTrackedDomainsConfirmation = true
-                }
-            }
-
-            Section("Tier") {
-                LabeledContent("Status", value: purchaseService.currentTier.title)
-
-                if purchaseService.currentTier == .free {
-                    Button("Upgrade") {
-                        viewModel.isPaywallPresented = true
-                    }
-                } else {
-                    Button("Manage Subscription") {
-                        Task {
-                            await purchaseService.manageSubscription()
-                        }
-                    }
-                }
-
-                Button(purchaseService.isRestoring ? "Restoring…" : "Restore Purchases") {
-                    Task {
-                        await purchaseService.restorePurchases()
-                    }
-                }
-                .disabled(purchaseService.isRestoring || purchaseService.isPurchasing)
-
-                if let statusMessage = purchaseService.statusMessage {
-                    Text(statusMessage)
+            #if DEBUG
+            if let importDebugStatus {
+                Section("Import Debug") {
+                    Text(importDebugStatus)
                         .font(appDensity.font(.caption, design: .default))
                         .foregroundStyle(.secondary)
+                        .textSelection(.enabled)
                 }
-
-                if let errorMessage = purchaseService.errorMessage {
-                    Text(errorMessage)
-                        .font(appDensity.font(.caption, design: .default))
-                        .foregroundStyle(.red)
-                }
-            }
-
-            Section("Data+ Usage") {
-                ForEach(UsageCreditFeature.allCases) { feature in
-                    let status = viewModel.usageCredits[feature] ?? UsageCreditStatus(
-                        feature: feature,
-                        remaining: feature.defaultAllowance,
-                        total: feature.defaultAllowance,
-                        resetContext: "Resets with app version \(AppVersion.current)"
-                    )
-                    VStack(alignment: .leading, spacing: 2) {
-                        LabeledContent(feature.title, value: status.summary)
-                        Text(status.resetContext)
-                            .font(appDensity.font(.caption, design: .default))
-                            .foregroundStyle(.secondary)
-                    }
-                }
-            }
-
-            Section("Features") {
-                ForEach(FeatureAccessService.enabledFeatureLabels(), id: \.self) { label in
-                    Text(label)
-                }
-            }
-
-            Section("About") {
-                LabeledContent("Version", value: appVersion)
-                LabeledContent("Storage", value: cloudSyncService.isEnabled ? "Local-first + iCloud" : "Local-only")
-                LabeledContent("Backup Schema", value: "v\(DomainDigBackup.currentSchemaVersion)")
             }
+            #endif
         }
-        .navigationTitle("Settings")
-        .alert("Clear history?", isPresented: $showClearHistoryConfirmation) {
-            Button("Clear", role: .destructive) {
-                viewModel.clearHistory()
-            }
-            Button("Cancel", role: .cancel) {}
-        } message: {
-            Text("This removes saved lookup snapshots from this device.")
-        }
-        .alert("Clear cache?", isPresented: $showClearCacheConfirmation) {
-            Button("Clear", role: .destructive) {
-                viewModel.clearLookupCache()
-            }
-            Button("Cancel", role: .cancel) {}
-        } message: {
-            Text("This clears the in-memory lookup cache and cancels any cached in-flight work.")
-        }
-        .alert("Clear workflows?", isPresented: $showClearWorkflowsConfirmation) {
-            Button("Clear", role: .destructive) {
-                viewModel.clearWorkflows()
-            }
-            Button("Cancel", role: .cancel) {}
-        } message: {
-            Text("This removes saved workflows only. History, tracked domains, and saved reports stay intact.")
-        }
-        .alert("Clear tracked domains?", isPresented: $showClearTrackedDomainsConfirmation) {
-            Button("Clear", role: .destructive) {
-                viewModel.clearTrackedDomains()
-            }
-            Button("Cancel", role: .cancel) {}
-        } message: {
-            Text("This removes the watchlist only. History and workflows stay intact.")
-        }
+        .navigationTitle("Import & Export")
         .alert("Replace local data?", isPresented: $showReplaceImportConfirmation) {
             Button("Replace", role: .destructive) {
                 applyPendingImport()
@@ -2867,83 +3084,24 @@ struct SettingsView: View {
             }
         }
         .fileImporter(
-            isPresented: $showBackupImporter,
-            allowedContentTypes: [UTType.json],
+            isPresented: Binding(
+                get: { activeImportTarget != nil },
+                set: { if !$0 { activeImportTarget = nil } }
+            ),
+            allowedContentTypes: activeImportTarget?.allowedContentTypes ?? [UTType.json],
             allowsMultipleSelection: false
         ) { result in
-            handleImportResult(result, expectedKind: .backup)
-        }
-        .fileImporter(
-            isPresented: $showTrackedDomainsImporter,
-            allowedContentTypes: [UTType.json, UTType.commaSeparatedText],
-            allowsMultipleSelection: false
-        ) { result in
-            handleImportResult(result, expectedKind: .trackedDomains)
-        }
-        .fileImporter(
-            isPresented: $showWorkflowsImporter,
-            allowedContentTypes: [UTType.json, UTType.commaSeparatedText],
-            allowsMultipleSelection: false
-        ) { result in
-            handleImportResult(result, expectedKind: .workflows)
-        }
-        .onAppear {
-            let currentResolverURL = storedResolverURL.trimmingCharacters(in: .whitespacesAndNewlines)
-            resolverOption = DNSResolverOption.option(for: currentResolverURL)
-            customResolverURL = resolverOption == .custom ? currentResolverURL : DNSResolverOption.defaultURLString
-            viewModel.refreshMonitoringState()
-            viewModel.refreshDataLifecycleSummary()
-            Task {
-                await viewModel.refreshUsageCredits()
-                await viewModel.refreshMonitoringAuthorizationStatus()
-                await cloudSyncService.refreshAvailability()
-            }
-        }
-        .onChange(of: resolverOption) { _, newValue in
-            guard let presetURL = newValue.urlString else {
-                storedResolverURL = customResolverURL.trimmingCharacters(in: .whitespacesAndNewlines)
-                viewModel.persistCurrentAppSettings(
-                    resolverURLString: storedResolverURL,
-                    appDensityRawValue: storedDensity
-                )
+            guard let pendingImportTarget else {
+                recordImportDebugStatus("fileImporter returned with no active target")
                 return
             }
-            storedResolverURL = presetURL
-            viewModel.persistCurrentAppSettings(
-                resolverURLString: storedResolverURL,
-                appDensityRawValue: storedDensity
-            )
-        }
-        .onChange(of: customResolverURL) { _, newValue in
-            guard resolverOption == .custom else { return }
-            storedResolverURL = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
-            viewModel.persistCurrentAppSettings(
-                resolverURLString: storedResolverURL,
-                appDensityRawValue: storedDensity
-            )
+            recordImportDebugStatus("fileImporter returned for \(pendingImportTarget.expectedKind.rawValue)")
+            handleImportResult(result, expectedKind: pendingImportTarget.expectedKind)
+            self.pendingImportTarget = nil
+            self.activeImportTarget = nil
         }
-        .onChange(of: storedDensity) { _, newValue in
-            viewModel.persistCurrentAppSettings(
-                resolverURLString: storedResolverURL,
-                appDensityRawValue: newValue
-            )
-        }
-    }
-
-    private var appVersion: String {
-        AppVersion.current
-    }
-
-    private var notificationAuthorizationLabel: String {
-        switch viewModel.monitoringNotificationStatus {
-        case .authorized, .provisional, .ephemeral:
-            return "Allowed"
-        case .denied:
-            return "Denied"
-        case .notDetermined:
-            return "Not Requested"
-        @unknown default:
-            return "Unknown"
+        .onAppear {
+            viewModel.refreshDataLifecycleSummary()
         }
     }
 
@@ -2985,32 +3143,55 @@ struct SettingsView: View {
         _ result: Result<[URL], Error>,
         expectedKind: DataPortabilityImportKind
     ) {
+        DomainDebugLog.debug("DataPortabilitySettingsView.handleImportResult expectedKind=\(expectedKind.rawValue)")
+        recordImportDebugStatus("handleImportResult started for \(expectedKind.rawValue)")
         do {
-            guard let url = try result.get().first else { return }
+            let urls = try result.get()
+            guard let url = urls.first else {
+                DomainDebugLog.debug("DataPortabilitySettingsView.handleImportResult noURLReturned")
+                recordImportDebugStatus("No URL returned from picker")
+                return
+            }
+            DomainDebugLog.debug("DataPortabilitySettingsView.handleImportResult selectedURL=\(url.absoluteString)")
+            recordImportDebugStatus("Selected \(url.lastPathComponent)")
             let shouldStopAccessing = url.startAccessingSecurityScopedResource()
+            DomainDebugLog.debug("DataPortabilitySettingsView.handleImportResult securityScopeGranted=\(shouldStopAccessing)")
+            recordImportDebugStatus("Security scope granted: \(shouldStopAccessing)")
             defer {
                 if shouldStopAccessing {
                     url.stopAccessingSecurityScopedResource()
+                    DomainDebugLog.debug("DataPortabilitySettingsView.handleImportResult securityScopeReleased")
                 }
             }
 
             let data = try Data(contentsOf: url)
+            DomainDebugLog.debug("DataPortabilitySettingsView.handleImportResult dataRead bytes=\(data.count) fileName=\(url.lastPathComponent)")
+            recordImportDebugStatus("Read \(data.count) bytes from \(url.lastPathComponent)")
             let preview = try viewModel.prepareDataImport(
                 data: data,
                 fileName: url.lastPathComponent,
                 mode: importMode
             )
+            DomainDebugLog.debug("DataPortabilitySettingsView.handleImportResult previewReady previewKind=\(preview.kind.rawValue) expectedKind=\(expectedKind.rawValue)")
+            recordImportDebugStatus("Preview ready: \(preview.kind.rawValue)")
 
             guard preview.kind == expectedKind else {
-                pendingImportError = preview.kind == .backup
+                let message = preview.kind == .backup
                     ? "That file is a full backup. Use Import Backup."
                     : "That file type does not match this import action."
+                DomainDebugLog.error("DataPortabilitySettingsView.handleImportResult kindMismatch message=\(message)")
+                recordImportDebugStatus("Kind mismatch: \(message)")
+                presentImportError(message)
                 return
             }
 
-            pendingImportPreview = preview
+            DomainDebugLog.debug("DataPortabilitySettingsView.handleImportResult presentingPreview kind=\(preview.kind.rawValue)")
+            recordImportDebugStatus("Presenting preview for \(preview.kind.rawValue)")
+            presentImportPreview(preview)
         } catch {
-            pendingImportError = error.localizedDescription
+            DomainDebugLog.error("DataPortabilitySettingsView.handleImportResult failed error=\(error.localizedDescription)")
+            recordImportDebugStatus("Import failed: \(error.localizedDescription)")
+            presentImportError(error.localizedDescription)
         }
     }
 
@@ -3029,6 +3210,118 @@ struct SettingsView: View {
         formatter.dateFormat = "yyyyMMdd_HHmmss"
         return "\(formatter.string(from: Date()))_domaindig_\(suffix).\(fileExtension)"
     }
+
+    private func presentImportPreview(_ preview: DataImportPreview) {
+        Task { @MainActor in
+            try? await Task.sleep(for: .milliseconds(300))
+            DomainDebugLog.debug("DataPortabilitySettingsView.presentImportPreview kind=\(preview.kind.rawValue) fileName=\(preview.fileName)")
+            recordImportDebugStatus("Preview presented for \(preview.fileName)")
+            pendingImportPreview = preview
+        }
+    }
+
+    private func presentImportError(_ message: String) {
+        Task { @MainActor in
+            try? await Task.sleep(for: .milliseconds(300))
+            DomainDebugLog.error("DataPortabilitySettingsView.presentImportError message=\(message)")
+            recordImportDebugStatus("Error presented: \(message)")
+            pendingImportError = message
+        }
+    }
+
+    private func recordImportDebugStatus(_ message: String) {
+        #if DEBUG
+        let status = "[Import Debug] \(message)"
+        importDebugStatus = status
+        print(status)
+        #endif
+    }
+}
+
+private struct DataManagementSettingsView: View {
+    @Bindable var viewModel: DomainViewModel
+
+    @State private var showClearHistoryConfirmation = false
+    @State private var showClearCacheConfirmation = false
+    @State private var showClearWorkflowsConfirmation = false
+    @State private var showClearTrackedDomainsConfirmation = false
+
+    var body: some View {
+        Form {
+            Section("Data") {
+                Button("Clear History", role: .destructive) {
+                    showClearHistoryConfirmation = true
+                }
+
+                Button("Clear Cache", role: .destructive) {
+                    showClearCacheConfirmation = true
+                }
+
+                Button("Clear Workflows", role: .destructive) {
+                    showClearWorkflowsConfirmation = true
+                }
+
+                Button("Clear Tracked Domains", role: .destructive) {
+                    showClearTrackedDomainsConfirmation = true
+                }
+            }
+        }
+        .navigationTitle("Data Management")
+        .alert("Clear history?", isPresented: $showClearHistoryConfirmation) {
+            Button("Clear", role: .destructive) {
+                viewModel.clearHistory()
+            }
+            Button("Cancel", role: .cancel) {}
+        } message: {
+            Text("This removes saved lookup snapshots and clears monitoring run history on this device.")
+        }
+        .alert("Clear cache?", isPresented: $showClearCacheConfirmation) {
+            Button("Clear", role: .destructive) {
+                viewModel.clearLookupCache()
+            }
+            Button("Cancel", role: .cancel) {}
+        } message: {
+            Text("This clears the in-memory lookup cache and cancels any cached in-flight work.")
+        }
+        .alert("Clear workflows?", isPresented: $showClearWorkflowsConfirmation) {
+            Button("Clear", role: .destructive) {
+                viewModel.clearWorkflows()
+            }
+            Button("Cancel", role: .cancel) {}
+        } message: {
+            Text("This removes saved workflows only. History, tracked domains, and saved reports stay intact.")
+        }
+        .alert("Clear tracked domains?", isPresented: $showClearTrackedDomainsConfirmation) {
+            Button("Clear", role: .destructive) {
+                viewModel.clearTrackedDomains()
+            }
+            Button("Cancel", role: .cancel) {}
+        } message: {
+            Text("This removes the watchlist and clears monitoring run history. History and workflows stay intact.")
+        }
+    }
+}
+
+private struct AboutSettingsView: View {
+    @State private var cloudSyncService = CloudSyncService.shared
+
+    private var appVersion: String {
+        AppVersion.current
+    }
+
+    var body: some View {
+        Form {
+            Section("About") {
+                LabeledContent("Version", value: appVersion)
+                LabeledContent("Storage", value: cloudSyncService.isEnabled ? "Local-first + iCloud" : "Local-only")
+                LabeledContent("Backup Schema", value: "v\(DomainDigBackup.currentSchemaVersion)")
+            }
+        }
+        .navigationTitle("App Info")
+        .task {
+            await cloudSyncService.refreshAvailability()
+        }
+    }
 }
 
 private struct DataImportPreviewSheet: View {
diff --git a/DomainDig/DomainDigUI.swift b/DomainDig/DomainDigUI.swift
index c3caf59..592d7a7 100644
--- a/DomainDig/DomainDigUI.swift
+++ b/DomainDig/DomainDigUI.swift
@@ -235,6 +235,21 @@ struct EmptyStateCardView: View {
     let message: String
     let suggestion: String
     let systemImage: String
+    let showsCardBackground: Bool
+
+    init(
+        title: String,
+        message: String,
+        suggestion: String,
+        systemImage: String,
+        showsCardBackground: Bool = true
+    ) {
+        self.title = title
+        self.message = message
+        self.suggestion = suggestion
+        self.systemImage = systemImage
+        self.showsCardBackground = showsCardBackground
+    }
 
     var body: some View {
         VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
@@ -253,7 +268,7 @@ struct EmptyStateCardView: View {
         }
         .frame(maxWidth: .infinity, alignment: .leading)
         .padding(appDensity.metrics.cardPadding)
-        .background(Color(.systemGray6).opacity(0.45))
+        .background(showsCardBackground ? Color(.systemGray6).opacity(0.45) : Color.clear)
         .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
     }
 }
diff --git a/DomainDig/DomainMonitoringService.swift b/DomainDig/DomainMonitoringService.swift
index 56dfe3f..5034402 100644
--- a/DomainDig/DomainMonitoringService.swift
+++ b/DomainDig/DomainMonitoringService.swift
@@ -1,5 +1,6 @@
 import Foundation
 import UserNotifications
+import CryptoKit
 
 #if canImport(BackgroundTasks)
 import BackgroundTasks
@@ -132,9 +133,10 @@ final class DomainMonitoringScheduler {
         }
         #endif
 
+        let trackedDomains = MonitoringStorage.loadTrackedDomains()
         let request = BGAppRefreshTaskRequest(identifier: Self.taskIdentifier)
         request.earliestBeginDate = Date(
-            timeIntervalSinceNow: max(settings.frequency.schedulingInterval, 15 * 60)
+            timeIntervalSinceNow: max(Self.nextScheduleInterval(settings: settings, trackedDomains: trackedDomains), 15 * 60)
         )
 
         do {
@@ -149,6 +151,38 @@ final class DomainMonitoringScheduler {
         #endif
     }
 
+    private static func nextScheduleInterval(
+        settings: MonitoringSettings,
+        trackedDomains: [TrackedDomain],
+        now: Date = Date()
+    ) -> TimeInterval {
+        let monitored = MonitoringStorage.monitoredDomains(settings: settings, trackedDomains: trackedDomains)
+        guard !monitored.isEmpty else {
+            return settings.config.sanitizedBaseInterval
+        }
+
+        return monitored
+            .map { trackedDomain in
+                let state = normalizedMonitoringState(for: trackedDomain, settings: settings)
+                guard let lastCheck = trackedDomain.monitoringState.lastCheck else {
+                    return 0
+                }
+                return max(0, lastCheck.addingTimeInterval(state.currentInterval).timeIntervalSince(now))
+            }
+            .min() ?? settings.config.sanitizedBaseInterval
+    }
+
+    static func normalizedMonitoringState(
+        for trackedDomain: TrackedDomain,
+        settings: MonitoringSettings
+    ) -> MonitoringState {
+        var state = trackedDomain.monitoringState
+        if state.currentInterval <= 0 {
+            state.currentInterval = settings.config.sanitizedBaseInterval
+        }
+        return state
+    }
+
 #if canImport(BackgroundTasks)
     private func handleAppRefresh(task: BGAppRefreshTask) {
         _ = syncSchedule()
@@ -174,6 +208,7 @@ final class DomainMonitoringService {
 
     private let inspectionService = DomainInspectionService()
     private let maxHistoryEntries = 250
+    private let calendar = Calendar.current
 
     func performMonitoring(
         trigger: MonitoringRunTrigger,
@@ -200,9 +235,9 @@ final class DomainMonitoringService {
 
         var history = MonitoringStorage.loadHistoryEntries()
         var mutableTrackedDomains = trackedDomains
-        let eligibleDomains = MonitoringStorage.monitoredDomains(settings: settings, trackedDomains: mutableTrackedDomains)
+        let monitoredDomains = MonitoringStorage.monitoredDomains(settings: settings, trackedDomains: mutableTrackedDomains)
 
-        guard !eligibleDomains.isEmpty else {
+        guard !monitoredDomains.isEmpty else {
             let log = MonitoringLog(
                 timestamp: Date(),
                 trigger: trigger,
@@ -216,6 +251,24 @@ final class DomainMonitoringService {
             return MonitoringRunOutcome(success: false, message: "No domains selected for monitoring.", log: log)
         }
 
+        let eligibleDomains = monitoredDomains.filter { trackedDomain in
+            shouldInspect(trackedDomain: trackedDomain, trigger: trigger, settings: settings)
+        }
+
+        guard !eligibleDomains.isEmpty else {
+            let log = MonitoringLog(
+                timestamp: Date(),
+                trigger: trigger,
+                domainsChecked: 0,
+                changesFound: 0,
+                alertsTriggered: 0,
+                checkedDomains: [],
+                errors: []
+            )
+            saveLog(log)
+            return MonitoringRunOutcome(success: true, message: "No domains are due for monitoring.", log: log)
+        }
+
         let notificationsAuthorized: Bool
         if settings.alertsEnabled {
             notificationsAuthorized = await LocalNotificationService.shared.isAuthorizedForAlerts()
@@ -225,6 +278,7 @@ final class DomainMonitoringService {
         var results: [MonitoringDomainResult] = []
         var errors: [String] = []
         var alertsTriggered = 0
+        let now = Date()
 
         for trackedDomain in eligibleDomains {
             guard !Task.isCancelled else {
@@ -252,33 +306,32 @@ final class DomainMonitoringService {
             let alertDescriptor = alertDescriptor(
                 previousSnapshot: previousSnapshot,
                 snapshot: snapshot,
-                entry: savedEntry
+                entry: savedEntry,
+                sensitivity: settings.sensitivity
             )
 
             if let index = mutableTrackedDomains.firstIndex(where: { $0.id == trackedDomain.id }) {
-                mutableTrackedDomains[index].lastMonitoredAt = Date()
-            }
-
-            if notificationsAuthorized,
-               let alertDescriptor,
-               alertDescriptor.severity >= settings.alertFilter.minimumSeverity {
-                await LocalNotificationService.shared.notifyMonitoringAlert(
-                    domain: trackedDomain.domain,
-                    message: alertDescriptor.message,
-                    severity: alertDescriptor.severity
+                mutableTrackedDomains[index].lastMonitoredAt = now
+                let outcome = await processAdaptiveMonitoringState(
+                    trackedDomain: mutableTrackedDomains[index],
+                    snapshot: snapshot,
+                    previousSnapshot: previousSnapshot,
+                    alertDescriptor: alertDescriptor,
+                    settings: settings,
+                    notificationsAuthorized: notificationsAuthorized,
+                    now: now
                 )
-                alertsTriggered += 1
-                if let index = mutableTrackedDomains.firstIndex(where: { $0.id == trackedDomain.id }) {
-                    mutableTrackedDomains[index].lastAlertAt = Date()
-                }
+                mutableTrackedDomains[index] = outcome.trackedDomain
+                alertsTriggered += outcome.alertsTriggered
             }
 
             let result = MonitoringDomainResult(
                 domain: trackedDomain.domain,
                 historyEntryID: savedEntry?.id ?? snapshot.historyEntryID,
-                checkedAt: Date(),
-                didChange: snapshot.statusMessage == nil && savedEntry?.changeSummary?.hasChanges == true,
+                checkedAt: now,
+                didChange: alertDescriptor != nil,
                 summaryMessage: snapshot.statusMessage
+                    ?? alertDescriptor?.message
                     ?? savedEntry?.changeSummary?.message
                     ?? "No meaningful changes",
                 alertSeverity: alertDescriptor?.severity,
@@ -431,54 +484,283 @@ final class DomainMonitoringService {
         return entry
     }
 
+    private func shouldInspect(
+        trackedDomain: TrackedDomain,
+        trigger: MonitoringRunTrigger,
+        settings: MonitoringSettings
+    ) -> Bool {
+        guard trigger == .background else { return true }
+        guard let lastCheck = trackedDomain.monitoringState.lastCheck else { return true }
+        let state = DomainMonitoringScheduler.normalizedMonitoringState(for: trackedDomain, settings: settings)
+        return lastCheck.addingTimeInterval(state.currentInterval) <= Date()
+    }
+
+    private func processAdaptiveMonitoringState(
+        trackedDomain: TrackedDomain,
+        snapshot: LookupSnapshot,
+        previousSnapshot: LookupSnapshot?,
+        alertDescriptor: MonitoringAlertDescriptor?,
+        settings: MonitoringSettings,
+        notificationsAuthorized: Bool,
+        now: Date
+    ) async -> (trackedDomain: TrackedDomain, alertsTriggered: Int) {
+        var updated = trackedDomain
+        var state = DomainMonitoringScheduler.normalizedMonitoringState(for: trackedDomain, settings: settings)
+        let baseInterval = settings.config.sanitizedBaseInterval
+        let minInterval = settings.sensitivity.minimumInterval
+        let maxInterval = settings.config.maxInterval
+        let isQuiet = settings.quietHours?.contains(now, calendar: calendar) == true
+        var alertsTriggered = 0
+
+        if let alertDescriptor {
+            if state.lastChangeHash != alertDescriptor.changeHash {
+                state.lastChangeHash = alertDescriptor.changeHash
+                state.lastChangeDate = now
+                state.consecutiveStableChecks = 0
+                state.currentInterval = settings.adaptiveEnabled ? minInterval : baseInterval
+
+                if settings.alertsEnabled,
+                   alertDescriptor.severity >= settings.alertFilter.minimumSeverity {
+                    let pendingAlert = MonitoringPendingAlert(
+                        detectedAt: now,
+                        message: alertDescriptor.message,
+                        severity: alertDescriptor.severity,
+                        changeHash: alertDescriptor.changeHash
+                    )
+
+                    if isQuiet || !notificationsAuthorized {
+                        updated.pendingMonitoringAlerts = deduplicatedPendingAlerts(
+                            updated.pendingMonitoringAlerts + [pendingAlert]
+                        )
+                    } else {
+                        let pendingAlerts = deduplicatedPendingAlerts(
+                            updated.pendingMonitoringAlerts + [pendingAlert]
+                        )
+                        if pendingAlerts.count > 1 {
+                            await LocalNotificationService.shared.notifyMonitoringSummary(
+                                domain: trackedDomain.domain,
+                                alerts: pendingAlerts
+                            )
+                        } else {
+                            await LocalNotificationService.shared.notifyMonitoringAlert(
+                                domain: trackedDomain.domain,
+                                message: alertDescriptor.message,
+                                severity: alertDescriptor.severity
+                            )
+                        }
+                        alertsTriggered += 1
+                        updated.pendingMonitoringAlerts = []
+                        updated.lastAlertAt = now
+                        state.lastAlertDate = now
+                    }
+                }
+            }
+        } else {
+            state.consecutiveStableChecks += 1
+            if settings.adaptiveEnabled {
+                state.currentInterval = max(
+                    baseInterval,
+                    min(maxInterval, state.currentInterval * settings.sensitivity.intervalMultiplier)
+                )
+            } else {
+                state.currentInterval = baseInterval
+            }
+        }
+
+        if !isQuiet,
+           settings.alertsEnabled,
+           notificationsAuthorized,
+           !updated.pendingMonitoringAlerts.isEmpty,
+           alertDescriptor == nil {
+            await LocalNotificationService.shared.notifyMonitoringSummary(
+                domain: trackedDomain.domain,
+                alerts: updated.pendingMonitoringAlerts
+            )
+            alertsTriggered += 1
+            updated.pendingMonitoringAlerts = []
+            updated.lastAlertAt = now
+            state.lastAlertDate = now
+        }
+
+        state.lastCheck = now
+        updated.monitoringState = state
+        return (updated, alertsTriggered)
+    }
+
+    private func deduplicatedPendingAlerts(_ alerts: [MonitoringPendingAlert]) -> [MonitoringPendingAlert] {
+        var uniqueByHash: [String: MonitoringPendingAlert] = [:]
+        for alert in alerts {
+            if let existing = uniqueByHash[alert.changeHash] {
+                uniqueByHash[alert.changeHash] = existing.detectedAt >= alert.detectedAt ? existing : alert
+            } else {
+                uniqueByHash[alert.changeHash] = alert
+            }
+        }
+        return uniqueByHash.values.sorted { $0.detectedAt < $1.detectedAt }
+    }
+
+    private struct MonitoringAlertDescriptor {
+        let severity: MonitoringAlertSeverity
+        let message: String
+        let changeHash: String
+    }
+
+    private struct HeaderFingerprint: Encodable, Equatable {
+        let name: String
+        let value: String
+    }
+
     private func alertDescriptor(
         previousSnapshot: LookupSnapshot?,
         snapshot: LookupSnapshot,
-        entry: HistoryEntry?
-    ) -> (severity: MonitoringAlertSeverity, message: String)? {
+        entry: HistoryEntry?,
+        sensitivity: MonitoringSensitivity
+    ) -> MonitoringAlertDescriptor? {
+        guard let previousSnapshot else { return nil }
+        let previousHash = monitoringHash(for: previousSnapshot, sensitivity: sensitivity)
+        let currentHash = monitoringHash(for: snapshot, sensitivity: sensitivity)
+        guard previousHash != currentHash else { return nil }
+
         let changedLabels = Set(
-            (previousSnapshot.map { DomainDiffService.diff(from: $0, to: snapshot) } ?? [])
+            DomainDiffService.diff(from: previousSnapshot, to: snapshot)
                 .flatMap(\.items)
                 .filter(\.hasChanges)
                 .map(\.label)
         )
 
-        if changedLabels.contains("Availability") {
-            return (.critical, "Availability changed")
+        if changedLabels.contains("Nameservers") || changedLabels.contains(where: { $0.hasSuffix("Records") }) {
+            return MonitoringAlertDescriptor(severity: .warning, message: "DNS records changed", changeHash: currentHash)
         }
-        if changedLabels.contains("Primary IP") {
-            return (.critical, "Primary IP changed")
+
+        let oldCertificateLevel = DomainDiffService.certificateWarningLevel(for: previousSnapshot)
+        let newCertificateLevel = DomainDiffService.certificateWarningLevel(for: snapshot)
+        if snapshot.sslInfo?.issuer != previousSnapshot.sslInfo?.issuer
+            || snapshot.sslInfo?.validUntil != previousSnapshot.sslInfo?.validUntil
+            || (newCertificateLevel != .none && newCertificateLevel != oldCertificateLevel) {
+            return MonitoringAlertDescriptor(severity: .warning, message: "Certificate updated", changeHash: currentHash)
         }
-        if changedLabels.contains("Redirect Target") {
-            return (.critical, "Redirect target changed")
+
+        if sensitivity != .low,
+           previousSnapshot.redirectChain.map(\.url) != snapshot.redirectChain.map(\.url)
+            || previousSnapshot.redirectChain.map(\.statusCode) != snapshot.redirectChain.map(\.statusCode) {
+            return MonitoringAlertDescriptor(severity: .warning, message: "Redirect chain changed", changeHash: currentHash)
         }
 
-        let ownershipLabels: Set<String> = [
-            "Registrar",
-            "Registration Date",
-            "Expiration Date",
-            "Ownership Status",
-            "Abuse Contact"
-        ]
-        if !changedLabels.isDisjoint(with: ownershipLabels) {
-            return (.warning, "Ownership changed")
+        if sensitivity != .low,
+           filteredHTTPHeaders(previousSnapshot.httpHeaders, sensitivity: sensitivity)
+            != filteredHTTPHeaders(snapshot.httpHeaders, sensitivity: sensitivity) {
+            return MonitoringAlertDescriptor(severity: .info, message: "HTTP headers changed", changeHash: currentHash)
         }
-        if changedLabels.contains("Nameservers") || changedLabels.contains(where: { $0.hasSuffix("Records") }) {
-            return (.warning, "DNS changed")
+
+        if sensitivity == .high,
+           entry?.changeSummary?.hasChanges == true,
+           let message = entry?.changeSummary?.message {
+            return MonitoringAlertDescriptor(severity: .info, message: message, changeHash: currentHash)
         }
 
-        let oldCertificateLevel = previousSnapshot.map { DomainDiffService.certificateWarningLevel(for: $0) } ?? .none
-        let newCertificateLevel = DomainDiffService.certificateWarningLevel(for: snapshot)
-        if newCertificateLevel != .none, newCertificateLevel != oldCertificateLevel {
-            let daysRemaining = snapshot.sslInfo?.daysUntilExpiry ?? 0
-            return (.warning, "Certificate expires in \(daysRemaining) days")
+        return nil
+    }
+
+    private func monitoringHash(for snapshot: LookupSnapshot, sensitivity: MonitoringSensitivity) -> String {
+        struct DNSRecordPayload: Encodable {
+            let type: String
+            let records: [String]
+            let wildcardRecords: [String]
         }
 
-        if entry?.changeSummary?.hasChanges == true, let message = entry?.changeSummary?.message {
-            return (.info, message)
+        struct CertificatePayload: Encodable {
+            let issuer: String?
+            let commonName: String?
+            let validUntil: Date?
         }
 
-        return nil
+        struct RedirectPayload: Encodable {
+            let url: String
+            let statusCode: Int
+        }
+
+        struct HeaderPayload: Encodable {
+            let name: String
+            let value: String
+        }
+
+        struct MonitoringHashPayload: Encodable {
+            let dns: [DNSRecordPayload]
+            let certificate: CertificatePayload
+            let headers: [HeaderPayload]
+            let redirects: [RedirectPayload]
+        }
+
+        let payload = MonitoringHashPayload(
+            dns: snapshot.dnsSections
+                .sorted { $0.recordType.rawValue < $1.recordType.rawValue }
+                .map { section in
+                    DNSRecordPayload(
+                        type: section.recordType.rawValue,
+                        records: section.records
+                            .sorted { lhs, rhs in
+                                lhs.value == rhs.value ? lhs.ttl < rhs.ttl : lhs.value < rhs.value
+                            }
+                            .map { "\($0.value)|\($0.ttl)" },
+                        wildcardRecords: section.wildcardRecords
+                            .sorted { lhs, rhs in
+                                lhs.value == rhs.value ? lhs.ttl < rhs.ttl : lhs.value < rhs.value
+                            }
+                            .map { "\($0.value)|\($0.ttl)" }
+                    )
+                },
+            certificate: CertificatePayload(
+                issuer: snapshot.sslInfo?.issuer,
+                commonName: snapshot.sslInfo?.commonName,
+                validUntil: snapshot.sslInfo?.validUntil
+            ),
+            headers: filteredHTTPHeaders(snapshot.httpHeaders, sensitivity: sensitivity).map {
+                HeaderPayload(name: $0.name, value: $0.value)
+            },
+            redirects: sensitivity == .low ? [] : snapshot.redirectChain.map {
+                RedirectPayload(url: $0.url, statusCode: $0.statusCode)
+            }
+        )
+
+        let encoder = JSONEncoder()
+        if #available(iOS 11.0, macOS 10.13, *) {
+            encoder.outputFormatting = [.sortedKeys]
+        }
+        let data = (try? encoder.encode(payload)) ?? Data()
+        return SHA256.hash(data: data).compactMap { String(format: "%02x", $0) }.joined()
+    }
+
+    private func filteredHTTPHeaders(
+        _ headers: [HTTPHeader],
+        sensitivity: MonitoringSensitivity
+    ) -> [HeaderFingerprint] {
+        let mediumHeaderNames: Set<String> = [
+            "cache-control",
+            "content-security-policy",
+            "location",
+            "permissions-policy",
+            "referrer-policy",
+            "server",
+            "strict-transport-security",
+            "x-content-type-options",
+            "x-frame-options"
+        ]
+
+        return headers
+            .filter { header in
+                switch sensitivity {
+                case .low:
+                    return false
+                case .medium:
+                    return mediumHeaderNames.contains(header.name.lowercased())
+                case .high:
+                    return true
+                }
+            }
+            .map { HeaderFingerprint(name: $0.name.lowercased(), value: $0.value) }
+            .sorted { lhs, rhs in
+                lhs.name == rhs.name ? lhs.value < rhs.value : lhs.name < rhs.name
+            }
     }
 
     private static func resolvedSnapshotAfterFallback(
diff --git a/DomainDig/DomainViewModel.swift b/DomainDig/DomainViewModel.swift
index 656529c..03e9ad4 100644
--- a/DomainDig/DomainViewModel.swift
+++ b/DomainDig/DomainViewModel.swift
@@ -756,6 +756,7 @@ final class DomainViewModel {
     func clearHistory() {
         history.removeAll()
         persistHistory()
+        clearMonitoringLogs()
         refreshDataLifecycleSummary()
     }
 
@@ -779,6 +780,7 @@ final class DomainViewModel {
         refreshingTrackedDomainID = nil
         persistTrackedDomains()
         sanitizeMonitoringSelection()
+        clearMonitoringLogs()
         refreshDataLifecycleSummary()
     }
 
@@ -842,16 +844,45 @@ final class DomainViewModel {
         persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
     }
 
-    func setMonitoringFrequency(_ frequency: MonitoringFrequency) {
+    func setMonitoringBaseInterval(_ baseInterval: MonitoringBaseInterval) {
         guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else {
             upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring)
             return
         }
-        monitoringSettings.frequency = frequency
+        monitoringSettings.baseInterval = baseInterval.interval
         persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
         monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule()
     }
 
+    func setMonitoringAdaptiveEnabled(_ isEnabled: Bool) {
+        guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else {
+            upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring)
+            return
+        }
+        monitoringSettings.adaptiveEnabled = isEnabled
+        persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
+        monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule()
+    }
+
+    func setMonitoringSensitivity(_ sensitivity: MonitoringSensitivity) {
+        guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else {
+            upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring)
+            return
+        }
+        monitoringSettings.sensitivity = sensitivity
+        persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
+        monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule()
+    }
+
+    func setMonitoringQuietHours(startHour: Int, endHour: Int, isEnabled: Bool) {
+        guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else {
+            upgradePrompt = FeatureAccessService.upgradePrompt(for: .automatedMonitoring)
+            return
+        }
+        monitoringSettings.quietHours = isEnabled ? QuietHours(startHour: startHour, endHour: endHour) : nil
+        persistMonitoringSettings(localActivationConfirmed: monitoringSettings.isEnabled)
+    }
+
     func setMonitoringAlertFilter(_ filter: MonitoringAlertFilter) {
         guard FeatureAccessService.hasAccess(to: .localAlerts) else {
             monitoringSettings.alertsEnabled = false
@@ -928,6 +959,30 @@ final class DomainViewModel {
         }
     }
 
+    func monitoringIntervalLabel(for trackedDomain: TrackedDomain) -> String {
+        let state = DomainMonitoringScheduler.normalizedMonitoringState(for: trackedDomain, settings: monitoringSettings)
+        return Self.intervalLabel(for: state.currentInterval)
+    }
+
+    func monitoringStatusLabel(for trackedDomain: TrackedDomain) -> String {
+        let state = trackedDomain.monitoringState
+        if let lastChangeDate = state.lastChangeDate,
+           Date().timeIntervalSince(lastChangeDate) <= 6 * 60 * 60 {
+            return "Recently Changed"
+        }
+        if state.consecutiveStableChecks >= 2 {
+            return "Stable"
+        }
+        return trackedDomain.monitoringEnabled ? "Active" : "Paused"
+    }
+
+    private static func intervalLabel(for interval: TimeInterval) -> String {
+        let formatter = DateComponentsFormatter()
+        formatter.allowedUnits = interval < 3600 ? [.minute] : [.hour, .minute]
+        formatter.unitsStyle = .full
+        return formatter.string(from: interval) ?? "Unknown"
+    }
+
     func rerunLookup(from entry: HistoryEntry, useSnapshotResolver: Bool) {
         if useSnapshotResolver {
             UserDefaults.standard.set(entry.resolverURLString, forKey: DNSResolverOption.userDefaultsKey)
@@ -968,6 +1023,22 @@ final class DomainViewModel {
         clearLookupState()
     }
 
+    func clearPresentedResults() {
+        lookupTask?.cancel()
+        customPortScanTask?.cancel()
+        batchTask?.cancel()
+        hasRun = false
+        searchedDomain = ""
+        lastLookupDurationMs = nil
+        currentDiffSections = []
+        currentChangeSummary = nil
+        ownershipDiff = []
+        currentReport = nil
+        refreshingTrackedDomainID = nil
+        clearBatchState()
+        clearLookupState()
+    }
+
     func run() {
         let target = trimmedDomain
         guard !target.isEmpty else { return }
@@ -1222,13 +1293,6 @@ final class DomainViewModel {
         }
         guard ownershipHistory.isEmpty else { return }
 
-        let creditStatus = await UsageCreditService.shared.status(for: .ownershipHistory)
-        guard !creditStatus.isExhausted else {
-            ownershipHistoryError = "No ownership history credits remaining"
-            await refreshUsageCredits()
-            return
-        }
-
         ownershipHistoryLoading = true
         ownershipHistoryError = nil
 
@@ -1242,15 +1306,9 @@ final class DomainViewModel {
         case let .success(events):
             ownershipHistory = events
             ownershipHistoryError = nil
-            if outcome.source != .cached {
-                _ = await UsageCreditService.shared.consume(.ownershipHistory)
-            }
         case let .empty(message):
             ownershipHistory = []
             ownershipHistoryError = message
-            if outcome.source != .cached {
-                _ = await UsageCreditService.shared.consume(.ownershipHistory)
-            }
         case let .error(message):
             ownershipHistory = []
             ownershipHistoryError = conciseExternalMessage(message, fallback: "Ownership history unavailable")
@@ -1258,7 +1316,6 @@ final class DomainViewModel {
 
         ownershipHistoryLoading = false
         _ = saveHistoryEntry(replaceLatest: true)
-        await refreshUsageCredits()
     }
 
     func loadDNSHistory() async {
@@ -1269,13 +1326,6 @@ final class DomainViewModel {
         }
         guard dnsHistory.isEmpty else { return }
 
-        let creditStatus = await UsageCreditService.shared.status(for: .dnsHistory)
-        guard !creditStatus.isExhausted else {
-            dnsHistoryError = "No DNS history credits remaining"
-            await refreshUsageCredits()
-            return
-        }
-
         dnsHistoryLoading = true
         dnsHistoryError = nil
 
@@ -1289,15 +1339,9 @@ final class DomainViewModel {
         case let .success(events):
             dnsHistory = events
             dnsHistoryError = nil
-            if outcome.source != .cached {
-                _ = await UsageCreditService.shared.consume(.dnsHistory)
-            }
         case let .empty(message):
             dnsHistory = []
             dnsHistoryError = message
-            if outcome.source != .cached {
-                _ = await UsageCreditService.shared.consume(.dnsHistory)
-            }
         case let .error(message):
             dnsHistory = []
             dnsHistoryError = conciseExternalMessage(message, fallback: "DNS history unavailable")
@@ -1305,7 +1349,6 @@ final class DomainViewModel {
 
         dnsHistoryLoading = false
         _ = saveHistoryEntry(replaceLatest: true)
-        await refreshUsageCredits()
     }
 
     func loadExtendedSubdomains() async {
@@ -1316,13 +1359,6 @@ final class DomainViewModel {
         }
         guard extendedSubdomains.isEmpty else { return }
 
-        let creditStatus = await UsageCreditService.shared.status(for: .extendedSubdomains)
-        guard !creditStatus.isExhausted else {
-            extendedSubdomainsError = "No extended subdomain credits remaining"
-            await refreshUsageCredits()
-            return
-        }
-
         extendedSubdomainsLoading = true
         extendedSubdomainsError = nil
 
@@ -1335,15 +1371,9 @@ final class DomainViewModel {
         case let .success(results):
             extendedSubdomains = results
             extendedSubdomainsError = nil
-            if outcome.source != .cached {
-                _ = await UsageCreditService.shared.consume(.extendedSubdomains)
-            }
         case let .empty(message):
             extendedSubdomains = []
             extendedSubdomainsError = message
-            if outcome.source != .cached {
-                _ = await UsageCreditService.shared.consume(.extendedSubdomains)
-            }
         case let .error(message):
             extendedSubdomains = []
             extendedSubdomainsError = conciseExternalMessage(message, fallback: "Extended subdomains unavailable")
@@ -1351,7 +1381,6 @@ final class DomainViewModel {
 
         extendedSubdomainsLoading = false
         _ = saveHistoryEntry(replaceLatest: true)
-        await refreshUsageCredits()
     }
 
     func refreshUsageCredits() async {
@@ -1525,14 +1554,12 @@ final class DomainViewModel {
         }
 
         guard snapshot.statusMessage == nil else {
-            await refreshUsageCredits()
             return history.first(where: { $0.id == snapshot.historyEntryID })
         }
 
         let saveStartedAt = DomainDebugLog.signpostStart("DomainViewModel.saveHistoryEntry", domain: domain)
         let entry = saveHistoryEntry(replaceLatest: false, reuseCurrentAnalysis: true)
         DomainDebugLog.signpostEnd("DomainViewModel.saveHistoryEntry", start: saveStartedAt, domain: domain)
-        await refreshUsageCredits()
         DomainDebugLog.signpostEnd("DomainViewModel.performLookup", start: lookupStartedAt, domain: domain)
         return entry
     }
@@ -2246,6 +2273,12 @@ final class DomainViewModel {
         CloudSyncService.shared.markMonitoringSettingsChanged(localActivationConfirmed: false)
     }
 
+    private func clearMonitoringLogs() {
+        monitoringLogs.removeAll()
+        monitoringStatusMessage = nil
+        MonitoringStorage.saveLogs([])
+    }
+
     private func updateTrackedDomainAvailability(for domain: String, status: DomainAvailabilityStatus) {
         guard let index = trackedDomains.firstIndex(where: { $0.domain.caseInsensitiveCompare(domain) == .orderedSame }) else {
             return
@@ -2533,14 +2566,9 @@ final class DomainViewModel {
             return
         }
 
-        let entry: HistoryEntry?
-        if payload.snapshot.statusMessage == nil {
-            entry = saveHistoryEntry(from: payload.snapshot, replaceLatest: false, updateCurrentState: false)
-        } else {
-            entry = payload.snapshot.historyEntryID.flatMap { id in
-                history.first(where: { $0.id == id })
-            }
-        }
+        let entry = payload.snapshot.historyEntryID.flatMap { id in
+            history.first(where: { $0.id == id })
+        } ?? saveHistoryEntry(from: payload.snapshot, replaceLatest: false, updateCurrentState: false)
         let certificateWarningLevel = DomainDiffService.certificateWarningLevel(for: payload.snapshot)
         let riskAssessment = entry?.changeSummary?.riskAssessment ?? DomainInsightEngine.analyze(snapshot: payload.snapshot).riskAssessment
         let quickStatus: String
@@ -3568,7 +3596,7 @@ final class DomainViewModel {
                 }
             }
             if !DataAccessService.hasAccess(to: .extendedSubdomains) {
-                lines.append("  Extended subdomain discovery (Data+)")
+                lines.append("  Extended subdomain discovery (Pro+)")
             }
         }
 
diff --git a/DomainDig/FeatureAccessService.swift b/DomainDig/FeatureAccessService.swift
index 9fae3bd..a601b33 100644
--- a/DomainDig/FeatureAccessService.swift
+++ b/DomainDig/FeatureAccessService.swift
@@ -3,7 +3,7 @@ import Foundation
 enum FeatureTier: String, Codable, CaseIterable, Identifiable {
     case free
     case pro
-    case dataPlus
+    case proPlus = "dataPlus"
 
     var id: String { rawValue }
 
@@ -13,8 +13,8 @@ enum FeatureTier: String, Codable, CaseIterable, Identifiable {
             return "Free"
         case .pro:
             return "Pro"
-        case .dataPlus:
-            return "Data+"
+        case .proPlus:
+            return "Pro+"
         }
     }
 }
@@ -114,9 +114,9 @@ enum FeatureAccessService {
                 workflowLimit: nil,
                 batchSizeLimit: nil
             )
-        case .dataPlus:
+        case .proPlus:
             return FeatureEntitlements(
-                tier: .dataPlus,
+                tier: .proPlus,
                 capabilities: Set(FeatureCapability.allCases),
                 trackedDomainLimit: effectivelyUnlimitedTrackedDomains,
                 workflowLimit: nil,
@@ -158,7 +158,7 @@ enum FeatureAccessService {
         case .workflows, .batchOperations, .automatedMonitoring, .localAlerts, .advancedExports:
             return "Available in Pro"
         case .ownershipHistory, .dnsHistory, .extendedSubdomains, .domainPricing:
-            return "Available in Data+"
+            return "Available in Pro+"
         case .limitedTracking:
             return "Tracking is limited on Free"
         case .singleLookup, .basicHistory:
@@ -220,7 +220,7 @@ enum FeatureAccessService {
         let title: String
         switch capability {
         case .ownershipHistory, .dnsHistory, .extendedSubdomains, .domainPricing:
-            title = "Available in Data+"
+            title = "Available in Pro+"
         default:
             title = "Available in Pro"
         }
diff --git a/DomainDig/HistoryView.swift b/DomainDig/HistoryView.swift
index be7548a..3d6151c 100644
--- a/DomainDig/HistoryView.swift
+++ b/DomainDig/HistoryView.swift
@@ -22,7 +22,8 @@ struct HistoryView: View {
                     title: "No History Yet",
                     message: "History stores local snapshots of previous inspections so you can revisit and compare them later.",
                     suggestion: "Run a lookup to create your first saved snapshot.",
-                    systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90"
+                    systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90",
+                    showsCardBackground: false
                 )
                     .listRowBackground(Color(.systemGray6).opacity(0.5))
             } else {
diff --git a/DomainDig/LocalNotificationService.swift b/DomainDig/LocalNotificationService.swift
index 7b43d67..93beca5 100644
--- a/DomainDig/LocalNotificationService.swift
+++ b/DomainDig/LocalNotificationService.swift
@@ -78,6 +78,35 @@ final class LocalNotificationService {
         )
     }
 
+    func notifyMonitoringSummary(
+        domain: String,
+        alerts: [MonitoringPendingAlert]
+    ) async {
+        let summary = alerts
+            .sorted { $0.detectedAt < $1.detectedAt }
+            .prefix(2)
+            .map(\.message)
+            .joined(separator: " • ")
+        let body: String
+        if alerts.count <= 1 {
+            body = alerts.first?.message ?? "Monitoring change detected"
+        } else if summary.isEmpty {
+            body = "\(alerts.count) monitoring changes detected"
+        } else {
+            body = "\(alerts.count) monitoring changes: \(summary)"
+        }
+
+        let severity = alerts.map(\.severity).max() ?? .info
+        let interruptionLevel: UNNotificationInterruptionLevel = severity == .critical ? .timeSensitive : .active
+
+        await schedule(
+            identifier: "monitoring-summary-\(domain)-\(UUID().uuidString)",
+            title: domain,
+            body: body,
+            interruptionLevel: interruptionLevel
+        )
+    }
+
     func notifySweepComplete(summary: BatchSweepSummary) async {
         let body = "\(summary.changedDomains) changed, \(summary.warningDomains) warnings, \(summary.unchangedDomains) unchanged"
         await schedule(
diff --git a/DomainDig/Models.swift b/DomainDig/Models.swift
index 51273b4..f872aab 100644
--- a/DomainDig/Models.swift
+++ b/DomainDig/Models.swift
@@ -728,6 +728,179 @@ struct CollaborationMetadata: Codable, Equatable {
     }
 }
 
+enum MonitoringSensitivity: String, Codable, CaseIterable, Identifiable, Sendable {
+    case low
+    case medium
+    case high
+
+    var id: String { rawValue }
+
+    var title: String {
+        rawValue.capitalized
+    }
+
+    var minimumInterval: TimeInterval {
+        switch self {
+        case .low:
+            return 30 * 60
+        case .medium:
+            return 15 * 60
+        case .high:
+            return 5 * 60
+        }
+    }
+
+    var intervalMultiplier: Double {
+        switch self {
+        case .low:
+            return 1.5
+        case .medium:
+            return 1.25
+        case .high:
+            return 1.1
+        }
+    }
+}
+
+struct QuietHours: Codable, Equatable, Sendable {
+    var startHour: Int
+    var endHour: Int
+
+    init(startHour: Int, endHour: Int) {
+        self.startHour = max(0, min(23, startHour))
+        self.endHour = max(0, min(23, endHour))
+    }
+
+    func contains(_ date: Date, calendar: Calendar = .current) -> Bool {
+        let hour = calendar.component(.hour, from: date)
+        if startHour <= endHour {
+            return hour >= startHour && hour < endHour
+        }
+        return hour >= startHour || hour < endHour
+    }
+}
+
+enum MonitoringBaseInterval: String, Codable, CaseIterable, Identifiable {
+    case thirtyMinutes
+    case hourly
+    case sixHours
+    case twelveHours
+    case daily
+
+    var id: String { rawValue }
+
+    var title: String {
+        switch self {
+        case .thirtyMinutes:
+            return "Every 30 Minutes"
+        case .hourly:
+            return "Hourly"
+        case .sixHours:
+            return "Every 6 Hours"
+        case .twelveHours:
+            return "Every 12 Hours"
+        case .daily:
+            return "Daily"
+        }
+    }
+
+    var interval: TimeInterval {
+        switch self {
+        case .thirtyMinutes:
+            return 30 * 60
+        case .hourly:
+            return 60 * 60
+        case .sixHours:
+            return 6 * 60 * 60
+        case .twelveHours:
+            return 12 * 60 * 60
+        case .daily:
+            return 24 * 60 * 60
+        }
+    }
+
+    static func nearest(to interval: TimeInterval) -> MonitoringBaseInterval {
+        allCases.min { abs($0.interval - interval) < abs($1.interval - interval) } ?? .twelveHours
+    }
+}
+
+struct MonitoringConfig: Codable, Equatable, Sendable {
+    var isEnabled: Bool
+    var baseInterval: TimeInterval
+    var adaptiveEnabled: Bool
+    var sensitivity: MonitoringSensitivity
+    var quietHours: QuietHours?
+
+    init(
+        isEnabled: Bool = false,
+        baseInterval: TimeInterval = MonitoringBaseInterval.twelveHours.interval,
+        adaptiveEnabled: Bool = true,
+        sensitivity: MonitoringSensitivity = .medium,
+        quietHours: QuietHours? = nil
+    ) {
+        self.isEnabled = isEnabled
+        self.baseInterval = baseInterval
+        self.adaptiveEnabled = adaptiveEnabled
+        self.sensitivity = sensitivity
+        self.quietHours = quietHours
+    }
+
+    var maxInterval: TimeInterval {
+        24 * 60 * 60
+    }
+
+    var sanitizedBaseInterval: TimeInterval {
+        max(5 * 60, min(baseInterval, maxInterval))
+    }
+}
+
+struct MonitoringState: Codable, Equatable, Sendable {
+    var lastCheck: Date?
+    var lastChangeHash: String?
+    var lastAlertDate: Date?
+    var consecutiveStableChecks: Int
+    var currentInterval: TimeInterval
+    var lastChangeDate: Date?
+
+    init(
+        lastCheck: Date? = nil,
+        lastChangeHash: String? = nil,
+        lastAlertDate: Date? = nil,
+        consecutiveStableChecks: Int = 0,
+        currentInterval: TimeInterval = 0,
+        lastChangeDate: Date? = nil
+    ) {
+        self.lastCheck = lastCheck
+        self.lastChangeHash = lastChangeHash
+        self.lastAlertDate = lastAlertDate
+        self.consecutiveStableChecks = consecutiveStableChecks
+        self.currentInterval = currentInterval
+        self.lastChangeDate = lastChangeDate
+    }
+}
+
+struct MonitoringPendingAlert: Codable, Identifiable, Equatable, Sendable {
+    let id: UUID
+    var detectedAt: Date
+    var message: String
+    var severity: MonitoringAlertSeverity
+    var changeHash: String
+
+    init(
+        id: UUID = UUID(),
+        detectedAt: Date,
+        message: String,
+        severity: MonitoringAlertSeverity,
+        changeHash: String
+    ) {
+        self.id = id
+        self.detectedAt = detectedAt
+        self.message = message
+        self.severity = severity
+        self.changeHash = changeHash
+    }
+}
+
 struct TrackedDomain: Codable, Identifiable, Equatable {
     let id: UUID
     var domain: String
@@ -744,6 +917,8 @@ struct TrackedDomain: Codable, Identifiable, Equatable {
     var certificateDaysRemaining: Int?
     var lastMonitoredAt: Date?
     var lastAlertAt: Date?
+    var monitoringState: MonitoringState
+    var pendingMonitoringAlerts: [MonitoringPendingAlert]
     var collaboration: CollaborationMetadata?
 
     init(
@@ -762,6 +937,8 @@ struct TrackedDomain: Codable, Identifiable, Equatable {
         certificateDaysRemaining: Int? = nil,
         lastMonitoredAt: Date? = nil,
         lastAlertAt: Date? = nil,
+        monitoringState: MonitoringState = MonitoringState(),
+        pendingMonitoringAlerts: [MonitoringPendingAlert] = [],
         collaboration: CollaborationMetadata? = nil
     ) {
         self.id = id
@@ -779,6 +956,8 @@ struct TrackedDomain: Codable, Identifiable, Equatable {
         self.certificateDaysRemaining = certificateDaysRemaining
         self.lastMonitoredAt = lastMonitoredAt
         self.lastAlertAt = lastAlertAt
+        self.monitoringState = monitoringState
+        self.pendingMonitoringAlerts = pendingMonitoringAlerts
         self.collaboration = collaboration
     }
 
@@ -799,35 +978,12 @@ struct TrackedDomain: Codable, Identifiable, Equatable {
         certificateDaysRemaining = try container.decodeIfPresent(Int.self, forKey: .certificateDaysRemaining)
         lastMonitoredAt = try container.decodeIfPresent(Date.self, forKey: .lastMonitoredAt)
         lastAlertAt = try container.decodeIfPresent(Date.self, forKey: .lastAlertAt)
+        monitoringState = try container.decodeIfPresent(MonitoringState.self, forKey: .monitoringState) ?? MonitoringState()
+        pendingMonitoringAlerts = try container.decodeIfPresent([MonitoringPendingAlert].self, forKey: .pendingMonitoringAlerts) ?? []
         collaboration = try container.decodeIfPresent(CollaborationMetadata.self, forKey: .collaboration)
     }
 }
 
-enum MonitoringFrequency: String, Codable, CaseIterable, Identifiable {
-    case daily
-    case twiceDaily
-
-    var id: String { rawValue }
-
-    var title: String {
-        switch self {
-        case .daily:
-            return "Daily"
-        case .twiceDaily:
-            return "Twice Daily"
-        }
-    }
-
-    var schedulingInterval: TimeInterval {
-        switch self {
-        case .daily:
-            return 24 * 60 * 60
-        case .twiceDaily:
-            return 12 * 60 * 60
-        }
-    }
-}
-
 enum MonitoringScope: String, Codable, CaseIterable, Identifiable {
     case allTracked
     case selectedOnly
@@ -913,28 +1069,102 @@ enum MonitoringRunTrigger: String, Codable {
 }
 
 struct MonitoringSettings: Codable, Equatable {
-    var isEnabled: Bool
-    var frequency: MonitoringFrequency
+    private enum CodingKeys: String, CodingKey {
+        case config
+        case isEnabled
+        case frequency
+        case scope
+        case selectedDomainIDs
+        case alertFilter
+        case alertsEnabled
+    }
+
+    var config: MonitoringConfig
     var scope: MonitoringScope
     var selectedDomainIDs: [UUID]
     var alertFilter: MonitoringAlertFilter
     var alertsEnabled: Bool
 
     init(
-        isEnabled: Bool = false,
-        frequency: MonitoringFrequency = .daily,
+        config: MonitoringConfig = MonitoringConfig(),
         scope: MonitoringScope = .allTracked,
         selectedDomainIDs: [UUID] = [],
         alertFilter: MonitoringAlertFilter = .criticalAndWarnings,
         alertsEnabled: Bool = false
     ) {
-        self.isEnabled = isEnabled
-        self.frequency = frequency
+        self.config = config
         self.scope = scope
         self.selectedDomainIDs = selectedDomainIDs
         self.alertFilter = alertFilter
         self.alertsEnabled = alertsEnabled
     }
+
+    init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        let legacyEnabled = try container.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? false
+        let legacyFrequencyRaw = try container.decodeIfPresent(String.self, forKey: .frequency)
+
+        if let decodedConfig = try container.decodeIfPresent(MonitoringConfig.self, forKey: .config) {
+            config = decodedConfig
+        } else {
+            let mappedInterval: TimeInterval
+            switch legacyFrequencyRaw {
+            case "daily":
+                mappedInterval = MonitoringBaseInterval.daily.interval
+            case "twiceDaily":
+                mappedInterval = MonitoringBaseInterval.twelveHours.interval
+            default:
+                mappedInterval = MonitoringBaseInterval.twelveHours.interval
+            }
+
+            config = MonitoringConfig(
+                isEnabled: legacyEnabled,
+                baseInterval: mappedInterval,
+                adaptiveEnabled: true,
+                sensitivity: .medium,
+                quietHours: nil
+            )
+        }
+
+        scope = try container.decodeIfPresent(MonitoringScope.self, forKey: .scope) ?? .allTracked
+        selectedDomainIDs = try container.decodeIfPresent([UUID].self, forKey: .selectedDomainIDs) ?? []
+        alertFilter = try container.decodeIfPresent(MonitoringAlertFilter.self, forKey: .alertFilter) ?? .criticalAndWarnings
+        alertsEnabled = try container.decodeIfPresent(Bool.self, forKey: .alertsEnabled) ?? false
+    }
+
+    func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        try container.encode(config, forKey: .config)
+        try container.encode(scope, forKey: .scope)
+        try container.encode(selectedDomainIDs, forKey: .selectedDomainIDs)
+        try container.encode(alertFilter, forKey: .alertFilter)
+        try container.encode(alertsEnabled, forKey: .alertsEnabled)
+    }
+
+    var isEnabled: Bool {
+        get { config.isEnabled }
+        set { config.isEnabled = newValue }
+    }
+
+    var baseInterval: TimeInterval {
+        get { config.baseInterval }
+        set { config.baseInterval = newValue }
+    }
+
+    var adaptiveEnabled: Bool {
+        get { config.adaptiveEnabled }
+        set { config.adaptiveEnabled = newValue }
+    }
+
+    var sensitivity: MonitoringSensitivity {
+        get { config.sensitivity }
+        set { config.sensitivity = newValue }
+    }
+
+    var quietHours: QuietHours? {
+        get { config.quietHours }
+        set { config.quietHours = newValue }
+    }
 }
 
 struct MonitoringDomainResult: Codable, Identifiable, Equatable {
diff --git a/DomainDig/MonitoringView.swift b/DomainDig/MonitoringView.swift
index 0b7a916..fc0b8ab 100644
--- a/DomainDig/MonitoringView.swift
+++ b/DomainDig/MonitoringView.swift
@@ -17,7 +17,14 @@ struct MonitoringView: View {
                 VStack(alignment: .leading, spacing: 8) {
                     LabeledContent("Status", value: viewModel.monitoringSettings.isEnabled ? "Scheduled" : "Manual only")
                     LabeledContent("Domains", value: "\(monitoredDomainsCount)")
-                    LabeledContent("Frequency", value: viewModel.monitoringSettings.frequency.title)
+                    LabeledContent("Base Interval", value: MonitoringBaseInterval.nearest(to: viewModel.monitoringSettings.baseInterval).title)
+                    LabeledContent("Adaptive", value: viewModel.monitoringSettings.adaptiveEnabled ? viewModel.monitoringSettings.sensitivity.title : "Off")
+                    LabeledContent(
+                        "Quiet Hours",
+                        value: viewModel.monitoringSettings.quietHours.map {
+                            "\(Self.hourLabel($0.startHour)) to \(Self.hourLabel($0.endHour))"
+                        } ?? "Off"
+                    )
                     LabeledContent("Alerts", value: viewModel.monitoringSettings.alertsEnabled ? viewModel.monitoringSettings.alertFilter.title : "Off")
 
                     if let monitoringStatusMessage = viewModel.monitoringStatusMessage,
@@ -42,7 +49,8 @@ struct MonitoringView: View {
                         title: "No Monitoring Runs Yet",
                         message: "Monitoring history appears here after manual or background runs finish.",
                         suggestion: "Enable monitoring in Settings or run a manual monitoring sweep.",
-                        systemImage: "waveform.path.ecg"
+                        systemImage: "waveform.path.ecg",
+                        showsCardBackground: false
                     )
                 }
                 .listRowBackground(Color(.systemGray6).opacity(0.5))
@@ -102,6 +110,13 @@ struct MonitoringView: View {
             .background(tint.opacity(0.16))
             .clipShape(Capsule())
     }
+
+    private static func hourLabel(_ hour: Int) -> String {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "h a"
+        let components = DateComponents(calendar: .current, hour: hour)
+        return components.date.map(formatter.string(from:)) ?? "\(hour):00"
+    }
 }
 
 struct MonitoringLogDetailView: View {
diff --git a/DomainDig/PaywallView.swift b/DomainDig/PaywallView.swift
index 90578c7..cad8be4 100644
--- a/DomainDig/PaywallView.swift
+++ b/DomainDig/PaywallView.swift
@@ -10,7 +10,7 @@ struct PaywallView: View {
         NavigationStack {
             List {
                 Section {
-                    Text("Pro unlocks workflows, scale, monitoring automation, and exports. Data+ adds deeper external intelligence with local-first usage credits and no account requirement.")
+                    Text("Pro unlocks workflows, scale, monitoring automation, and exports. Pro+ adds deeper external intelligence with no additional usage limits.")
                         .font(appDensity.font(.body, design: .default))
                         .foregroundStyle(.secondary)
                         .fixedSize(horizontal: false, vertical: true)
@@ -25,7 +25,7 @@ struct PaywallView: View {
                     featureRow("Advanced exports")
                 }
 
-                Section("What Data+ Unlocks") {
+                Section("What Pro+ Unlocks") {
                     featureRow("Ownership history")
                     featureRow("DNS history")
                     featureRow("Extended subdomains")
@@ -125,10 +125,10 @@ struct PaywallView: View {
             return "Pro Monthly"
         case PurchaseService.yearlyProductID:
             return "Pro Yearly"
-        case PurchaseService.dataPlusMonthlyProductID:
-            return "Data+ Monthly"
-        case PurchaseService.dataPlusYearlyProductID:
-            return "Data+ Yearly"
+        case PurchaseService.proPlusMonthlyProductID:
+            return "Pro+ Monthly"
+        case PurchaseService.proPlusYearlyProductID:
+            return "Pro+ Yearly"
         default:
             return product.displayName
         }
diff --git a/DomainDig/PurchaseService.swift b/DomainDig/PurchaseService.swift
index 20ee818..af34a33 100644
--- a/DomainDig/PurchaseService.swift
+++ b/DomainDig/PurchaseService.swift
@@ -17,18 +17,30 @@ final class PurchaseService {
     static let shared = PurchaseService()
     static let monthlyProductID = "domaindig.pro.monthly"
     static let yearlyProductID = "domaindig.pro.yearly"
-    static let dataPlusMonthlyProductID = "domaindig.dataplus.monthly"
-    static let dataPlusYearlyProductID = "domaindig.dataplus.yearly"
+    static let proPlusMonthlyProductID = "domaindig.dataplus.monthly"
+    static let proPlusYearlyProductID = "domaindig.dataplus.yearly"
     static let productIDs = [
         monthlyProductID,
         yearlyProductID,
-        dataPlusMonthlyProductID,
-        dataPlusYearlyProductID
+        proPlusMonthlyProductID,
+        proPlusYearlyProductID
     ]
 
     private static let entitlementCacheKey = "purchase.cachedEntitlement"
+    #if DEBUG
+    // Local-only screenshot/testing override. Release builds always use StoreKit entitlements.
+    private static let debugForceFreeArgument = "DOMAIN_DIG_FORCE_FREE"
+    private static let debugForceProArgument = "DOMAIN_DIG_FORCE_PRO"
+    private static let debugForceProPlusArgument = "DOMAIN_DIG_FORCE_PRO_PLUS"
+    #endif
 
     static var cachedEntitlement: CachedEntitlement? {
+        #if DEBUG
+        if let forcedEntitlement = debugForcedEntitlement {
+            return forcedEntitlement
+        }
+        #endif
+
         guard let data = UserDefaults.standard.data(forKey: entitlementCacheKey) else { return nil }
         return try? JSONDecoder().decode(CachedEntitlement.self, from: data)
     }
@@ -51,6 +63,7 @@ final class PurchaseService {
     private init() {
         currentTier = Self.cachedTier
         activeProductID = Self.cachedEntitlement?.activeProductID
+        applyDebugOverrideIfNeeded()
         updatesTask = observeTransactionUpdates()
         Task {
             await refreshProducts()
@@ -62,8 +75,8 @@ final class PurchaseService {
         currentTier != .free
     }
 
-    var hasDataPlusAccess: Bool {
-        currentTier == .dataPlus
+    var hasProPlusAccess: Bool {
+        currentTier == .proPlus
     }
 
     func refreshProducts() async {
@@ -104,6 +117,7 @@ final class PurchaseService {
         self.activeProductID = activeProductID
         currentTier = tier(for: activeProductID)
         persistCurrentEntitlement()
+        applyDebugOverrideIfNeeded()
     }
 
     func purchase(_ product: Product) async {
@@ -120,7 +134,7 @@ final class PurchaseService {
                 apply(transaction: transaction)
                 await transaction.finish()
                 await refreshEntitlements()
-                statusMessage = currentTier == .dataPlus ? "Data+ is active." : "Pro is active."
+                statusMessage = currentTier == .proPlus ? "Pro+ is active." : "Pro is active."
             case .userCancelled:
                 break
             case .pending:
@@ -190,6 +204,7 @@ final class PurchaseService {
         activeProductID = transaction.productID
         currentTier = tier(for: transaction.productID)
         persistCurrentEntitlement()
+        applyDebugOverrideIfNeeded()
     }
 
     private func observeTransactionUpdates() -> Task<Void, Never> {
@@ -220,6 +235,14 @@ final class PurchaseService {
         }
     }
 
+    private func applyDebugOverrideIfNeeded() {
+        #if DEBUG
+        guard let forcedEntitlement = Self.debugForcedEntitlement else { return }
+        currentTier = forcedEntitlement.tier
+        activeProductID = forcedEntitlement.activeProductID
+        #endif
+    }
+
     private func verifiedTransaction(from result: VerificationResult<Transaction>) throws -> Transaction {
         switch result {
         case .verified(let transaction):
@@ -235,9 +258,9 @@ final class PurchaseService {
             return 0
         case Self.yearlyProductID:
             return 1
-        case Self.dataPlusMonthlyProductID:
+        case Self.proPlusMonthlyProductID:
             return 2
-        case Self.dataPlusYearlyProductID:
+        case Self.proPlusYearlyProductID:
             return 3
         default:
             return Int.max
@@ -248,8 +271,8 @@ final class PurchaseService {
         switch productID {
         case Self.monthlyProductID, Self.yearlyProductID:
             return .pro
-        case Self.dataPlusMonthlyProductID, Self.dataPlusYearlyProductID:
-            return .dataPlus
+        case Self.proPlusMonthlyProductID, Self.proPlusYearlyProductID:
+            return .proPlus
         default:
             return .free
         }
@@ -268,4 +291,36 @@ final class PurchaseService {
         let message = error.localizedDescription.trimmingCharacters(in: .whitespacesAndNewlines)
         return message.isEmpty ? fallback : message
     }
+
+    #if DEBUG
+    private static var debugForcedEntitlement: CachedEntitlement? {
+        let arguments = ProcessInfo.processInfo.arguments
+
+        if arguments.contains(debugForceFreeArgument) {
+            return CachedEntitlement(
+                tier: .free,
+                activeProductID: nil,
+                updatedAt: .distantPast
+            )
+        }
+
+        if arguments.contains(debugForceProPlusArgument) {
+            return CachedEntitlement(
+                tier: .proPlus,
+                activeProductID: proPlusMonthlyProductID,
+                updatedAt: .distantPast
+            )
+        }
+
+        if arguments.contains(debugForceProArgument) {
+            return CachedEntitlement(
+                tier: .pro,
+                activeProductID: monthlyProductID,
+                updatedAt: .distantPast
+            )
+        }
+
+        return nil
+    }
+    #endif
 }
diff --git a/DomainDig/RootTabView.swift b/DomainDig/RootTabView.swift
index adf5702..06a9352 100644
--- a/DomainDig/RootTabView.swift
+++ b/DomainDig/RootTabView.swift
@@ -34,18 +34,11 @@ struct RootTabView: View {
                 Label("History", systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90")
             }
 
-            NavigationStack {
-                WorkflowsView(viewModel: viewModel)
-            }
-            .tabItem {
-                Label("Workflows", systemImage: "square.stack.3d.down.right")
-            }
-
             NavigationStack {
                 SettingsView(viewModel: viewModel)
             }
             .tabItem {
-                Label("Settings", systemImage: "gearshape")
+                Label("More", systemImage: "ellipsis.circle")
             }
         }
         .sheet(isPresented: Binding(
diff --git a/DomainDig/TimelineView.swift b/DomainDig/TimelineView.swift
index 404fc1e..b163c76 100644
--- a/DomainDig/TimelineView.swift
+++ b/DomainDig/TimelineView.swift
@@ -12,6 +12,10 @@ struct TimelineView: View {
         viewModel.timelineSections(for: domain)
     }
 
+    private var compareButtonDisabled: Bool {
+        viewModel.selectedSnapshots.count != 2 && viewModel.historyEntries(for: domain).count < 2
+    }
+
     var body: some View {
         List {
             ForEach(timelineSections) { section in
@@ -61,10 +65,16 @@ struct TimelineView: View {
                 }
 
                 Button("Compare") {
-                    presentedDiff = viewModel.generateDiffForSelectedSnapshots()
+                    if viewModel.selectedSnapshots.count == 2 {
+                        presentedDiff = viewModel.generateDiffForSelectedSnapshots()
+                    } else {
+                        let entries = viewModel.historyEntries(for: domain)
+                        guard entries.count >= 2 else { return }
+                        presentedDiff = viewModel.generateDiff(from: entries[1], to: entries[0])
+                    }
                     focusedSectionID = viewModel.currentDiffTargetSectionID
                 }
-                .disabled(viewModel.selectedSnapshots.count != 2)
+                .disabled(compareButtonDisabled)
 
                 Menu("Export") {
                     Button("Export TXT") {
@@ -119,46 +129,24 @@ private struct TimelineRow: View {
 
             HStack(spacing: 8) {
                 AppStatusBadgeView(model: AppStatusFactory.availability(summary.availability))
-                if let primaryIP = summary.primaryIP {
-                    AppStatusBadgeView(
-                        model: .init(
-                            title: primaryIP,
-                            systemImage: "network",
-                            foregroundColor: .blue,
-                            backgroundColor: .blue.opacity(0.16)
-                        )
-                    )
-                }
-                if let tlsStatus = summary.tlsStatus {
-                    AppStatusBadgeView(
-                        model: .init(
-                            title: tlsStatus.capitalized,
-                            systemImage: "lock.shield",
-                            foregroundColor: .green,
-                            backgroundColor: .green.opacity(0.16)
-                        )
-                    )
-                }
                 if let riskScore = summary.riskScore {
-                    AppStatusBadgeView(
-                        model: .init(
-                            title: "Risk \(riskScore)",
-                            systemImage: "exclamationmark.shield",
-                            foregroundColor: riskScore >= 70 ? .red : .orange,
-                            backgroundColor: (riskScore >= 70 ? Color.red : .orange).opacity(0.16)
-                        )
-                    )
+                    Text("Risk \(riskScore)")
+                        .lineLimit(1)
+                        .minimumScaleFactor(0.85)
                 }
             }
+            .font(appDensity.font(.caption2))
+            .foregroundStyle(.secondary)
 
             HStack(spacing: 8) {
-                if let snapshotIndex = summary.snapshotIndex {
-                    Text("#\(snapshotIndex)")
-                }
-                Text(summary.timestamp.formatted(.relative(presentation: .named)))
-                if summary.changeCount > 0 {
-                    Text("\(summary.changeCount) changes")
+                if let primaryIP = summary.primaryIP {
+                    Text(primaryIP)
+                        .lineLimit(1)
+                        .truncationMode(.middle)
                 }
+                Spacer(minLength: 8)
+                Text(summary.timestamp.formatted(date: .abbreviated, time: .shortened))
+                    .lineLimit(1)
             }
             .font(appDensity.font(.caption2))
             .foregroundStyle(.secondary)
diff --git a/DomainDig/WatchlistView.swift b/DomainDig/WatchlistView.swift
index 31c6202..d092f65 100644
--- a/DomainDig/WatchlistView.swift
+++ b/DomainDig/WatchlistView.swift
@@ -6,6 +6,10 @@ struct WatchlistView: View {
     @Environment(\.dismiss) private var dismiss
     @State private var purchaseService = PurchaseService.shared
     @State private var showWorkflowAddSheet = false
+    @State private var showAddDomainSheet = false
+    @State private var newTrackedDomain = ""
+    @State private var addDomainError: String?
+    @FocusState private var isAddDomainFieldFocused: Bool
 
     private var pinnedDomains: [TrackedDomain] {
         viewModel.filteredTrackedDomains.filter(\.isPinned)
@@ -53,7 +57,8 @@ struct WatchlistView: View {
                         title: "No Tracked Domains",
                         message: "Track important domains locally so you can refresh them quickly and see status changes at a glance.",
                         suggestion: "Run an inspection and use the Track action on a domain you care about.",
-                        systemImage: "eye"
+                        systemImage: "eye",
+                        showsCardBackground: false
                     )
                 }
                 .listRowBackground(Color(.systemGray6).opacity(0.5))
@@ -82,8 +87,16 @@ struct WatchlistView: View {
         .navigationTitle("Watchlist")
         .searchable(text: $viewModel.watchlistSearchText, prompt: "Search tracked domains")
         .toolbar {
-            if !viewModel.filteredTrackedDomains.isEmpty {
-                ToolbarItemGroup(placement: .topBarTrailing) {
+            ToolbarItemGroup(placement: .topBarTrailing) {
+                Button {
+                    addDomainError = nil
+                    newTrackedDomain = ""
+                    showAddDomainSheet = true
+                } label: {
+                    Image(systemName: "plus")
+                }
+
+                if !viewModel.filteredTrackedDomains.isEmpty {
                     Menu {
                         Picker("Filter", selection: $viewModel.watchlistFilter) {
                             ForEach(WatchlistFilterOption.allCases) { option in
@@ -139,6 +152,52 @@ struct WatchlistView: View {
         .sheet(item: batchSummaryBinding) { summary in
             BatchSweepSummaryView(viewModel: viewModel, summary: summary)
         }
+        .sheet(isPresented: $showAddDomainSheet) {
+            NavigationStack {
+                Form {
+                    Section("Domain") {
+                        TextField("example.com", text: $newTrackedDomain)
+                            .textInputAutocapitalization(.never)
+                            .autocorrectionDisabled()
+                            .keyboardType(.URL)
+                            .textContentType(.URL)
+                            .focused($isAddDomainFieldFocused)
+                            .onSubmit(addTrackedDomain)
+                    }
+
+                    Section {
+                        Text("Adds the domain directly to your watchlist so monitoring can run without a prior inspection.")
+                            .font(appDensity.font(.caption))
+                            .foregroundStyle(.secondary)
+                    }
+
+                    if let addDomainError {
+                        Section {
+                            Text(addDomainError)
+                                .font(appDensity.font(.caption))
+                                .foregroundStyle(.red)
+                        }
+                    }
+                }
+                .navigationTitle("Add Domain")
+                .toolbar {
+                    ToolbarItem(placement: .cancellationAction) {
+                        Button("Cancel") {
+                            showAddDomainSheet = false
+                        }
+                    }
+
+                    ToolbarItem(placement: .confirmationAction) {
+                        Button("Add", action: addTrackedDomain)
+                    }
+                }
+                .onAppear {
+                    DispatchQueue.main.async {
+                        isAddDomainFieldFocused = true
+                    }
+                }
+            }
+        }
         .sheet(isPresented: $showWorkflowAddSheet) {
             WorkflowBulkAddSheet(
                 viewModel: viewModel,
@@ -271,6 +330,24 @@ struct WatchlistView: View {
 
         ExportPresenter.share(filename: filename, data: data)
     }
+
+    private func addTrackedDomain() {
+        let draft = newTrackedDomain.trimmingCharacters(in: .whitespacesAndNewlines)
+        guard !draft.isEmpty else {
+            addDomainError = "Enter a domain to add."
+            return
+        }
+
+        if viewModel.trackDomain(domain: draft, availabilityStatus: nil) {
+            AppHaptics.track()
+            isAddDomainFieldFocused = false
+            addDomainError = nil
+            newTrackedDomain = ""
+            showAddDomainSheet = false
+        } else if viewModel.upgradePrompt == nil {
+            addDomainError = "Enter a valid domain like example.com."
+        }
+    }
 }
 
 struct WatchlistRowView: View {
@@ -468,6 +545,19 @@ struct TrackedDomainDetailView: View {
             }
             .listRowBackground(Color(.systemGray6).opacity(0.5))
 
+            Section("Monitoring Status") {
+                LabeledContent("State", value: viewModel.monitoringStatusLabel(for: liveTrackedDomain))
+                LabeledContent("Current Interval", value: viewModel.monitoringIntervalLabel(for: liveTrackedDomain))
+                LabeledContent(
+                    "Last Change",
+                    value: liveTrackedDomain.monitoringState.lastChangeDate?.formatted(date: .abbreviated, time: .shortened) ?? "None"
+                )
+                if !liveTrackedDomain.pendingMonitoringAlerts.isEmpty {
+                    LabeledContent("Queued Alerts", value: "\(liveTrackedDomain.pendingMonitoringAlerts.count)")
+                }
+            }
+            .listRowBackground(Color(.systemGray6).opacity(0.5))
+
             if let summary = viewModel.latestChangeSummary(for: liveTrackedDomain) {
                 Section("Latest Change Summary") {
                     DomainChangeSummaryView(summary: summary)
diff --git a/DomainDigCLI.swift b/DomainDigCLI.swift
index da289a9..00944ee 100644
--- a/DomainDigCLI.swift
+++ b/DomainDigCLI.swift
@@ -36,7 +36,6 @@ struct DomainDigCLI {
         let wantsDNSHistory = arguments.contains("--dns-history")
         let wantsExtendedSubdomains = arguments.contains("--extended-subdomains")
         let wantsPricing = arguments.contains("--pricing")
-        let wantsUsage = arguments.contains("--show-usage")
         let domains = arguments.filter { !$0.hasPrefix("-") }
 
         let requestedDomains = domains
@@ -52,8 +51,6 @@ struct DomainDigCLI {
         let reportBuilder = DomainReportBuilder()
         var reports: [DomainReport] = []
         var seen = Set<String>()
-        var usageImpact: [String] = []
-
         for domain in requestedDomains {
             let normalizedDomain = domain.lowercased()
             guard seen.insert(normalizedDomain).inserted else { continue }
@@ -63,8 +60,7 @@ struct DomainDigCLI {
                 wantsOwnershipHistory: wantsOwnershipHistory,
                 wantsDNSHistory: wantsDNSHistory,
                 wantsExtendedSubdomains: wantsExtendedSubdomains,
-                wantsPricing: wantsPricing,
-                usageImpact: &usageImpact
+                wantsPricing: wantsPricing
             )
             reports.append(reportBuilder.build(from: enrichedSnapshot))
         }
@@ -83,9 +79,6 @@ struct DomainDigCLI {
                     title: "DomainDig Batch Report"
                 )
             }
-            if wantsUsage, !usageImpact.isEmpty {
-                FileHandle.standardError.write(Data(("Data+ usage impact: " + usageImpact.joined(separator: ", ") + "\n").utf8))
-            }
             FileHandle.standardOutput.write(data)
             if data.last != 0x0A {
                 FileHandle.standardOutput.write(Data([0x0A]))
@@ -101,10 +94,9 @@ struct DomainDigCLI {
         wantsOwnershipHistory: Bool,
         wantsDNSHistory: Bool,
         wantsExtendedSubdomains: Bool,
-        wantsPricing: Bool,
-        usageImpact: inout [String]
+        wantsPricing: Bool
     ) async -> LookupSnapshot {
-        guard FeatureAccessService.currentTier == .dataPlus else {
+        guard FeatureAccessService.currentTier == .proPlus else {
             return snapshot
         }
 
@@ -118,8 +110,7 @@ struct DomainDigCLI {
         var domainPricing = snapshot.domainPricing
         var domainPricingError = snapshot.domainPricingError
 
-        if wantsOwnershipHistory,
-           await UsageCreditService.shared.canUse(.ownershipHistory) {
+        if wantsOwnershipHistory {
             let outcome = await ExternalDataService.shared.ownershipHistory(
                 domain: snapshot.domain,
                 currentOwnership: snapshot.ownership,
@@ -129,23 +120,14 @@ struct DomainDigCLI {
             case let .success(events):
                 ownershipHistory = events
                 ownershipHistoryError = nil
-                if outcome.source != .cached {
-                    _ = await UsageCreditService.shared.consume(.ownershipHistory)
-                    usageImpact.append("ownership history -1")
-                }
             case let .empty(message):
                 ownershipHistoryError = message
-                if outcome.source != .cached {
-                    _ = await UsageCreditService.shared.consume(.ownershipHistory)
-                    usageImpact.append("ownership history -1")
-                }
             case let .error(message):
                 ownershipHistoryError = message
             }
         }
 
-        if wantsDNSHistory,
-           await UsageCreditService.shared.canUse(.dnsHistory) {
+        if wantsDNSHistory {
             let outcome = await ExternalDataService.shared.dnsHistory(
                 domain: snapshot.domain,
                 dnsSections: snapshot.dnsSections,
@@ -155,23 +137,14 @@ struct DomainDigCLI {
             case let .success(events):
                 dnsHistory = events
                 dnsHistoryError = nil
-                if outcome.source != .cached {
-                    _ = await UsageCreditService.shared.consume(.dnsHistory)
-                    usageImpact.append("dns history -1")
-                }
             case let .empty(message):
                 dnsHistoryError = message
-                if outcome.source != .cached {
-                    _ = await UsageCreditService.shared.consume(.dnsHistory)
-                    usageImpact.append("dns history -1")
-                }
             case let .error(message):
                 dnsHistoryError = message
             }
         }
 
-        if wantsExtendedSubdomains,
-           await UsageCreditService.shared.canUse(.extendedSubdomains) {
+        if wantsExtendedSubdomains {
             let outcome = await ExternalDataService.shared.extendedSubdomains(
                 domain: snapshot.domain,
                 existing: snapshot.subdomains
@@ -180,16 +153,8 @@ struct DomainDigCLI {
             case let .success(results):
                 extendedSubdomains = results
                 extendedSubdomainsError = nil
-                if outcome.source != .cached {
-                    _ = await UsageCreditService.shared.consume(.extendedSubdomains)
-                    usageImpact.append("extended subdomains -1")
-                }
             case let .empty(message):
                 extendedSubdomainsError = message
-                if outcome.source != .cached {
-                    _ = await UsageCreditService.shared.consume(.extendedSubdomains)
-                    usageImpact.append("extended subdomains -1")
-                }
             case let .error(message):
                 extendedSubdomainsError = message
             }
@@ -538,7 +503,7 @@ struct DomainDigCLI {
 
     private static var usageText: String {
         """
-        usage: domaindig <domain> [--json] [--ownership-history] [--dns-history] [--extended-subdomains] [--pricing] [--show-usage]
+        usage: domaindig <domain> [--json] [--ownership-history] [--dns-history] [--extended-subdomains] [--pricing]
                domaindig history <domain> [--json]
                domaindig diff <domain> --from <id> --to <id> [--json]
                domaindig monitor [--json]