From 237a72a03102319638c5b0572ca3ab543238b821 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Sun, 26 Apr 2026 00:39:28 -0500 Subject: DomainDig v3.5.0: Expand the Pro+ Data+ intelligence layer with deeper local historical context and inferred enrichment. - add derived intelligence fields for provider fingerprinting, classification, ownership transitions, hosting transitions, subdomain history, risk signals, and inferred timeline events - expand DNS history beyond A/NS snapshots to retain A, AAAA, MX, NS, TXT, and CNAME change state - persist enriched intelligence in snapshots and history entries so analysis is local-first and incremental - add a dedicated Data+ Intelligence panel to current and historical domain detail views - surface intelligence events in timeline rows and include Data+ changes in diff output - preserve non-blocking inspection behavior by keeping enrichment additive to the main lookup path This makes Pro+ materially deeper for investigative workflows by improving historical ownership visibility, infrastructure context, hosting change detection, subdomain intelligence, and explainable risk signals. --- DomainDig/ContentView.swift | 133 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) (limited to 'DomainDig/ContentView.swift') diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift index 3716f88..5a469e1 100644 --- a/DomainDig/ContentView.swift +++ b/DomainDig/ContentView.swift @@ -11,6 +11,7 @@ enum LookupInputMode: String, CaseIterable, Identifiable { enum ResultSection: String, Hashable { case domain + case intelligence case ownership case dns case web @@ -78,6 +79,10 @@ struct ContentView: View { .padding(.top, appDensity.metrics.cardSpacing) } } + if let report = viewModel.currentReport { + intelligenceSection(report: report) + .padding(.top, appDensity.metrics.sectionSpacing) + } domainOverviewSection .padding(.top, appDensity.metrics.sectionSpacing) ownershipSection @@ -415,6 +420,14 @@ struct ContentView: View { ) } + private func intelligenceSection(report: DomainReport) -> some View { + IntelligenceSectionView( + isCollapsed: sectionCollapsedBinding(.intelligence), + report: report, + showsPlaceholder: FeatureAccessService.currentTier != .proPlus + ) + } + private var ownershipSection: some View { OwnershipSectionView( isCollapsed: sectionCollapsedBinding(.ownership), @@ -1558,6 +1571,126 @@ struct OwnershipSectionView: View { } } +struct IntelligenceSectionView: View { + @Environment(\.appDensity) private var appDensity + @Binding var isCollapsed: Bool + let report: DomainReport + let showsPlaceholder: Bool + + var body: some View { + CollapsibleSectionView(title: "Data+ Intelligence", isCollapsed: $isCollapsed) { + CardView(allowsHorizontalScroll: false) { + if showsPlaceholder { + MessageRowView(text: "Richer intelligence history, hosting analysis, and risk signals are available in Pro+", isError: false) + } else { + if let provider = report.inferredProvider { + intelligenceBlock(title: "Infrastructure") { + LabeledValueRow(row: .init(label: "Provider", value: provider.name, tone: .primary)) + if !provider.evidence.isEmpty { + MessageRowView(text: provider.evidence.joined(separator: " • "), isError: false) + } + if !report.priorProviders.isEmpty { + LabeledValueRow(row: .init(label: "Prior", value: report.priorProviders.joined(separator: ", "), tone: .secondary)) + } + } + } + if let classification = report.domainClassification { + intelligenceBlock(title: "Classification") { + LabeledValueRow(row: .init(label: "Purpose", value: classification.kind.title, tone: .primary)) + MessageRowView(text: classification.reasons.joined(separator: " • "), isError: false) + } + } + intelligenceBlock(title: "Risk Signals") { + if report.riskSignals.isEmpty { + MessageRowView(text: "No material historical risk signals detected", isError: false) + } else { + ForEach(report.riskSignals.prefix(4)) { signal in + VStack(alignment: .leading, spacing: 3) { + Text(signal.title) + .font(appDensity.font(.caption, weight: .semibold)) + Text(signal.detail) + .font(appDensity.font(.caption2)) + .foregroundStyle(.secondary) + } + } + } + } + intelligenceBlock(title: "Ownership History") { + if report.ownershipTransitions.isEmpty { + MessageRowView(text: "No ownership transitions observed locally", isError: false) + } else { + ForEach(report.ownershipTransitions.prefix(4)) { event in + intelligenceEventRow(date: event.date, title: event.summary) + } + } + } + intelligenceBlock(title: "Hosting History") { + if report.hostingTransitions.isEmpty { + MessageRowView(text: "No hosting transitions observed locally", isError: false) + } else { + ForEach(report.hostingTransitions.prefix(4)) { event in + intelligenceEventRow(date: event.date, title: event.summary) + } + } + } + intelligenceBlock(title: "Subdomain Intelligence") { + if report.subdomainHistory.isEmpty { + MessageRowView(text: "No subdomain history available", isError: false) + } else { + ForEach(report.subdomainHistory.prefix(5)) { item in + VStack(alignment: .leading, spacing: 3) { + HStack { + Text(item.hostname) + .font(appDensity.font(.caption)) + Spacer() + if item.isEphemeral { + Text("Ephemeral") + .font(appDensity.font(.caption2)) + .foregroundStyle(.yellow) + } + } + Text("First \(item.firstSeen.formatted(date: .abbreviated, time: .omitted)) • Last \(item.lastSeen.formatted(date: .abbreviated, time: .omitted)) • Seen \(item.recurrenceCount)x") + .font(appDensity.font(.caption2)) + .foregroundStyle(.secondary) + } + } + } + } + intelligenceBlock(title: "Timeline") { + if report.intelligenceTimeline.isEmpty { + MessageRowView(text: "No inferred intelligence events yet", isError: false) + } else { + ForEach(report.intelligenceTimeline.prefix(5)) { event in + intelligenceEventRow(date: event.date, title: "\(event.title): \(event.detail)") + } + } + } + } + } + } + } + + @ViewBuilder + private func intelligenceBlock(title: String, @ViewBuilder content: () -> Content) -> some View { + VStack(alignment: .leading, spacing: 8) { + Text(title) + .font(appDensity.font(.subheadline, weight: .semibold)) + .foregroundStyle(.cyan) + content() + } + } + + private func intelligenceEventRow(date: Date, title: String) -> some View { + VStack(alignment: .leading, spacing: 3) { + Text(date.formatted(date: .abbreviated, time: .omitted)) + .font(appDensity.font(.caption2)) + .foregroundStyle(.secondary) + Text(title) + .font(appDensity.font(.caption)) + } + } +} + struct SubdomainsSectionView: View { @Environment(\.appDensity) private var appDensity @Binding var isCollapsed: Bool -- cgit v1.2.3