krz/domain-dig

an ios app for DNS & SSL analysis

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

36ea668d7fc9070770973b0bc329f4319bf43cab

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-04-22T06:20:47Z

feat(v2​.6​.0): refine ​UI, density, and usability

• add compact/comfortable density modes
• implement collapsible sections and sticky summary header
• improve visual status indicators and copy actions
• refine watchlist and history UX with swipe actions
• reorganize settings and improve empty states
• add subtle animations and accessibility improvements
 DomainDig.xcodeproj/project.pbxproj |   8 +-
 DomainDig/BatchResultsView.swift    |  54 ++--
 DomainDig/ContentView.swift         | 598 +++++++++++++++++++++++++-----------
 DomainDig/DomainDigApp.swift        |   3 +
 DomainDig/DomainDigUI.swift         | 319 +++++++++++++++++++
 DomainDig/DomainViewModel.swift     |   6 +
 DomainDig/HistoryView.swift         | 138 ++++++---
 DomainDig/LookupRuntime.swift       |   7 +
 DomainDig/WatchlistView.swift       | 252 +++++++--------
 9 files changed, 997 insertions(+), 388 deletions(-)

diff --git a/DomainDig.xcodeproj/project.pbxproj b/DomainDig.xcodeproj/project.pbxproj
index fbaa03c..1129505 100644
--- a/DomainDig.xcodeproj/project.pbxproj
+++ b/DomainDig.xcodeproj/project.pbxproj
@@ -354,7 +354,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 18;
+				CURRENT_PROJECT_VERSION = 19;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -371,7 +371,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 2.5.0;
+				MARKETING_VERSION = 2.6.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -390,7 +390,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 18;
+				CURRENT_PROJECT_VERSION = 19;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -407,7 +407,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 2.5.0;
+				MARKETING_VERSION = 2.6.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
diff --git a/DomainDig/BatchResultsView.swift b/DomainDig/BatchResultsView.swift
index 1e0333d..51f6415 100644
--- a/DomainDig/BatchResultsView.swift
+++ b/DomainDig/BatchResultsView.swift
@@ -1,11 +1,12 @@
 import SwiftUI
 
 struct BatchResultsView: View {
+    @Environment(\.appDensity) private var appDensity
     @Bindable var viewModel: DomainViewModel
     let title: String
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 12) {
+        VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
             HStack(alignment: .top) {
                 SectionTitleView(title: title)
                 Spacer()
@@ -15,18 +16,23 @@ struct BatchResultsView: View {
                             .tint(.cyan)
                             .frame(width: 120)
                         Text(viewModel.batchProgressLabel)
-                            .font(.system(.caption2, design: .monospaced))
+                            .font(appDensity.font(.caption2))
                             .foregroundStyle(.secondary)
                     }
                 } else if !viewModel.batchResults.isEmpty {
                     Text("\(viewModel.batchResults.count) domains")
-                        .font(.system(.caption2, design: .monospaced))
+                        .font(appDensity.font(.caption2))
                         .foregroundStyle(.secondary)
                 }
             }
 
             if viewModel.batchResults.isEmpty {
-                MessageCardView(text: "No batch results yet", isError: false)
+                EmptyStateCardView(
+                    title: "No Batch Results Yet",
+                    message: "Batch runs collect availability, IP, and change status for multiple domains in one pass.",
+                    suggestion: "Switch to Bulk mode, paste a list of domains, then run a batch lookup.",
+                    systemImage: "square.stack.3d.up"
+                )
             } else {
                 CardView(allowsHorizontalScroll: false) {
                     ForEach(viewModel.batchResults) { result in
@@ -48,50 +54,46 @@ struct BatchResultsView: View {
 }
 
 struct BatchResultRowView: View {
+    @Environment(\.appDensity) private var appDensity
     let result: BatchLookupResult
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 6) {
+        VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing + 1) {
             HStack(alignment: .firstTextBaseline, spacing: 8) {
                 Text(result.domain)
-                    .font(.system(.callout, design: .monospaced))
+                    .font(appDensity.font(.callout))
                     .foregroundStyle(.primary)
                     .lineLimit(1)
                 Spacer(minLength: 8)
                 Text(result.resultSource.label.lowercased())
-                    .font(.system(.caption2, design: .monospaced))
+                    .font(appDensity.font(.caption2))
                     .foregroundStyle(.secondary)
-                Text(result.quickStatus)
-                    .font(.system(.caption2, design: .monospaced))
-                    .foregroundStyle(quickStatusColor)
-                    .padding(.horizontal, 8)
-                    .padding(.vertical, 4)
-                    .background(quickStatusColor.opacity(0.16))
-                    .clipShape(Capsule())
+                AppStatusBadgeView(model: quickStatusBadge)
             }
 
             HStack(spacing: 10) {
-                Text(availabilityText)
+                AppStatusBadgeView(model: AppStatusFactory.availability(result.availability))
                 Text(result.primaryIP ?? "No IP")
                 Text(result.timestamp.formatted(date: .abbreviated, time: .shortened))
             }
-            .font(.system(.caption2, design: .monospaced))
+            .font(appDensity.font(.caption2))
             .foregroundStyle(.secondary)
 
             if let summaryMessage = result.summaryMessage {
                 Text(summaryMessage)
-                    .font(.system(.caption2, design: .monospaced))
+                    .font(appDensity.font(.caption2))
                     .foregroundStyle(.secondary)
             }
 
             if let errorMessage = result.errorMessage {
                 Text(errorMessage)
-                    .font(.system(.caption2, design: .monospaced))
+                    .font(appDensity.font(.caption2))
                     .foregroundStyle(result.status == .failed ? .red : .secondary)
             }
         }
         .frame(maxWidth: .infinity, alignment: .leading)
         .padding(.vertical, 4)
+        .frame(minHeight: appDensity.metrics.rowMinHeight + 12, alignment: .topLeading)
     }
 
     private var availabilityText: String {
@@ -105,25 +107,25 @@ struct BatchResultRowView: View {
         }
     }
 
-    private var quickStatusColor: Color {
+    private var quickStatusBadge: AppStatusBadgeModel {
         switch result.status {
         case .pending:
-            return .secondary
+            return .init(title: "Pending", systemImage: "clock", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
         case .running:
-            return .cyan
+            return .init(title: "Running", systemImage: "arrow.clockwise", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16))
         case .completed:
             if result.changeSeverity == .high || result.certificateWarningLevel == .critical {
-                return .red
+                return .init(title: "High", systemImage: "exclamationmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
             }
             if result.changeSeverity == .medium || result.certificateWarningLevel == .warning {
-                return .yellow
+                return .init(title: "Warning", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
             }
             if result.quickStatus == "Changed" {
-                return .blue
+                return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16))
             }
-            return .green
+            return .init(title: "Stable", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
         case .failed:
-            return .red
+            return .init(title: "Failed", systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
         }
     }
 }
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift
index 7a3a42d..21c37d2 100644
--- a/DomainDig/ContentView.swift
+++ b/DomainDig/ContentView.swift
@@ -8,7 +8,18 @@ enum LookupInputMode: String, CaseIterable, Identifiable {
     var id: String { rawValue }
 }
 
+enum ResultSection: String, Hashable {
+    case domain
+    case ownership
+    case dns
+    case web
+    case email
+    case network
+    case subdomains
+}
+
 struct ContentView: View {
+    @Environment(\.appDensity) private var appDensity
     @State private var viewModel = DomainViewModel()
     @State private var navigationPath = NavigationPath()
     @FocusState private var domainFieldFocused: Bool
@@ -18,6 +29,7 @@ struct ContentView: View {
     @State private var editingTrackedDomain: TrackedDomain?
     @State private var showTrackLimitAlert = false
     @State private var inputMode: LookupInputMode = .single
+    @State private var collapsedSections: Set<ResultSection> = [.network]
 
     var body: some View {
         NavigationStack(path: $navigationPath) {
@@ -26,21 +38,22 @@ struct ContentView: View {
                     inputSection
                     if !viewModel.batchResults.isEmpty || viewModel.batchLookupRunning {
                         batchSection
-                            .padding(.top, 8)
+                            .padding(.top, appDensity.metrics.cardSpacing)
                     }
                     if viewModel.hasRun {
                         actionButtons
-                        if let statusMessage = viewModel.currentStatusMessage ?? (viewModel.currentResultSource != .live ? viewModel.currentResultSource.label : nil) {
+                        if let statusMessage = resultStatusMessage {
                             LookupStatusBannerView(message: statusMessage, resultSource: viewModel.currentResultSource)
-                                .padding(.top, 8)
+                                .padding(.top, appDensity.metrics.cardSpacing)
                         }
                         SummaryView(fields: viewModel.summaryFields)
-                            .padding(.top, 8)
+                            .padding(.top, appDensity.metrics.cardSpacing)
                         if let changeSummary = viewModel.currentChangeSummary {
                             DomainChangeSummaryView(summary: changeSummary)
-                                .padding(.top, 12)
+                                .padding(.top, appDensity.metrics.cardSpacing)
                         }
                         DomainSectionView(
+                            isCollapsed: sectionCollapsedBinding(.domain),
                             rows: viewModel.domainRows,
                             suggestions: viewModel.suggestionRows,
                             showSuggestions: viewModel.availabilityResult?.status == .registered || viewModel.suggestionsLoading,
@@ -63,38 +76,42 @@ struct ContentView: View {
                                 editingTrackedDomain = trackedDomain
                             }
                         )
-                            .padding(.top, 16)
+                            .padding(.top, appDensity.metrics.sectionSpacing)
                         OwnershipSectionView(
+                            isCollapsed: sectionCollapsedBinding(.ownership),
                             rows: viewModel.ownershipRows,
                             loading: viewModel.ownershipLoading,
                             error: viewModel.ownershipError,
                             showsHistoryPlaceholder: !DataAccessService.hasAccess(to: .ownershipHistory)
                         )
-                        .padding(.top, 16)
+                        .padding(.top, appDensity.metrics.sectionSpacing)
                         SubdomainsSectionView(
+                            isCollapsed: sectionCollapsedBinding(.subdomains),
                             rows: viewModel.subdomainRows,
                             loading: viewModel.subdomainsLoading,
                             error: viewModel.subdomainsError,
                             showsExtendedPlaceholder: !DataAccessService.hasAccess(to: .extendedSubdomains)
                         )
-                        .padding(.top, 16)
+                        .padding(.top, appDensity.metrics.sectionSpacing)
                         if !viewModel.currentDiffSections.isEmpty {
                             DomainDiffView(
                                 title: "Latest Changes",
                                 sections: viewModel.currentDiffSections,
                                 showsUnchanged: false
                             )
-                            .padding(.top, 16)
+                            .padding(.top, appDensity.metrics.sectionSpacing)
                         }
                         DNSSectionView(
+                            isCollapsed: sectionCollapsedBinding(.dns),
                             dnssecLabel: viewModel.dnssecLabel,
                             sections: viewModel.dnsRows,
                             ptrMessage: viewModel.ptrMessage,
                             loading: viewModel.dnsLoading || viewModel.ptrLoading,
                             sectionError: viewModel.dnsError
                         )
-                        .padding(.top, 16)
+                        .padding(.top, appDensity.metrics.sectionSpacing)
                         WebSectionView(
+                            isCollapsed: sectionCollapsedBinding(.web),
                             certificateRows: viewModel.webCertificateRows,
                             sslInfo: viewModel.sslInfo,
                             sslLoading: viewModel.sslLoading || viewModel.hstsLoading,
@@ -108,14 +125,16 @@ struct ContentView: View {
                             redirectError: viewModel.redirectChainError,
                             finalURL: viewModel.currentSnapshot.redirectChain.last?.url
                         )
-                        .padding(.top, 16)
+                        .padding(.top, appDensity.metrics.sectionSpacing)
                         EmailSectionView(
+                            isCollapsed: sectionCollapsedBinding(.email),
                             rows: viewModel.emailRows,
                             loading: viewModel.emailSecurityLoading,
                             error: viewModel.emailSecurityError
                         )
-                        .padding(.top, 16)
+                        .padding(.top, appDensity.metrics.sectionSpacing)
                         NetworkSectionView(
+                            isCollapsed: sectionCollapsedBinding(.network),
                             reachabilityRows: viewModel.reachabilityRows,
                             reachabilityLoading: viewModel.reachabilityLoading,
                             reachabilityError: viewModel.reachabilityError,
@@ -134,7 +153,7 @@ struct ContentView: View {
                             customPortInput: $customPortInput,
                             onScanCustomPorts: runCustomPortScan
                         )
-                        .padding(.top, 16)
+                        .padding(.top, appDensity.metrics.sectionSpacing)
                     } else if !viewModel.recentSearches.isEmpty {
                         recentSearchesSection
                     }
@@ -142,7 +161,34 @@ struct ContentView: View {
                 .padding(.horizontal)
                 .padding(.bottom, 32)
             }
-            .background(Color.black)
+            .safeAreaInset(edge: .top) {
+                if viewModel.hasRun {
+                    StickyLookupSummaryView(
+                        domain: viewModel.searchedDomain,
+                        availability: viewModel.availabilityResult?.status,
+                        primaryIP: currentPrimaryIP,
+                        sslInfo: viewModel.sslInfo,
+                        sslError: viewModel.sslError,
+                        emailSecurity: viewModel.emailSecurity,
+                        emailError: viewModel.emailSecurityError,
+                        changeSummary: viewModel.currentChangeSummary
+                    )
+                    .padding(.horizontal)
+                    .padding(.top, 6)
+                    .background {
+                        Rectangle()
+                            .fill(.ultraThinMaterial)
+                            .opacity(0.96)
+                    }
+                }
+            }
+            .background(
+                LinearGradient(
+                    colors: [Color.black, Color(.systemGray6).opacity(0.12)],
+                    startPoint: .top,
+                    endPoint: .bottom
+                )
+            )
             .navigationTitle("DomainDig")
             .toolbarColorScheme(.dark, for: .navigationBar)
             .preferredColorScheme(.dark)
@@ -176,7 +222,7 @@ struct ContentView: View {
                         }
 
                         NavigationLink {
-                            SettingsView()
+                            SettingsView(viewModel: viewModel)
                         } label: {
                             Label("Settings", systemImage: "gearshape")
                         }
@@ -190,6 +236,9 @@ struct ContentView: View {
         .onAppear {
             domainFieldFocused = true
         }
+        .onChange(of: viewModel.searchedDomain) { _, _ in
+            collapsedSections = defaultCollapsedSections
+        }
         .onChange(of: viewModel.rerunNavigationToken) { _, _ in
             navigationPath = NavigationPath()
             domainFieldFocused = false
@@ -228,7 +277,7 @@ struct ContentView: View {
     }
 
     private var inputSection: some View {
-        VStack(spacing: 12) {
+        VStack(spacing: appDensity.metrics.cardSpacing + 2) {
             Picker("Mode", selection: $inputMode) {
                 Text("Single").tag(LookupInputMode.single)
                 Text("Bulk").tag(LookupInputMode.bulk)
@@ -237,13 +286,14 @@ struct ContentView: View {
 
             if inputMode == .single {
                 TextField("e.g. cleberg.net", text: $viewModel.domain)
-                    .font(.system(.title3, design: .monospaced))
+                    .font(appDensity.font(.title3, design: .monospaced))
                     .textInputAutocapitalization(.never)
                     .autocorrectionDisabled()
                     .keyboardType(.URL)
-                    .padding(12)
+                    .padding(.horizontal, 12)
+                    .padding(.vertical, appDensity.metrics.controlVerticalPadding)
                     .background(Color(.systemGray6))
-                    .cornerRadius(8)
+                    .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
                     .focused($domainFieldFocused)
                     .onSubmit { viewModel.run() }
 
@@ -252,16 +302,16 @@ struct ContentView: View {
                     viewModel.run()
                 } label: {
                     Text("Run")
-                        .font(.headline)
+                        .font(appDensity.font(.headline, design: .default, weight: .semibold))
                         .frame(maxWidth: .infinity)
-                        .padding(.vertical, 12)
+                        .frame(minHeight: appDensity.metrics.controlMinHeight)
                 }
                 .buttonStyle(.borderedProminent)
                 .disabled(viewModel.trimmedDomain.isEmpty)
             } else {
-                VStack(alignment: .leading, spacing: 8) {
+                VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
                     Text("Paste domains separated by new lines or commas.")
-                        .font(.system(.caption, design: .monospaced))
+                        .font(appDensity.font(.caption))
                         .foregroundStyle(.secondary)
 
                     TextField(
@@ -269,30 +319,31 @@ struct ContentView: View {
                         text: $viewModel.bulkInput,
                         axis: .vertical
                     )
-                    .font(.system(.body, design: .monospaced))
+                    .font(appDensity.font(.body))
                     .textInputAutocapitalization(.never)
                     .autocorrectionDisabled()
                     .keyboardType(.URL)
                     .lineLimit(4...10)
-                    .padding(12)
+                    .padding(.horizontal, 12)
+                    .padding(.vertical, appDensity.metrics.controlVerticalPadding)
                     .background(Color(.systemGray6))
-                    .cornerRadius(8)
+                    .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
 
                     Button {
                         domainFieldFocused = false
                         viewModel.runBulkLookup()
                     } label: {
                         Text(viewModel.batchLookupRunning ? "Running Batch…" : "Run Batch")
-                            .font(.headline)
+                            .font(appDensity.font(.headline, design: .default, weight: .semibold))
                             .frame(maxWidth: .infinity)
-                            .padding(.vertical, 12)
+                            .frame(minHeight: appDensity.metrics.controlMinHeight)
                     }
                     .buttonStyle(.borderedProminent)
                     .disabled(viewModel.bulkInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || viewModel.batchLookupRunning)
                 }
             }
         }
-        .padding(.vertical, 16)
+        .padding(.vertical, appDensity.metrics.sectionSpacing)
     }
 
     private var actionButtons: some View {
@@ -303,7 +354,7 @@ struct ContentView: View {
                     viewModel.toggleSavedDomain()
                 } label: {
                     Image(systemName: viewModel.isCurrentDomainSaved ? "bookmark.fill" : "bookmark")
-                        .font(.system(.body))
+                        .font(appDensity.font(.body, design: .default))
                         .foregroundStyle(viewModel.isCurrentDomainSaved ? .yellow : .secondary)
                 }
                 Menu {
@@ -318,7 +369,7 @@ struct ContentView: View {
                     }
                 } label: {
                     Image(systemName: "square.and.arrow.up")
-                        .font(.system(.body))
+                        .font(appDensity.font(.body, design: .default))
                         .foregroundStyle(.secondary)
                 }
             }
@@ -326,7 +377,7 @@ struct ContentView: View {
     }
 
     private var batchSection: some View {
-        VStack(alignment: .leading, spacing: 12) {
+        VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
             HStack {
                 Spacer()
                 if viewModel.batchLookupRunning {
@@ -334,7 +385,7 @@ struct ContentView: View {
                         viewModel.cancelBatchLookup()
                     }
                     .buttonStyle(.bordered)
-                    .font(.system(.caption, design: .monospaced))
+                    .font(appDensity.font(.caption))
                 }
                 if !viewModel.currentBatchResultEntries.isEmpty {
                     Menu {
@@ -349,7 +400,7 @@ struct ContentView: View {
                         }
                     } label: {
                         Label("Export", systemImage: "square.and.arrow.up")
-                            .font(.system(.caption, design: .monospaced))
+                            .font(appDensity.font(.caption))
                     }
                     .buttonStyle(.bordered)
                 }
@@ -363,16 +414,16 @@ struct ContentView: View {
     }
 
     private var recentSearchesSection: some View {
-        VStack(alignment: .leading, spacing: 8) {
+        VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
             HStack {
                 Text("RECENT")
-                    .font(.system(.caption2, design: .monospaced))
+                    .font(appDensity.font(.caption2))
                     .foregroundStyle(.secondary)
                 Spacer()
                 Button("Clear") {
                     viewModel.clearRecentSearches()
                 }
-                .font(.system(.caption2, design: .monospaced))
+                .font(appDensity.font(.caption2))
                 .foregroundStyle(.secondary)
             }
 
@@ -383,17 +434,17 @@ struct ContentView: View {
                     viewModel.run()
                 } label: {
                     Text(domain)
-                        .font(.system(.callout, design: .monospaced))
+                        .font(appDensity.font(.callout))
                         .foregroundStyle(.primary)
                         .frame(maxWidth: .infinity, alignment: .leading)
-                        .padding(.vertical, 6)
+                        .padding(.vertical, 8)
                         .padding(.horizontal, 10)
                         .background(Color(.systemGray6).opacity(0.5))
-                        .cornerRadius(6)
+                        .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
                 }
             }
         }
-        .padding(.top, 8)
+        .padding(.top, appDensity.metrics.cardSpacing)
     }
 
     private func runCustomPortScan() {
@@ -468,37 +519,125 @@ struct ContentView: View {
 
         return (filename, data)
     }
+
+    private var defaultCollapsedSections: Set<ResultSection> {
+        var sections: Set<ResultSection> = []
+        if viewModel.standardPortRows.count + viewModel.customPortRows.count > 6 || currentPrimaryIP == nil {
+            sections.insert(.network)
+        }
+        return sections
+    }
+
+    private var currentPrimaryIP: String? {
+        viewModel.currentSnapshot.dnsSections.first(where: { $0.recordType == .A })?.records.first?.value
+    }
+
+    private var resultStatusMessage: String? {
+        if let currentStatusMessage = viewModel.currentStatusMessage {
+            return currentStatusMessage
+        }
+
+        if viewModel.currentResultSource != .live {
+            return viewModel.currentResultSource.label
+        }
+
+        return nil
+    }
+
+    private func sectionCollapsedBinding(_ section: ResultSection) -> Binding<Bool> {
+        Binding(
+            get: { collapsedSections.contains(section) },
+            set: { isCollapsed in
+                if isCollapsed {
+                    collapsedSections.insert(section)
+                } else {
+                    collapsedSections.remove(section)
+                }
+            }
+        )
+    }
 }
 
 struct SummaryView: View {
+    @Environment(\.appDensity) private var appDensity
     let fields: [SummaryFieldViewData]
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 12) {
+        VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
             SectionTitleView(title: "Summary")
-            LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 8) {
+            LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: appDensity.metrics.cardSpacing) {
                 ForEach(fields) { field in
-                    VStack(alignment: .leading, spacing: 4) {
+                    VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing) {
                         Text(field.label)
-                            .font(.system(.caption2, design: .monospaced))
+                            .font(appDensity.font(.caption2))
                             .foregroundStyle(.secondary)
                         Text(field.value)
-                            .font(.system(.caption, design: .monospaced))
+                            .font(appDensity.font(.caption))
                             .foregroundStyle(ResultColors.color(for: field.tone))
                             .lineLimit(2)
                             .textSelection(.enabled)
                     }
+                    .frame(minHeight: appDensity.metrics.rowMinHeight + 12, alignment: .topLeading)
                     .frame(maxWidth: .infinity, alignment: .leading)
-                    .padding(10)
+                    .padding(appDensity.metrics.cardPadding)
                     .background(Color(.systemGray6).opacity(0.5))
-                    .cornerRadius(6)
+                    .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
                 }
             }
         }
     }
 }
 
+struct StickyLookupSummaryView: View {
+    @Environment(\.appDensity) private var appDensity
+
+    let domain: String
+    let availability: DomainAvailabilityStatus?
+    let primaryIP: String?
+    let sslInfo: SSLCertificateInfo?
+    let sslError: String?
+    let emailSecurity: EmailSecurityResult?
+    let emailError: String?
+    let changeSummary: DomainChangeSummary?
+
+    var body: some View {
+        CardView(allowsHorizontalScroll: false) {
+            VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
+                HStack(alignment: .center, spacing: 10) {
+                    Text(domain)
+                        .font(appDensity.font(.headline, weight: .semibold))
+                        .foregroundStyle(.primary)
+                        .lineLimit(1)
+                    Spacer(minLength: 6)
+                    AppCopyButton(value: domain, label: "Copy domain")
+                }
+
+                ScrollView(.horizontal, showsIndicators: false) {
+                    HStack(spacing: 8) {
+                        AppStatusBadgeView(model: AppStatusFactory.availability(availability))
+                        AppStatusBadgeView(model: AppStatusFactory.tls(sslInfo: sslInfo, error: sslError))
+                        AppStatusBadgeView(model: AppStatusFactory.email(emailSecurity, error: emailError))
+                        AppStatusBadgeView(model: AppStatusFactory.change(changeSummary))
+                    }
+                }
+
+                if let primaryIP {
+                    HStack(spacing: 8) {
+                        Label(primaryIP, systemImage: "network")
+                            .font(appDensity.font(.caption))
+                            .foregroundStyle(.secondary)
+                        Spacer(minLength: 6)
+                        AppCopyButton(value: primaryIP, label: "Copy IP")
+                    }
+                }
+            }
+        }
+        .shadow(color: .black.opacity(0.12), radius: 14, y: 6)
+    }
+}
+
 struct LookupStatusBannerView: View {
+    @Environment(\.appDensity) private var appDensity
     let message: String
     let resultSource: LookupResultSource
 
@@ -507,14 +646,14 @@ struct LookupStatusBannerView: View {
             Image(systemName: iconName)
                 .font(.caption)
             Text(message)
-                .font(.system(.caption, design: .monospaced))
+                .font(appDensity.font(.caption))
             Spacer()
         }
         .foregroundStyle(color)
-        .padding(8)
+        .padding(appDensity.metrics.cardPadding - 2)
         .frame(maxWidth: .infinity, alignment: .leading)
         .background(color.opacity(0.12))
-        .clipShape(RoundedRectangle(cornerRadius: 8))
+        .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
     }
 
     private var color: Color {
@@ -545,29 +684,30 @@ struct LookupStatusBannerView: View {
 }
 
 struct DomainChangeSummaryView: View {
+    @Environment(\.appDensity) private var appDensity
     let summary: DomainChangeSummary
 
     var body: some View {
         CardView(allowsHorizontalScroll: false) {
             HStack {
                 Label(summary.hasChanges ? "Changed" : "Stable", systemImage: summary.hasChanges ? "arrow.triangle.2.circlepath" : "checkmark.circle")
-                    .font(.system(.caption, design: .monospaced))
+                    .font(appDensity.font(.caption))
                     .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : .green)
                 Spacer()
                 Text(summary.severity.title.uppercased())
-                    .font(.system(.caption2, design: .monospaced))
+                    .font(appDensity.font(.caption2))
                     .foregroundStyle(summary.hasChanges ? severityColor(summary.severity) : .secondary)
                     .padding(.horizontal, 8)
                     .padding(.vertical, 4)
                     .background((summary.hasChanges ? severityColor(summary.severity) : .secondary).opacity(0.16))
                     .clipShape(Capsule())
                 Text(summary.generatedAt, style: .time)
-                    .font(.system(.caption2, design: .monospaced))
+                    .font(appDensity.font(.caption2))
                     .foregroundStyle(.secondary)
             }
 
             Text(summary.message)
-                .font(.system(.caption, design: .monospaced))
+                .font(appDensity.font(.caption))
                 .foregroundStyle(.primary)
         }
     }
@@ -770,6 +910,8 @@ struct TrackedDomainDetailHeaderView: View {
 }
 
 struct DomainSectionView: View {
+    @Environment(\.appDensity) private var appDensity
+    @Binding var isCollapsed: Bool
     let rows: [InfoRowViewData]
     let suggestions: [DomainSuggestionViewData]
     let showSuggestions: Bool
@@ -782,38 +924,34 @@ struct DomainSectionView: View {
     let onEditNote: (() -> Void)?
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 12) {
-            HStack {
-                SectionTitleView(title: "Domain")
-                Spacer()
-                if let trackedDomain {
-                    HStack(spacing: 8) {
-                        Text("Tracked")
-                            .font(.system(.caption, design: .monospaced))
-                            .foregroundStyle(.green)
-                        Button {
-                            onTogglePinned()
-                        } label: {
-                            Image(systemName: trackedDomain.isPinned ? "pin.fill" : "pin")
+        CollapsibleSectionView(title: "Domain", isCollapsed: $isCollapsed) {
+            if let trackedDomain {
+                HStack(spacing: 8) {
+                    AppStatusBadgeView(model: .init(title: "Tracked", systemImage: "eye.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16)))
+                    Button {
+                        onTogglePinned()
+                    } label: {
+                        Image(systemName: trackedDomain.isPinned ? "pin.fill" : "pin")
+                    }
+                    .buttonStyle(.bordered)
+                    .font(appDensity.font(.caption))
+                    if let onEditNote {
+                        Button("Note") {
+                            onEditNote()
                         }
                         .buttonStyle(.bordered)
-                        .font(.system(.caption, design: .monospaced))
-                        if let onEditNote {
-                            Button("Note") {
-                                onEditNote()
-                            }
-                            .buttonStyle(.bordered)
-                            .font(.system(.caption, design: .monospaced))
-                        }
-                    }
-                } else {
-                    Button("Track") {
-                        onTrack()
+                        .font(appDensity.font(.caption))
                     }
-                    .buttonStyle(.bordered)
-                    .font(.system(.caption, design: .monospaced))
                 }
+            } else {
+                Button("Track") {
+                    AppHaptics.track()
+                    onTrack()
+                }
+                .buttonStyle(.bordered)
+                .font(appDensity.font(.caption))
             }
+        } content: {
             CardView(allowsHorizontalScroll: false) {
                 ForEach(rows) { row in
                     LabeledValueRow(row: row)
@@ -832,7 +970,7 @@ struct DomainSectionView: View {
                 }
                 if showSuggestions {
                     Text("Suggestions")
-                        .font(.system(.caption, design: .monospaced))
+                        .font(appDensity.font(.caption))
                         .foregroundStyle(.secondary)
                         .padding(.top, 4)
                     if suggestionsLoading {
@@ -844,13 +982,11 @@ struct DomainSectionView: View {
                         ForEach(suggestions) { suggestion in
                             HStack {
                                 Text(suggestion.domain)
-                                    .font(.system(.caption, design: .monospaced))
+                                    .font(appDensity.font(.caption))
                                     .foregroundStyle(.primary)
                                     .textSelection(.enabled)
                                 Spacer()
-                                Text(suggestion.status)
-                                    .font(.system(.caption2, design: .monospaced))
-                                    .foregroundStyle(ResultColors.color(for: suggestion.tone))
+                                AppStatusBadgeView(model: AppStatusFactory.availability(suggestion.status == "Available" ? .available : .registered))
                             }
                         }
                     }
@@ -861,14 +997,14 @@ struct DomainSectionView: View {
 }
 
 struct OwnershipSectionView: View {
+    @Binding var isCollapsed: Bool
     let rows: [InfoRowViewData]
     let loading: Bool
     let error: String?
     let showsHistoryPlaceholder: Bool
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 12) {
-            SectionTitleView(title: "Ownership")
+        CollapsibleSectionView(title: "Ownership", isCollapsed: $isCollapsed) {
             CardView(allowsHorizontalScroll: false) {
                 if loading {
                     ProgressView("Fetching RDAP ownership…")
@@ -892,21 +1028,15 @@ struct OwnershipSectionView: View {
 }
 
 struct SubdomainsSectionView: View {
+    @Environment(\.appDensity) private var appDensity
+    @Binding var isCollapsed: Bool
     let rows: [SubdomainRowViewData]
     let loading: Bool
     let error: String?
     let showsExtendedPlaceholder: Bool
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 12) {
-            HStack {
-                SectionTitleView(title: "Subdomains")
-                Spacer()
-                Text("\(rows.count)")
-                    .font(.system(.caption2, design: .monospaced))
-                    .foregroundStyle(.secondary)
-            }
-
+        CollapsibleSectionView(title: "Subdomains", isCollapsed: $isCollapsed, subtitle: "\(rows.count) found") {
             CardView(allowsHorizontalScroll: false) {
                 if loading {
                     ProgressView("Checking certificate transparency…")
@@ -921,7 +1051,7 @@ struct SubdomainsSectionView: View {
                     ForEach(rows) { row in
                         HStack(spacing: 8) {
                             Text(row.hostname)
-                                .font(.system(.caption, design: .monospaced))
+                                .font(appDensity.font(.caption))
                                 .foregroundStyle(.primary)
                                 .textSelection(.enabled)
                             Spacer()
@@ -947,6 +1077,7 @@ struct SubdomainsSectionView: View {
 }
 
 struct DNSSectionView: View {
+    @Binding var isCollapsed: Bool
     let dnssecLabel: String?
     let sections: [DNSRecordSectionViewData]
     let ptrMessage: SectionMessageViewData?
@@ -954,18 +1085,7 @@ struct DNSSectionView: View {
     let sectionError: String?
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 12) {
-            HStack(alignment: .top, spacing: 8) {
-                SectionTitleView(title: "DNS")
-                Spacer()
-                if let dnssecLabel {
-                    Text(dnssecLabel)
-                        .font(.system(.caption2, design: .monospaced))
-                        .foregroundStyle(.secondary)
-                        .multilineTextAlignment(.trailing)
-                }
-            }
-
+        CollapsibleSectionView(title: "DNS", isCollapsed: $isCollapsed, subtitle: dnssecLabel) {
             if loading {
                 LoadingCardView(text: "Querying DNS…")
             } else if let sectionError, sections.isEmpty {
@@ -1013,6 +1133,8 @@ struct DNSSectionView: View {
 }
 
 struct WebSectionView: View {
+    @Environment(\.appDensity) private var appDensity
+    @Binding var isCollapsed: Bool
     let certificateRows: [InfoRowViewData]
     let sslInfo: SSLCertificateInfo?
     let sslLoading: Bool
@@ -1027,14 +1149,15 @@ struct WebSectionView: View {
     let finalURL: String?
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 12) {
-            SectionTitleView(title: "Web")
-
+        CollapsibleSectionView(title: "Web", isCollapsed: $isCollapsed) {
             CardView {
-                Text("TLS")
-                    .font(.system(.subheadline, design: .monospaced))
-                    .fontWeight(.semibold)
-                    .foregroundStyle(.cyan)
+                HStack {
+                    Text("TLS")
+                        .font(appDensity.font(.subheadline, weight: .semibold))
+                        .foregroundStyle(.cyan)
+                    Spacer()
+                    AppStatusBadgeView(model: AppStatusFactory.tls(sslInfo: sslInfo, error: sslError))
+                }
                 if sslLoading {
                     ProgressView("Checking certificate…")
                         .appLoadingStyle()
@@ -1046,12 +1169,16 @@ struct WebSectionView: View {
                     }
                     if let sslInfo, !sslInfo.subjectAltNames.isEmpty {
                         Text("SANs")
-                            .font(.system(.caption2, design: .monospaced))
+                            .font(appDensity.font(.caption2))
                             .foregroundStyle(.secondary)
                         ForEach(sslInfo.subjectAltNames, id: \.self) { san in
-                            Text(san)
-                                .font(.system(.caption, design: .monospaced))
-                                .textSelection(.enabled)
+                            HStack(spacing: 8) {
+                                Text(san)
+                                    .font(appDensity.font(.caption))
+                                    .textSelection(.enabled)
+                                Spacer()
+                                AppCopyButton(value: san, label: "Copy certificate SAN")
+                            }
                         }
                     }
                 }
@@ -1059,8 +1186,7 @@ struct WebSectionView: View {
 
             CardView {
                 Text("Headers")
-                    .font(.system(.subheadline, design: .monospaced))
-                    .fontWeight(.semibold)
+                    .font(appDensity.font(.subheadline, weight: .semibold))
                     .foregroundStyle(.cyan)
                 if headersLoading {
                     ProgressView("Fetching headers…")
@@ -1077,10 +1203,10 @@ struct WebSectionView: View {
                         ForEach(headers) { header in
                             HStack(alignment: .top, spacing: 4) {
                                 Text(header.name + ":")
-                                    .font(.system(.caption, design: .monospaced))
+                                    .font(appDensity.font(.caption))
                                     .foregroundStyle(header.isSecurityHeader ? .yellow : .cyan)
                                 Text(header.value)
-                                    .font(.system(.caption, design: .monospaced))
+                                    .font(appDensity.font(.caption))
                                     .foregroundStyle(.primary)
                                     .textSelection(.enabled)
                             }
@@ -1090,10 +1216,15 @@ struct WebSectionView: View {
             }
 
             CardView {
-                Text("Redirects")
-                    .font(.system(.subheadline, design: .monospaced))
-                    .fontWeight(.semibold)
-                    .foregroundStyle(.cyan)
+                HStack {
+                    Text("Redirects")
+                        .font(appDensity.font(.subheadline, weight: .semibold))
+                        .foregroundStyle(.cyan)
+                    Spacer()
+                    if let finalURL {
+                        AppCopyButton(value: finalURL, label: "Copy redirect URL")
+                    }
+                }
                 if redirectLoading {
                     ProgressView("Tracing redirects…")
                         .appLoadingStyle()
@@ -1108,19 +1239,20 @@ struct WebSectionView: View {
                     ForEach(redirects) { redirect in
                         HStack(alignment: .top, spacing: 6) {
                             Text(redirect.stepLabel)
-                                .font(.system(.caption, design: .monospaced))
+                                .font(appDensity.font(.caption))
                                 .foregroundStyle(.secondary)
                                 .frame(width: 16, alignment: .trailing)
                             Text(redirect.statusCode)
-                                .font(.system(.caption, design: .monospaced))
+                                .font(appDensity.font(.caption))
                                 .foregroundStyle(.cyan)
                                 .frame(width: 36, alignment: .leading)
                             Text(redirect.url)
-                                .font(.system(.caption, design: .monospaced))
+                                .font(appDensity.font(.caption))
                                 .textSelection(.enabled)
+                            AppCopyButton(value: redirect.url, label: "Copy redirect URL")
                             if redirect.isFinal {
                                 Text("(final)")
-                                    .font(.system(.caption2, design: .monospaced))
+                                    .font(appDensity.font(.caption2))
                                     .foregroundStyle(.secondary)
                             }
                         }
@@ -1132,14 +1264,20 @@ struct WebSectionView: View {
 }
 
 struct EmailSectionView: View {
+    @Environment(\.appDensity) private var appDensity
+    @Binding var isCollapsed: Bool
     let rows: [EmailRowViewData]
     let loading: Bool
     let error: String?
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 12) {
-            SectionTitleView(title: "Email")
+        CollapsibleSectionView(title: "Email", isCollapsed: $isCollapsed) {
             CardView {
+                HStack {
+                    Spacer()
+                    AppStatusBadgeView(model: AppStatusFactory.email(nil, error: error))
+                        .opacity(loading ? 0 : 1)
+                }
                 if loading {
                     ProgressView("Checking email records…")
                         .appLoadingStyle()
@@ -1152,20 +1290,18 @@ struct EmailSectionView: View {
                         VStack(alignment: .leading, spacing: 4) {
                             HStack(spacing: 8) {
                                 Text(row.label)
-                                    .font(.system(.caption, design: .monospaced))
+                                    .font(appDensity.font(.caption))
                                     .foregroundStyle(.cyan)
                                     .frame(width: 76, alignment: .leading)
-                                Text(row.status)
-                                    .font(.system(.caption, design: .monospaced))
-                                    .foregroundStyle(ResultColors.color(for: row.statusTone))
+                                AppStatusBadgeView(model: emailRowBadge(row))
                             }
                             Text(row.detail)
-                                .font(.system(.caption2, design: .monospaced))
+                                .font(appDensity.font(.caption2))
                                 .foregroundStyle(.primary)
                                 .textSelection(.enabled)
                             if let auxiliaryDetail = row.auxiliaryDetail {
                                 Text(auxiliaryDetail)
-                                    .font(.system(.caption2, design: .monospaced))
+                                    .font(appDensity.font(.caption2))
                                     .foregroundStyle(.secondary)
                             }
                         }
@@ -1174,9 +1310,24 @@ struct EmailSectionView: View {
             }
         }
     }
+
+    private func emailRowBadge(_ row: EmailRowViewData) -> AppStatusBadgeModel {
+        switch row.statusTone {
+        case .success:
+            return .init(title: row.status, systemImage: "checkmark.shield.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
+        case .warning:
+            return .init(title: row.status, systemImage: "shield.lefthalf.filled", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
+        case .failure:
+            return .init(title: row.status, systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
+        case .primary, .secondary:
+            return .init(title: row.status, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
+        }
+    }
 }
 
 struct NetworkSectionView: View {
+    @Environment(\.appDensity) private var appDensity
+    @Binding var isCollapsed: Bool
     let reachabilityRows: [ReachabilityRowViewData]
     let reachabilityLoading: Bool
     let reachabilityError: String?
@@ -1196,13 +1347,10 @@ struct NetworkSectionView: View {
     let onScanCustomPorts: () -> Void
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 12) {
-            SectionTitleView(title: "Network")
-
+        CollapsibleSectionView(title: "Network", isCollapsed: $isCollapsed) {
             CardView {
                 Text("Reachability")
-                    .font(.system(.subheadline, design: .monospaced))
-                    .fontWeight(.semibold)
+                    .font(appDensity.font(.subheadline, weight: .semibold))
                     .foregroundStyle(.cyan)
                 if reachabilityLoading {
                     ProgressView("Checking ports…")
@@ -1213,14 +1361,12 @@ struct NetworkSectionView: View {
                     ForEach(reachabilityRows) { row in
                         HStack {
                             Text(row.portLabel)
-                                .font(.system(.caption, design: .monospaced))
+                                .font(appDensity.font(.caption))
                             Spacer()
                             Text(row.latencyLabel)
-                                .font(.system(.caption2, design: .monospaced))
+                                .font(appDensity.font(.caption2))
                                 .foregroundStyle(.secondary)
-                            Text(row.statusLabel)
-                                .font(.system(.caption, design: .monospaced))
-                                .foregroundStyle(ResultColors.color(for: row.statusTone))
+                            AppStatusBadgeView(model: reachabilityBadge(row))
                         }
                     }
                 }
@@ -1228,8 +1374,7 @@ struct NetworkSectionView: View {
 
             CardView(allowsHorizontalScroll: false) {
                 Text("Location")
-                    .font(.system(.subheadline, design: .monospaced))
-                    .fontWeight(.semibold)
+                    .font(appDensity.font(.subheadline, weight: .semibold))
                     .foregroundStyle(.cyan)
                 if geolocationLoading {
                     ProgressView("Looking up location…")
@@ -1260,13 +1405,12 @@ struct NetworkSectionView: View {
 
             CardView(allowsHorizontalScroll: false) {
                 Text("Port Scan")
-                    .font(.system(.subheadline, design: .monospaced))
-                    .fontWeight(.semibold)
+                    .font(appDensity.font(.subheadline, weight: .semibold))
                     .foregroundStyle(.cyan)
 
                 if isCloudflareProxied {
                     Text("Domain is behind Cloudflare's proxy. Results reflect the edge, not the origin.")
-                        .font(.system(.caption2, design: .monospaced))
+                        .font(appDensity.font(.caption2))
                         .foregroundStyle(.orange)
                         .fixedSize(horizontal: false, vertical: true)
                 }
@@ -1286,15 +1430,16 @@ struct NetworkSectionView: View {
                 DisclosureGroup("Custom Ports", isExpanded: $customPortsExpanded) {
                     VStack(alignment: .leading, spacing: 10) {
                         TextField("8888, 9000, 27017", text: $customPortInput)
-                            .font(.system(.caption, design: .monospaced))
+                            .font(appDensity.font(.caption))
                             .textInputAutocapitalization(.never)
                             .autocorrectionDisabled()
                             .keyboardType(.numberPad)
                             .padding(10)
                             .background(Color(.systemGray6).opacity(0.5))
-                            .cornerRadius(6)
+                            .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
 
                         Button("Scan") {
+                            AppHaptics.refresh()
                             onScanCustomPorts()
                         }
                         .buttonStyle(.borderedProminent)
@@ -1316,9 +1461,23 @@ struct NetworkSectionView: View {
             }
         }
     }
+
+    private func reachabilityBadge(_ row: ReachabilityRowViewData) -> AppStatusBadgeModel {
+        switch row.statusTone {
+        case .success:
+            return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
+        case .warning:
+            return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
+        case .failure:
+            return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
+        case .primary, .secondary:
+            return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
+        }
+    }
 }
 
 struct PortRowsView: View {
+    @Environment(\.appDensity) private var appDensity
     let rows: [PortScanRowViewData]
 
     var body: some View {
@@ -1326,47 +1485,61 @@ struct PortRowsView: View {
             MessageRowView(text: "No results", isError: false)
         } else {
             ForEach(rows) { row in
-                VStack(alignment: .leading, spacing: 2) {
+                VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing - 1) {
                     HStack {
                         Text(row.portLabel)
-                            .font(.system(.caption, design: .monospaced))
+                            .font(appDensity.font(.caption))
                             .frame(width: 52, alignment: .leading)
                         Text(row.service)
-                            .font(.system(.caption, design: .monospaced))
+                            .font(appDensity.font(.caption))
                             .foregroundStyle(.primary)
                         Spacer()
                         if let durationLabel = row.durationLabel {
                             Text(durationLabel)
-                                .font(.system(.caption2, design: .monospaced))
+                                .font(appDensity.font(.caption2))
                                 .foregroundStyle(.secondary)
                         }
-                        Text(row.statusLabel)
-                            .font(.system(.caption2, design: .monospaced))
-                            .foregroundStyle(ResultColors.color(for: row.statusTone))
+                        AppStatusBadgeView(model: portBadge(row))
                     }
                     if let banner = row.banner {
                         Text(banner)
-                            .font(.system(.caption2, design: .monospaced))
+                            .font(appDensity.font(.caption2))
                             .foregroundStyle(.secondary)
                             .padding(.leading, 8)
                     }
                 }
+                .frame(minHeight: appDensity.metrics.rowMinHeight, alignment: .topLeading)
             }
         }
     }
+
+    private func portBadge(_ row: PortScanRowViewData) -> AppStatusBadgeModel {
+        switch row.statusTone {
+        case .success:
+            return .init(title: row.statusLabel, systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
+        case .warning:
+            return .init(title: row.statusLabel, systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
+        case .failure:
+            return .init(title: row.statusLabel, systemImage: "xmark.circle.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
+        case .primary, .secondary:
+            return .init(title: row.statusLabel, systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
+        }
+    }
 }
 
 struct SectionTitleView: View {
+    @Environment(\.appDensity) private var appDensity
     let title: String
 
     var body: some View {
         Text(title)
-            .font(.system(.headline))
+            .font(appDensity.font(.headline, design: .default, weight: .semibold))
             .foregroundStyle(.white)
     }
 }
 
 struct CardView<Content: View>: View {
+    @Environment(\.appDensity) private var appDensity
     let allowsHorizontalScroll: Bool
     let content: Content
 
@@ -1389,13 +1562,13 @@ struct CardView<Content: View>: View {
             }
         }
         .frame(maxWidth: .infinity, alignment: .leading)
-        .padding(10)
+        .padding(appDensity.metrics.cardPadding)
         .background(Color(.systemGray6).opacity(0.5))
-        .cornerRadius(6)
+        .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
     }
 
     private var cardContent: some View {
-        VStack(alignment: .leading, spacing: 6) {
+        VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
             content
         }
     }
@@ -1425,29 +1598,40 @@ struct MessageCardView: View {
 }
 
 struct MessageRowView: View {
+    @Environment(\.appDensity) private var appDensity
     let text: String
     let isError: Bool
 
     var body: some View {
         Label(text, systemImage: isError ? "exclamationmark.triangle.fill" : "info.circle")
-            .font(.system(.caption, design: .monospaced))
+            .font(appDensity.font(.caption))
             .foregroundStyle(isError ? .red : .secondary)
     }
 }
 
 struct LabeledValueRow: View {
+    @Environment(\.appDensity) private var appDensity
     let row: InfoRowViewData
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 2) {
-            Text(row.label)
-                .font(.system(.caption2, design: .monospaced))
-                .foregroundStyle(.secondary)
-            Text(row.value)
-                .font(.system(.caption, design: .monospaced))
-                .foregroundStyle(ResultColors.color(for: row.tone))
-                .textSelection(.enabled)
+        VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing - 1) {
+            HStack(alignment: .top, spacing: 8) {
+                VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing - 1) {
+                    Text(row.label)
+                        .font(appDensity.font(.caption2))
+                        .foregroundStyle(.secondary)
+                    Text(row.value)
+                        .font(appDensity.font(.caption))
+                        .foregroundStyle(ResultColors.color(for: row.tone))
+                        .textSelection(.enabled)
+                }
+                Spacer(minLength: 6)
+                if !row.value.isEmpty, row.value != "Unavailable" {
+                    AppCopyButton(value: row.value, label: "Copy \(row.label)")
+                }
+            }
         }
+        .frame(minHeight: appDensity.metrics.rowMinHeight, alignment: .topLeading)
     }
 }
 
@@ -1490,11 +1674,17 @@ private extension String {
 }
 
 private struct SettingsView: 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
+    @State private var showClearHistoryConfirmation = false
+    @State private var showClearCacheConfirmation = false
 
     private var customResolverError: String? {
         guard resolverOption == .custom else {
@@ -1505,7 +1695,15 @@ private struct SettingsView: View {
 
     var body: some View {
         Form {
-            Section {
+            Section("Display") {
+                Picker("Density", selection: $storedDensity) {
+                    ForEach(AppDensity.allCases) { density in
+                        Text(density.title).tag(density.rawValue)
+                    }
+                }
+            }
+
+            Section("Network") {
                 Picker("Resolver", selection: $resolverOption) {
                     ForEach(DNSResolverOption.allCases) { option in
                         Text(option.title).tag(option)
@@ -1520,13 +1718,45 @@ private struct SettingsView: View {
 
                     if let customResolverError {
                         Text(customResolverError)
-                            .font(.caption)
+                            .font(appDensity.font(.caption, design: .default))
                             .foregroundStyle(.red)
                     }
                 }
             }
+
+            Section("Data") {
+                Button("Clear History", role: .destructive) {
+                    showClearHistoryConfirmation = true
+                }
+
+                Button("Clear Cache", role: .destructive) {
+                    showClearCacheConfirmation = true
+                }
+            }
+
+            Section("About") {
+                LabeledContent("Version", value: appVersion)
+                LabeledContent("Storage", value: "Local-only")
+                LabeledContent("Focus", value: "Readable domain inspection")
+            }
         }
         .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.")
+        }
         .onAppear {
             let currentResolverURL = storedResolverURL.trimmingCharacters(in: .whitespacesAndNewlines)
             resolverOption = DNSResolverOption.option(for: currentResolverURL)
@@ -1544,6 +1774,10 @@ private struct SettingsView: View {
             storedResolverURL = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
         }
     }
+
+    private var appVersion: String {
+        Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "2.6.0"
+    }
 }
 
 #Preview {
diff --git a/DomainDig/DomainDigApp.swift b/DomainDig/DomainDigApp.swift
index a34eb12..29e0117 100644
--- a/DomainDig/DomainDigApp.swift
+++ b/DomainDig/DomainDigApp.swift
@@ -9,6 +9,8 @@ import SwiftUI
 
 @main
 struct DomainDigApp: App {
+    @AppStorage(AppDensity.userDefaultsKey) private var density = AppDensity.compact.rawValue
+
     init() {
         LocalNotificationService.shared.configureForegroundPresentation()
     }
@@ -16,6 +18,7 @@ struct DomainDigApp: App {
     var body: some Scene {
         WindowGroup {
             ContentView()
+                .environment(\.appDensity, AppDensity(rawValue: density) ?? .compact)
         }
     }
 }
diff --git a/DomainDig/DomainDigUI.swift b/DomainDig/DomainDigUI.swift
new file mode 100644
index 0000000..c3caf59
--- /dev/null
+++ b/DomainDig/DomainDigUI.swift
@@ -0,0 +1,319 @@
+import SwiftUI
+
+#if canImport(UIKit)
+import UIKit
+#elseif canImport(AppKit)
+import AppKit
+#endif
+
+enum AppDensity: String, CaseIterable, Identifiable {
+    case compact
+    case comfortable
+
+    static let userDefaultsKey = "appDensity"
+
+    var id: String { rawValue }
+
+    var title: String {
+        switch self {
+        case .compact:
+            return "Compact"
+        case .comfortable:
+            return "Comfortable"
+        }
+    }
+
+    var metrics: AppDensityMetrics {
+        switch self {
+        case .compact:
+            return AppDensityMetrics(
+                sectionSpacing: 14,
+                cardSpacing: 6,
+                cardPadding: 10,
+                rowSpacing: 4,
+                rowMinHeight: 30,
+                controlVerticalPadding: 10,
+                controlMinHeight: 42,
+                cardCornerRadius: 10
+            )
+        case .comfortable:
+            return AppDensityMetrics(
+                sectionSpacing: 18,
+                cardSpacing: 10,
+                cardPadding: 14,
+                rowSpacing: 7,
+                rowMinHeight: 38,
+                controlVerticalPadding: 14,
+                controlMinHeight: 48,
+                cardCornerRadius: 14
+            )
+        }
+    }
+
+    func font(_ textStyle: Font.TextStyle, design: Font.Design = .monospaced, weight: Font.Weight? = nil) -> Font {
+        var font = Font.system(textStyle, design: design)
+        if let weight {
+            font = font.weight(weight)
+        }
+        return font
+    }
+}
+
+struct AppDensityMetrics: Equatable {
+    let sectionSpacing: CGFloat
+    let cardSpacing: CGFloat
+    let cardPadding: CGFloat
+    let rowSpacing: CGFloat
+    let rowMinHeight: CGFloat
+    let controlVerticalPadding: CGFloat
+    let controlMinHeight: CGFloat
+    let cardCornerRadius: CGFloat
+}
+
+private struct AppDensityKey: EnvironmentKey {
+    static let defaultValue: AppDensity = .compact
+}
+
+extension EnvironmentValues {
+    var appDensity: AppDensity {
+        get { self[AppDensityKey.self] }
+        set { self[AppDensityKey.self] = newValue }
+    }
+}
+
+struct AppStatusBadgeModel: Equatable {
+    let title: String
+    let systemImage: String?
+    let foregroundColor: Color
+    let backgroundColor: Color
+}
+
+enum AppStatusFactory {
+    static func availability(_ status: DomainAvailabilityStatus?) -> AppStatusBadgeModel {
+        switch status {
+        case .available:
+            return .init(title: "Available", systemImage: "checkmark.circle.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
+        case .registered:
+            return .init(title: "Registered", systemImage: "circle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
+        case .unknown, .none:
+            return .init(title: "Unknown", systemImage: "questionmark.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
+        }
+    }
+
+    static func tls(sslInfo: SSLCertificateInfo?, error: String?) -> AppStatusBadgeModel {
+        if error != nil || sslInfo == nil {
+            return .init(title: "Invalid", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
+        }
+        if let sslInfo, sslInfo.daysUntilExpiry <= 14 {
+            return .init(title: "Expiring", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
+        }
+        return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
+    }
+
+    static func email(_ result: EmailSecurityResult?, error: String?) -> AppStatusBadgeModel {
+        guard error == nil, let result else {
+            return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
+        }
+
+        let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found].filter { $0 }.count
+        switch foundCount {
+        case 3:
+            return .init(title: "Secure", systemImage: "checkmark.shield.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
+        case 1, 2:
+            return .init(title: "Partial", systemImage: "shield.lefthalf.filled", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
+        default:
+            return .init(title: "Missing", systemImage: "minus.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
+        }
+    }
+
+    static func change(_ summary: DomainChangeSummary?) -> AppStatusBadgeModel {
+        guard let summary else {
+            return .init(title: "Unchanged", systemImage: "circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
+        }
+        if summary.hasChanges {
+            return .init(title: "Changed", systemImage: "arrow.triangle.2.circlepath", foregroundColor: .cyan, backgroundColor: .cyan.opacity(0.16))
+        }
+        return .init(title: "Unchanged", systemImage: "checkmark.circle", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.55))
+    }
+}
+
+struct AppStatusBadgeView: View {
+    @Environment(\.appDensity) private var appDensity
+
+    let model: AppStatusBadgeModel
+
+    var body: some View {
+        HStack(spacing: 6) {
+            if let systemImage = model.systemImage {
+                Image(systemName: systemImage)
+                    .font(.caption2)
+            }
+            Text(model.title)
+        }
+        .font(appDensity.font(.caption, weight: .semibold))
+        .foregroundStyle(model.foregroundColor)
+        .padding(.horizontal, 9)
+        .padding(.vertical, 5)
+        .background(model.backgroundColor)
+        .clipShape(Capsule())
+    }
+}
+
+struct AppCopyButton: View {
+    @Environment(\.appDensity) private var appDensity
+    @State private var didCopy = false
+
+    let value: String
+    let label: String
+
+    var body: some View {
+        Button {
+            AppClipboard.copy(value)
+            AppHaptics.copy()
+            withAnimation(.easeInOut(duration: 0.18)) {
+                didCopy = true
+            }
+            Task {
+                try? await Task.sleep(nanoseconds: 900_000_000)
+                await MainActor.run {
+                    withAnimation(.easeInOut(duration: 0.18)) {
+                        didCopy = false
+                    }
+                }
+            }
+        } label: {
+            Image(systemName: didCopy ? "checkmark" : "doc.on.doc")
+                .font(appDensity.font(.caption))
+                .foregroundStyle(didCopy ? Color.green : .secondary)
+                .frame(width: 30, height: 30)
+                .background(Color(.systemGray5).opacity(0.35))
+                .clipShape(RoundedRectangle(cornerRadius: 8))
+        }
+        .buttonStyle(.plain)
+        .accessibilityLabel(didCopy ? "\(label) copied" : label)
+    }
+}
+
+enum AppClipboard {
+    static func copy(_ value: String) {
+        #if canImport(UIKit)
+        UIPasteboard.general.string = value
+        #elseif canImport(AppKit)
+        NSPasteboard.general.clearContents()
+        NSPasteboard.general.setString(value, forType: .string)
+        #endif
+    }
+}
+
+enum AppHaptics {
+    static func copy() {
+        #if canImport(UIKit)
+        let generator = UINotificationFeedbackGenerator()
+        generator.notificationOccurred(.success)
+        #endif
+    }
+
+    static func refresh() {
+        #if canImport(UIKit)
+        let generator = UIImpactFeedbackGenerator(style: .light)
+        generator.impactOccurred()
+        #endif
+    }
+
+    static func track() {
+        #if canImport(UIKit)
+        let generator = UIImpactFeedbackGenerator(style: .soft)
+        generator.impactOccurred()
+        #endif
+    }
+}
+
+struct EmptyStateCardView: View {
+    @Environment(\.appDensity) private var appDensity
+
+    let title: String
+    let message: String
+    let suggestion: String
+    let systemImage: String
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
+            Label(title, systemImage: systemImage)
+                .font(appDensity.font(.headline, weight: .semibold))
+                .foregroundStyle(.primary)
+
+            Text(message)
+                .font(appDensity.font(.body))
+                .foregroundStyle(.secondary)
+                .fixedSize(horizontal: false, vertical: true)
+
+            Text(suggestion)
+                .font(appDensity.font(.caption))
+                .foregroundStyle(.cyan)
+        }
+        .frame(maxWidth: .infinity, alignment: .leading)
+        .padding(appDensity.metrics.cardPadding)
+        .background(Color(.systemGray6).opacity(0.45))
+        .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
+    }
+}
+
+struct CollapsibleSectionView<HeaderTrailing: View, Content: View>: View {
+    @Environment(\.appDensity) private var appDensity
+
+    let title: String
+    @Binding var isCollapsed: Bool
+    let subtitle: String?
+    @ViewBuilder let trailing: () -> HeaderTrailing
+    @ViewBuilder let content: () -> Content
+
+    init(
+        title: String,
+        isCollapsed: Binding<Bool>,
+        subtitle: String? = nil,
+        @ViewBuilder trailing: @escaping () -> HeaderTrailing = { EmptyView() },
+        @ViewBuilder content: @escaping () -> Content
+    ) {
+        self.title = title
+        self._isCollapsed = isCollapsed
+        self.subtitle = subtitle
+        self.trailing = trailing
+        self.content = content
+    }
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: appDensity.metrics.cardSpacing) {
+            Button {
+                withAnimation(.easeInOut(duration: 0.2)) {
+                    isCollapsed.toggle()
+                }
+            } label: {
+                HStack(alignment: .center, spacing: 10) {
+                    VStack(alignment: .leading, spacing: 3) {
+                        Text(title)
+                            .font(appDensity.font(.headline, design: .default, weight: .semibold))
+                            .foregroundStyle(.white)
+                        if let subtitle {
+                            Text(subtitle)
+                                .font(appDensity.font(.caption))
+                                .foregroundStyle(.secondary)
+                        }
+                    }
+                    Spacer(minLength: 8)
+                    trailing()
+                    Image(systemName: isCollapsed ? "chevron.down" : "chevron.up")
+                        .font(.caption.weight(.semibold))
+                        .foregroundStyle(.secondary)
+                }
+                .contentShape(Rectangle())
+                .frame(minHeight: appDensity.metrics.controlMinHeight, alignment: .center)
+            }
+            .buttonStyle(.plain)
+
+            if !isCollapsed {
+                content()
+                    .transition(.opacity.combined(with: .move(edge: .top)))
+            }
+        }
+    }
+}
diff --git a/DomainDig/DomainViewModel.swift b/DomainDig/DomainViewModel.swift
index 9fdf1fc..326db3a 100644
--- a/DomainDig/DomainViewModel.swift
+++ b/DomainDig/DomainViewModel.swift
@@ -598,6 +598,12 @@ final class DomainViewModel {
         persistHistory()
     }
 
+    func clearLookupCache() {
+        Task {
+            await LookupRuntime.shared.clearCache()
+        }
+    }
+
     func clearRecentSearches() {
         recentSearches.removeAll()
         UserDefaults.standard.removeObject(forKey: Self.recentSearchesKey)
diff --git a/DomainDig/HistoryView.swift b/DomainDig/HistoryView.swift
index 6f3d1a9..1a5c0d0 100644
--- a/DomainDig/HistoryView.swift
+++ b/DomainDig/HistoryView.swift
@@ -1,53 +1,69 @@
 import SwiftUI
 
 struct HistoryView: View {
+    @Environment(\.appDensity) private var appDensity
     @Bindable var viewModel: DomainViewModel
     @Environment(\.dismiss) private var dismiss
     @State private var showClearAllConfirmation = false
 
-    private let dateFormatter: DateFormatter = {
-        let formatter = DateFormatter()
-        formatter.dateStyle = .medium
-        formatter.timeStyle = .short
-        return formatter
-    }()
+    private var groupedHistory: [HistoryGroup] {
+        HistoryGroup.groups(for: viewModel.filteredHistory)
+    }
 
     var body: some View {
         List {
             if viewModel.filteredHistory.isEmpty {
-                Text("No lookup history")
-                    .font(.system(.callout, design: .monospaced))
-                    .foregroundStyle(.secondary)
+                EmptyStateCardView(
+                    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"
+                )
                     .listRowBackground(Color(.systemGray6).opacity(0.5))
             } else {
-                ForEach(viewModel.filteredHistory) { entry in
-                    NavigationLink {
-                        HistoryDetailView(viewModel: viewModel, entry: entry)
-                    } label: {
-                        VStack(alignment: .leading, spacing: 4) {
-                            Text(entry.domain)
-                                .font(.system(.callout, design: .monospaced))
-                                .foregroundStyle(.primary)
-                            if let summary = entry.changeSummary {
-                                Text(summary.hasChanges ? "Changed" : "Unchanged")
-                                    .font(.system(.caption2, design: .monospaced))
-                                    .foregroundStyle(summary.hasChanges ? .yellow : .green)
+                ForEach(groupedHistory) { group in
+                    Section(group.title) {
+                        ForEach(group.entries) { entry in
+                            NavigationLink {
+                                HistoryDetailView(viewModel: viewModel, entry: entry)
+                            } label: {
+                                VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing + 1) {
+                                    HStack(alignment: .center, spacing: 8) {
+                                        Text(entry.domain)
+                                            .font(appDensity.font(.callout))
+                                            .foregroundStyle(.primary)
+                                        Spacer()
+                                        AppStatusBadgeView(model: AppStatusFactory.change(entry.changeSummary))
+                                    }
+
+                                    HStack(spacing: 8) {
+                                        AppStatusBadgeView(model: AppStatusFactory.availability(entry.availabilityResult?.status))
+                                        AppStatusBadgeView(model: AppStatusFactory.tls(sslInfo: entry.sslInfo, error: entry.sslError))
+                                    }
+
+                                    HStack(spacing: 8) {
+                                        Text(entry.timestamp.formatted(date: .abbreviated, time: .shortened))
+                                        Text(entry.timestamp.formatted(.relative(presentation: .named)))
+                                        Text(entry.resolverDisplayName)
+                                        if let totalLookupDurationMs = entry.totalLookupDurationMs {
+                                            Text("\(totalLookupDurationMs) ms")
+                                        }
+                                    }
+                                    .font(appDensity.font(.caption2))
+                                    .foregroundStyle(.secondary)
+                                }
                             }
-                            HStack(spacing: 8) {
-                                Text(dateFormatter.string(from: entry.timestamp))
-                                Text("Snapshot")
-                                Text(entry.resolverDisplayName)
-                                if let totalLookupDurationMs = entry.totalLookupDurationMs {
-                                    Text("\(totalLookupDurationMs) ms")
+                            .swipeActions(edge: .trailing, allowsFullSwipe: true) {
+                                Button(role: .destructive) {
+                                    viewModel.removeHistoryEntries(withIDs: [entry.id])
+                                } label: {
+                                    Label("Delete", systemImage: "trash")
                                 }
                             }
-                            .font(.system(.caption2, design: .monospaced))
-                            .foregroundStyle(.secondary)
                         }
+                        .listRowBackground(Color(.systemGray6).opacity(0.5))
                     }
-                    .listRowBackground(Color(.systemGray6).opacity(0.5))
                 }
-                .onDelete(perform: deleteFilteredHistoryEntries)
             }
         }
         .scrollContentBackground(.hidden)
@@ -108,6 +124,7 @@ struct HistoryView: View {
 }
 
 struct HistoryDetailView: View {
+    @Environment(\.appDensity) private var appDensity
     @Bindable var viewModel: DomainViewModel
     let entry: HistoryEntry
     @Environment(\.dismiss) private var dismiss
@@ -130,6 +147,7 @@ struct HistoryDetailView: View {
                 SummaryView(fields: DomainViewModel.summaryFields(from: snapshot))
                     .padding(.top, 8)
                 DomainSectionView(
+                    isCollapsed: .constant(false),
                     rows: DomainViewModel.domainRows(from: snapshot),
                     suggestions: DomainViewModel.suggestionRows(from: snapshot),
                     showSuggestions: entry.availabilityResult?.status == .registered && !entry.suggestions.isEmpty,
@@ -146,42 +164,46 @@ struct HistoryDetailView: View {
                     },
                     onEditNote: nil
                 )
-                    .padding(.top, 16)
+                    .padding(.top, appDensity.metrics.sectionSpacing)
                 OwnershipSectionView(
+                    isCollapsed: .constant(false),
                     rows: DomainViewModel.ownershipRows(from: snapshot),
                     loading: false,
                     error: snapshot.ownershipError,
                     showsHistoryPlaceholder: !DataAccessService.hasAccess(to: .ownershipHistory)
                 )
-                .padding(.top, 16)
+                .padding(.top, appDensity.metrics.sectionSpacing)
                 SubdomainsSectionView(
+                    isCollapsed: .constant(false),
                     rows: DomainViewModel.subdomainRows(from: snapshot),
                     loading: false,
                     error: snapshot.subdomainsError,
                     showsExtendedPlaceholder: !DataAccessService.hasAccess(to: .extendedSubdomains)
                 )
-                .padding(.top, 16)
+                .padding(.top, appDensity.metrics.sectionSpacing)
                 if let comparisonSnapshot = viewModel.comparisonSnapshot(for: entry) {
                     if let changeSummary = entry.changeSummary {
                         DomainChangeSummaryView(summary: changeSummary)
-                            .padding(.top, 16)
+                            .padding(.top, appDensity.metrics.sectionSpacing)
                     }
                     DomainDiffView(
                         title: "Compared With Previous Snapshot",
                         sections: DomainDiffService.diff(from: comparisonSnapshot, to: snapshot),
                         showsUnchanged: false
                     )
-                    .padding(.top, 16)
+                    .padding(.top, appDensity.metrics.sectionSpacing)
                 }
                 DNSSectionView(
+                    isCollapsed: .constant(false),
                     dnssecLabel: DomainViewModel.dnssecLabel(from: snapshot),
                     sections: DomainViewModel.dnsRows(from: snapshot),
                     ptrMessage: DomainViewModel.ptrMessage(from: snapshot),
                     loading: false,
                     sectionError: snapshot.dnsError
                 )
-                .padding(.top, 16)
+                .padding(.top, appDensity.metrics.sectionSpacing)
                 WebSectionView(
+                    isCollapsed: .constant(false),
                     certificateRows: DomainViewModel.webCertificateRows(from: snapshot),
                     sslInfo: snapshot.sslInfo,
                     sslLoading: false,
@@ -195,14 +217,16 @@ struct HistoryDetailView: View {
                     redirectError: snapshot.redirectChainError,
                     finalURL: snapshot.redirectChain.last?.url
                 )
-                .padding(.top, 16)
+                .padding(.top, appDensity.metrics.sectionSpacing)
                 EmailSectionView(
+                    isCollapsed: .constant(false),
                     rows: DomainViewModel.emailRows(from: snapshot),
                     loading: false,
                     error: snapshot.emailSecurityError
                 )
-                .padding(.top, 16)
+                .padding(.top, appDensity.metrics.sectionSpacing)
                 NetworkSectionView(
+                    isCollapsed: .constant(false),
                     reachabilityRows: DomainViewModel.reachabilityRows(from: snapshot),
                     reachabilityLoading: false,
                     reachabilityError: snapshot.reachabilityError,
@@ -221,7 +245,7 @@ struct HistoryDetailView: View {
                     customPortInput: .constant(""),
                     onScanCustomPorts: {}
                 )
-                .padding(.top, 16)
+                .padding(.top, appDensity.metrics.sectionSpacing)
             }
             .padding(.horizontal)
             .padding(.bottom, 32)
@@ -244,17 +268,45 @@ struct HistoryDetailView: View {
             Image(systemName: "archivebox")
                 .font(.caption)
             Text("Snapshot from \(dateFormatter.string(from: entry.timestamp))")
-                .font(.system(.caption, design: .monospaced))
+                .font(appDensity.font(.caption))
             Spacer()
             Text("Live re-run available")
-                .font(.system(.caption2, design: .monospaced))
+                .font(appDensity.font(.caption2))
                 .foregroundStyle(.secondary)
         }
         .foregroundStyle(.secondary)
         .padding(8)
         .frame(maxWidth: .infinity, alignment: .leading)
         .background(Color(.systemGray6).opacity(0.3))
-        .cornerRadius(6)
+        .clipShape(RoundedRectangle(cornerRadius: appDensity.metrics.cardCornerRadius))
         .padding(.vertical, 12)
     }
 }
+
+private struct HistoryGroup: Identifiable {
+    let title: String
+    let entries: [HistoryEntry]
+
+    var id: String { title }
+
+    static func groups(for entries: [HistoryEntry]) -> [HistoryGroup] {
+        let calendar = Calendar.current
+        let today = Date()
+        let yesterday = calendar.date(byAdding: .day, value: -1, to: today) ?? today
+
+        let grouped = Dictionary(grouping: entries) { entry -> String in
+            if calendar.isDate(entry.timestamp, inSameDayAs: today) {
+                return "Today"
+            }
+            if calendar.isDate(entry.timestamp, inSameDayAs: yesterday) {
+                return "Yesterday"
+            }
+            return "Older"
+        }
+
+        return ["Today", "Yesterday", "Older"].compactMap { title in
+            guard let entries = grouped[title], !entries.isEmpty else { return nil }
+            return HistoryGroup(title: title, entries: entries)
+        }
+    }
+}
diff --git a/DomainDig/LookupRuntime.swift b/DomainDig/LookupRuntime.swift
index b70a134..08ea122 100644
--- a/DomainDig/LookupRuntime.swift
+++ b/DomainDig/LookupRuntime.swift
@@ -58,6 +58,13 @@ actor LookupRuntime {
     private var inFlight: [RequestKey: Task<CachedPayload, Never>] = [:]
     private var nextAllowedAt: [RateLimitBucket: Date] = [:]
 
+    func clearCache() {
+        cache.removeAll()
+        inFlight.values.forEach { $0.cancel() }
+        inFlight.removeAll()
+        nextAllowedAt.removeAll()
+    }
+
     func dns(domain: String) async -> CachedLookupResult<ServiceResult<[DNSSection]>> {
         await execute(
             key: .domain(domain, .dns),
diff --git a/DomainDig/WatchlistView.swift b/DomainDig/WatchlistView.swift
index 7e42b53..0874956 100644
--- a/DomainDig/WatchlistView.swift
+++ b/DomainDig/WatchlistView.swift
@@ -1,9 +1,18 @@
 import SwiftUI
 
 struct WatchlistView: View {
+    @Environment(\.appDensity) private var appDensity
     @Bindable var viewModel: DomainViewModel
     @Environment(\.dismiss) private var dismiss
 
+    private var pinnedDomains: [TrackedDomain] {
+        viewModel.filteredTrackedDomains.filter(\.isPinned)
+    }
+
+    private var otherDomains: [TrackedDomain] {
+        viewModel.filteredTrackedDomains.filter { !$0.isPinned }
+    }
+
     var body: some View {
         List {
             if viewModel.batchLookupSource == .watchlistRefresh, (!viewModel.batchResults.isEmpty || viewModel.batchLookupRunning) {
@@ -13,7 +22,7 @@ struct WatchlistView: View {
                             .tint(.cyan)
                         HStack {
                             Text(viewModel.batchProgressLabel)
-                                .font(.system(.caption, design: .monospaced))
+                                .font(appDensity.font(.caption))
                                 .foregroundStyle(.secondary)
                             Spacer()
                             if viewModel.batchLookupRunning {
@@ -21,7 +30,7 @@ struct WatchlistView: View {
                                     viewModel.cancelBatchLookup()
                                 }
                                 .buttonStyle(.bordered)
-                                .font(.system(.caption2, design: .monospaced))
+                                .font(appDensity.font(.caption2))
                             }
                         }
 
@@ -36,87 +45,34 @@ struct WatchlistView: View {
 
             if viewModel.filteredTrackedDomains.isEmpty {
                 Section {
-                    VStack(alignment: .leading, spacing: 8) {
-                        Text("No tracked domains yet")
-                            .font(.system(.callout, design: .monospaced))
-                            .foregroundStyle(.primary)
-                        Text("Tracked domains appear here. Tracking is local and manual for now.")
-                            .font(.system(.caption, design: .monospaced))
-                            .foregroundStyle(.secondary)
-                    }
-                    .padding(.vertical, 8)
+                    EmptyStateCardView(
+                        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"
+                    )
                 }
                 .listRowBackground(Color(.systemGray6).opacity(0.5))
             } else {
                 if let limitMessage = PremiumAccessService.trackedDomainLimitMessage(currentCount: viewModel.trackedDomains.count) {
                     Section {
                         Text(limitMessage)
-                            .font(.system(.caption, design: .monospaced))
+                            .font(appDensity.font(.caption))
                             .foregroundStyle(.secondary)
                     }
                     .listRowBackground(Color(.systemGray6).opacity(0.5))
                 }
 
-                Section {
-                    ForEach(viewModel.filteredTrackedDomains) { trackedDomain in
-                        NavigationLink {
-                            TrackedDomainDetailView(viewModel: viewModel, trackedDomain: trackedDomain)
-                        } label: {
-                            WatchlistRowView(
-                                trackedDomain: trackedDomain,
-                                isRefreshing: viewModel.refreshingTrackedDomainID == trackedDomain.id
-                            )
-                        }
-                        .buttonStyle(.plain)
-                        .swipeActions(edge: .leading, allowsFullSwipe: false) {
-                            Button {
-                                viewModel.togglePinned(for: trackedDomain)
-                            } label: {
-                                Label(trackedDomain.isPinned ? "Unpin" : "Pin", systemImage: trackedDomain.isPinned ? "pin.slash" : "pin")
-                            }
-                            .tint(.yellow)
-                        }
-                        .swipeActions(edge: .trailing, allowsFullSwipe: false) {
-                            Button(role: .destructive) {
-                                viewModel.deleteTrackedDomain(trackedDomain)
-                            } label: {
-                                Label("Delete", systemImage: "trash")
-                            }
-                        }
-                        .contextMenu {
-                            Button {
-                                viewModel.refreshTrackedDomain(trackedDomain)
-                            } label: {
-                                Label("Refresh", systemImage: "arrow.clockwise")
-                            }
-
-                            Button {
-                                dismiss()
-                                viewModel.rerunInspection(for: trackedDomain)
-                            } label: {
-                                Label("Open Inspection", systemImage: "magnifyingglass")
-                            }
-
-                            Button {
-                                viewModel.togglePinned(for: trackedDomain)
-                            } label: {
-                                Label(trackedDomain.isPinned ? "Unpin" : "Pin", systemImage: trackedDomain.isPinned ? "pin.slash" : "pin")
-                            }
+                if !pinnedDomains.isEmpty {
+                    trackedSection(title: "Pinned", domains: pinnedDomains)
+                }
 
-                            Button(role: .destructive) {
-                                viewModel.deleteTrackedDomain(trackedDomain)
-                            } label: {
-                                Label("Delete", systemImage: "trash")
-                            }
-                        }
-                        .listRowBackground(Color(.systemGray6).opacity(0.5))
-                    }
-                    .onDelete(perform: deleteFilteredTrackedDomains)
-                } header: {
-                    Text("Tracked Domains")
+                if !otherDomains.isEmpty {
+                    trackedSection(title: pinnedDomains.isEmpty ? "Tracked Domains" : "Others", domains: otherDomains)
                 }
             }
         }
+        .animation(.easeInOut(duration: 0.2), value: viewModel.filteredTrackedDomains.map(\.id))
         .scrollContentBackground(.hidden)
         .background(Color.black)
         .navigationTitle("Watchlist")
@@ -138,6 +94,7 @@ struct WatchlistView: View {
                         }
 
                         Button(viewModel.batchLookupRunning ? "Check All Running" : "Check All") {
+                            AppHaptics.refresh()
                             viewModel.refreshAllTrackedDomains()
                         }
                         .disabled(viewModel.batchLookupRunning)
@@ -170,6 +127,86 @@ struct WatchlistView: View {
         .preferredColorScheme(.dark)
     }
 
+    @ViewBuilder
+    private func trackedSection(title: String, domains: [TrackedDomain]) -> some View {
+        Section(title) {
+            ForEach(domains) { trackedDomain in
+                trackedDomainRow(trackedDomain)
+            }
+        }
+    }
+
+    private func trackedDomainRow(_ trackedDomain: TrackedDomain) -> some View {
+        NavigationLink {
+            TrackedDomainDetailView(viewModel: viewModel, trackedDomain: trackedDomain)
+        } label: {
+            WatchlistRowView(
+                trackedDomain: trackedDomain,
+                isRefreshing: viewModel.refreshingTrackedDomainID == trackedDomain.id
+            )
+        }
+        .buttonStyle(.plain)
+        .swipeActions(edge: .leading, allowsFullSwipe: false) {
+            Button {
+                AppHaptics.refresh()
+                viewModel.refreshTrackedDomain(trackedDomain)
+            } label: {
+                Label("Refresh", systemImage: "arrow.clockwise")
+            }
+            .tint(.cyan)
+
+            Button {
+                viewModel.togglePinned(for: trackedDomain)
+            } label: {
+                Label(trackedDomain.isPinned ? "Unpin" : "Pin", systemImage: trackedDomain.isPinned ? "pin.slash" : "pin")
+            }
+            .tint(.yellow)
+        }
+        .swipeActions(edge: .trailing, allowsFullSwipe: false) {
+            Button {
+                AppHaptics.refresh()
+                viewModel.refreshTrackedDomain(trackedDomain)
+            } label: {
+                Label("Refresh", systemImage: "arrow.clockwise")
+            }
+            .tint(.cyan)
+
+            Button(role: .destructive) {
+                viewModel.deleteTrackedDomain(trackedDomain)
+            } label: {
+                Label("Delete", systemImage: "trash")
+            }
+        }
+        .contextMenu {
+            Button {
+                AppHaptics.refresh()
+                viewModel.refreshTrackedDomain(trackedDomain)
+            } label: {
+                Label("Refresh", systemImage: "arrow.clockwise")
+            }
+
+            Button {
+                dismiss()
+                viewModel.rerunInspection(for: trackedDomain)
+            } label: {
+                Label("Open Inspection", systemImage: "magnifyingglass")
+            }
+
+            Button {
+                viewModel.togglePinned(for: trackedDomain)
+            } label: {
+                Label(trackedDomain.isPinned ? "Unpin" : "Pin", systemImage: trackedDomain.isPinned ? "pin.slash" : "pin")
+            }
+
+            Button(role: .destructive) {
+                viewModel.deleteTrackedDomain(trackedDomain)
+            } label: {
+                Label("Delete", systemImage: "trash")
+            }
+        }
+        .listRowBackground(Color(.systemGray6).opacity(0.5))
+    }
+
     private var batchSummaryBinding: Binding<BatchSweepSummary?> {
         Binding(
             get: { viewModel.latestBatchSweepSummary },
@@ -203,11 +240,12 @@ struct WatchlistView: View {
 }
 
 struct WatchlistRowView: View {
+    @Environment(\.appDensity) private var appDensity
     let trackedDomain: TrackedDomain
     let isRefreshing: Bool
 
     var body: some View {
-        VStack(alignment: .leading, spacing: 6) {
+        VStack(alignment: .leading, spacing: appDensity.metrics.rowSpacing + 1) {
             HStack(alignment: .firstTextBaseline, spacing: 8) {
                 if trackedDomain.isPinned {
                     Image(systemName: "pin.fill")
@@ -215,7 +253,7 @@ struct WatchlistRowView: View {
                         .foregroundStyle(.yellow)
                 }
                 Text(trackedDomain.domain)
-                    .font(.system(.callout, design: .monospaced))
+                    .font(appDensity.font(.callout))
                     .foregroundStyle(.primary)
                     .lineLimit(2)
                     .multilineTextAlignment(.leading)
@@ -224,19 +262,19 @@ struct WatchlistRowView: View {
             }
 
             Text("Updated \(trackedDomain.updatedAt.formatted(date: .abbreviated, time: .shortened))")
-                .font(.system(.caption2, design: .monospaced))
+                .font(appDensity.font(.caption2))
                 .foregroundStyle(.secondary)
 
             indicatorRow
 
             if let note = trackedDomain.note?.trimmingCharacters(in: .whitespacesAndNewlines), !note.isEmpty {
                 Text(note)
-                    .font(.system(.caption, design: .monospaced))
+                    .font(appDensity.font(.caption))
                     .foregroundStyle(.secondary)
                     .lineLimit(2)
             } else if let summary = trackedDomain.lastChangeSummary {
                 Text(summary.message)
-                    .font(.system(.caption, design: .monospaced))
+                    .font(appDensity.font(.caption))
                     .foregroundStyle(.secondary)
                     .lineLimit(2)
             }
@@ -259,84 +297,32 @@ struct WatchlistRowView: View {
     @ViewBuilder
     private var statusBadge: some View {
         if isRefreshing {
-            HStack(spacing: 6) {
-                ProgressView()
-                    .controlSize(.small)
-                Text("Refreshing")
-                    .font(.system(.caption2, design: .monospaced))
-                    .foregroundStyle(.secondary)
-            }
-            .padding(.horizontal, 8)
-            .padding(.vertical, 4)
-            .background(Color(.systemGray5).opacity(0.6))
-            .clipShape(Capsule())
+            AppStatusBadgeView(model: .init(title: "Refreshing", systemImage: "arrow.clockwise", foregroundColor: .secondary, backgroundColor: Color(.systemGray5).opacity(0.6)))
         } else {
-            Text(availabilityLabel(trackedDomain.lastKnownAvailability))
-                .font(.system(.caption2, design: .monospaced))
-                .foregroundStyle(badgeColor)
-                .padding(.horizontal, 8)
-                .padding(.vertical, 4)
-                .background(badgeBackground)
-                .clipShape(Capsule())
-        }
-    }
-
-    private var badgeColor: Color {
-        switch trackedDomain.lastKnownAvailability {
-        case .available:
-            return .green
-        case .registered:
-            return .yellow
-        case .unknown, .none:
-            return .secondary
-        }
-    }
-
-    private var badgeBackground: Color {
-        switch trackedDomain.lastKnownAvailability {
-        case .available:
-            return .green.opacity(0.16)
-        case .registered:
-            return .yellow.opacity(0.16)
-        case .unknown, .none:
-            return Color(.systemGray5).opacity(0.6)
+            AppStatusBadgeView(model: AppStatusFactory.availability(trackedDomain.lastKnownAvailability))
         }
     }
 
     @ViewBuilder
     private var indicatorRow: some View {
         HStack(spacing: 8) {
-            if let severity = trackedDomain.lastChangeSeverity, severity >= .medium {
-                Label(severity.title, systemImage: severity == .high ? "exclamationmark.circle.fill" : "circle.fill")
-                    .font(.system(.caption2, design: .monospaced))
-                    .foregroundStyle(severity == .high ? .red : .yellow)
-            } else {
-                Label("Stable", systemImage: "circle.fill")
-                    .font(.system(.caption2, design: .monospaced))
-                    .foregroundStyle(.secondary)
-            }
+            AppStatusBadgeView(model: AppStatusFactory.change(trackedDomain.lastChangeSummary))
 
             if trackedDomain.certificateWarningLevel != .none {
-                Text(certificateLabel)
-                    .font(.system(.caption2, design: .monospaced))
-                    .foregroundStyle(trackedDomain.certificateWarningLevel == .critical ? .red : .yellow)
-                    .padding(.horizontal, 8)
-                    .padding(.vertical, 4)
-                    .background((trackedDomain.certificateWarningLevel == .critical ? Color.red : Color.yellow).opacity(0.16))
-                    .clipShape(Capsule())
+                AppStatusBadgeView(model: certificateBadge)
             }
         }
     }
 
-    private var certificateLabel: String {
+    private var certificateBadge: AppStatusBadgeModel {
         let days = trackedDomain.certificateDaysRemaining.map { "\($0)d" } ?? "Soon"
         switch trackedDomain.certificateWarningLevel {
         case .critical:
-            return "Cert \(days)"
+            return .init(title: "Invalid \(days)", systemImage: "xmark.octagon.fill", foregroundColor: .red, backgroundColor: .red.opacity(0.16))
         case .warning:
-            return "Warn \(days)"
+            return .init(title: "Expiring \(days)", systemImage: "exclamationmark.triangle.fill", foregroundColor: .yellow, backgroundColor: .yellow.opacity(0.16))
         case .none:
-            return ""
+            return .init(title: "Valid", systemImage: "lock.fill", foregroundColor: .green, backgroundColor: .green.opacity(0.16))
         }
     }
 }