krz/domain-dig

an ios app for DNS & SSL analysis

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

b4b94d54cbed9334a28cdcd70d3a8a0fd06e04ef

unsigned

author: Christian Cleberg <hello@cleberg.net> · 2026-07-17T13:36:05Z

Implement v4.6.0: sweep Live Activity, share extension, iPad split view, actionable notifications

- Sweep Live Activity: SweepActivityAttributes (Shared/), lock-screen + Dynamic
  Island UI in the widget extension, driven by SweepActivityController wired into
  the batch pipeline (begin/update/end); NSSupportsLiveActivities in Info.plist.
- Share extension (DomainDigShareExtension): accepts a web URL from the share
  sheet, extracts the host, and hands it to the app via the App Group inbox
  (DomainDigShareInbox); the app consumes it on activation and inspects it.
- iPad layout: RootTabView uses NavigationSplitView in the regular size class
  and the tab bar in compact.
- Actionable notifications: per-domain threadIdentifier grouping, a Re-inspect
  action, and tap routing into the domain detail via the intent router.
- Bump version to 4.6.0 (build 38) across app, widget, and share targets.
 DomainDig.xcodeproj/project.pbxproj                | 182 +++++++++++++++++++--
 .../xcschemes/xcschememanagement.plist             |   7 +-
 DomainDig/AppVersion.swift                         |   2 +-
 DomainDig/DomainDigApp.swift                       |   9 +
 DomainDig/DomainViewModel.swift                    |  16 ++
 DomainDig/Info.plist                               |   2 +
 DomainDig/LocalNotificationService.swift           |  65 +++++++-
 DomainDig/RootTabView.swift                        | 136 ++++++++++-----
 DomainDig/SweepActivityController.swift            |  63 +++++++
 .../DomainDigShareExtension.entitlements           |  10 ++
 DomainDigShareExtension/Info.plist                 |  39 +++++
 DomainDigShareExtension/ShareViewController.swift  |  65 ++++++++
 DomainDigWidget/DomainDigWidgetBundle.swift        |   1 +
 DomainDigWidget/SweepLiveActivity.swift            |  96 +++++++++++
 Shared/DomainDigShareInbox.swift                   |  28 ++++
 Shared/SweepActivityAttributes.swift               |  25 +++
 16 files changed, 677 insertions(+), 69 deletions(-)

diff --git a/DomainDig.xcodeproj/project.pbxproj b/DomainDig.xcodeproj/project.pbxproj
index dbd0873..708ef72 100644
--- a/DomainDig.xcodeproj/project.pbxproj
+++ b/DomainDig.xcodeproj/project.pbxproj
@@ -13,8 +13,9 @@
 		8BBFEF0C2F9874AE00E8E144 /* LookupSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BBFEF062F9874AE00E8E144 /* LookupSnapshot.swift */; };
 		8BCA3CC02F9C8D57004B742C /* LocalAPIModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BCA3CBC2F9C8D57004B742C /* LocalAPIModels.swift */; };
 		8BCA3CC12F9C8D57004B742C /* LocalAPIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BCA3CBD2F9C8D57004B742C /* LocalAPIService.swift */; };
-		8BF9DA872F9B13FB00EF41D5 /* DomainDataPortabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF9DA842F9B13FB00EF41D5 /* DomainDataPortabilityService.swift */; };
 		8BDA00010000000000000002 /* DomainDigWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 8BDA00010000000000000001 /* DomainDigWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+		8BDB00010000000000000002 /* DomainDigShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 8BDB00010000000000000001 /* DomainDigShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+		8BF9DA872F9B13FB00EF41D5 /* DomainDataPortabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF9DA842F9B13FB00EF41D5 /* DomainDataPortabilityService.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -25,6 +26,13 @@
 			remoteGlobalIDString = 8BDA00010000000000000006;
 			remoteInfo = DomainDigWidgetExtension;
 		};
+		8BDB00010000000000000003 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 8B7800612F6090E300933221 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 8BDB00010000000000000006;
+			remoteInfo = DomainDigShareExtension;
+		};
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -35,6 +43,7 @@
 			dstSubfolderSpec = 13;
 			files = (
 				8BDA00010000000000000002 /* DomainDigWidgetExtension.appex in Embed Foundation Extensions */,
+				8BDB00010000000000000002 /* DomainDigShareExtension.appex in Embed Foundation Extensions */,
 			);
 			name = "Embed Foundation Extensions";
 			runOnlyForDeploymentPostprocessing = 0;
@@ -49,8 +58,9 @@
 		8BBFEF062F9874AE00E8E144 /* LookupSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LookupSnapshot.swift; sourceTree = "<group>"; };
 		8BCA3CBC2F9C8D57004B742C /* LocalAPIModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAPIModels.swift; sourceTree = "<group>"; };
 		8BCA3CBD2F9C8D57004B742C /* LocalAPIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAPIService.swift; sourceTree = "<group>"; };
-		8BF9DA842F9B13FB00EF41D5 /* DomainDataPortabilityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainDataPortabilityService.swift; sourceTree = "<group>"; };
 		8BDA00010000000000000001 /* DomainDigWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = DomainDigWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+		8BDB00010000000000000001 /* DomainDigShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = DomainDigShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+		8BF9DA842F9B13FB00EF41D5 /* DomainDataPortabilityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainDataPortabilityService.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -70,6 +80,13 @@
 			);
 			target = 8BDA00010000000000000006 /* DomainDigWidgetExtension */;
 		};
+		8BDB0001000000000000000F /* Exceptions for "DomainDigShareExtension" folder in "DomainDigShareExtension" target */ = {
+			isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+			membershipExceptions = (
+				Info.plist,
+			);
+			target = 8BDB00010000000000000006 /* DomainDigShareExtension */;
+		};
 /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
 
 /* Begin PBXFileSystemSynchronizedRootGroup section */
@@ -94,6 +111,14 @@
 			path = DomainDigWidget;
 			sourceTree = "<group>";
 		};
+		8BDB0001000000000000000E /* DomainDigShareExtension */ = {
+			isa = PBXFileSystemSynchronizedRootGroup;
+			exceptions = (
+				8BDB0001000000000000000F /* Exceptions for "DomainDigShareExtension" folder in "DomainDigShareExtension" target */,
+			);
+			path = DomainDigShareExtension;
+			sourceTree = "<group>";
+		};
 /* End PBXFileSystemSynchronizedRootGroup section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -111,6 +136,13 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		8BDB00010000000000000008 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
@@ -120,6 +152,7 @@
 				8B78006B2F6090E300933221 /* DomainDig */,
 				8BDA0001000000000000000D /* Shared */,
 				8BDA0001000000000000000E /* DomainDigWidget */,
+				8BDB0001000000000000000E /* DomainDigShareExtension */,
 				8B78006A2F6090E300933221 /* Products */,
 				8BBFEF062F9874AE00E8E144 /* LookupSnapshot.swift */,
 				8BBFEF042F9874AE00E8E144 /* DomainReportBuilder.swift */,
@@ -136,6 +169,7 @@
 			children = (
 				8B7800692F6090E300933221 /* DomainDig.app */,
 				8BDA00010000000000000001 /* DomainDigWidgetExtension.appex */,
+				8BDB00010000000000000001 /* DomainDigShareExtension.appex */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -156,6 +190,7 @@
 			);
 			dependencies = (
 				8BDA00010000000000000004 /* PBXTargetDependency */,
+				8BDB00010000000000000004 /* PBXTargetDependency */,
 			);
 			fileSystemSynchronizedGroups = (
 				8B78006B2F6090E300933221 /* DomainDig */,
@@ -191,15 +226,30 @@
 			productReference = 8BDA00010000000000000001 /* DomainDigWidgetExtension.appex */;
 			productType = "com.apple.product-type.app-extension";
 		};
-/* End PBXNativeTarget section */
-
-/* Begin PBXTargetDependency section */
-		8BDA00010000000000000004 /* PBXTargetDependency */ = {
-			isa = PBXTargetDependency;
-			target = 8BDA00010000000000000006 /* DomainDigWidgetExtension */;
-			targetProxy = 8BDA00010000000000000003 /* PBXContainerItemProxy */;
+		8BDB00010000000000000006 /* DomainDigShareExtension */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 8BDB0001000000000000000A /* Build configuration list for PBXNativeTarget "DomainDigShareExtension" */;
+			buildPhases = (
+				8BDB00010000000000000007 /* Sources */,
+				8BDB00010000000000000008 /* Frameworks */,
+				8BDB00010000000000000009 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			fileSystemSynchronizedGroups = (
+				8BDA0001000000000000000D /* Shared */,
+				8BDB0001000000000000000E /* DomainDigShareExtension */,
+			);
+			name = DomainDigShareExtension;
+			packageProductDependencies = (
+			);
+			productName = DomainDigShareExtension;
+			productReference = 8BDB00010000000000000001 /* DomainDigShareExtension.appex */;
+			productType = "com.apple.product-type.app-extension";
 		};
-/* End PBXTargetDependency section */
+/* End PBXNativeTarget section */
 
 /* Begin PBXProject section */
 		8B7800612F6090E300933221 /* Project object */ = {
@@ -220,6 +270,9 @@
 					8BDA00010000000000000006 = {
 						CreatedOnToolsVersion = 26.3;
 					};
+					8BDB00010000000000000006 = {
+						CreatedOnToolsVersion = 26.3;
+					};
 				};
 			};
 			buildConfigurationList = 8B7800642F6090E300933221 /* Build configuration list for PBXProject "DomainDig" */;
@@ -238,6 +291,7 @@
 			targets = (
 				8B7800682F6090E300933221 /* DomainDig */,
 				8BDA00010000000000000006 /* DomainDigWidgetExtension */,
+				8BDB00010000000000000006 /* DomainDigShareExtension */,
 			);
 		};
 /* End PBXProject section */
@@ -257,6 +311,13 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		8BDB00010000000000000009 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -281,8 +342,28 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		8BDB00010000000000000007 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXSourcesBuildPhase section */
 
+/* Begin PBXTargetDependency section */
+		8BDA00010000000000000004 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 8BDA00010000000000000006 /* DomainDigWidgetExtension */;
+			targetProxy = 8BDA00010000000000000003 /* PBXContainerItemProxy */;
+		};
+		8BDB00010000000000000004 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 8BDB00010000000000000006 /* DomainDigShareExtension */;
+			targetProxy = 8BDB00010000000000000003 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
 /* Begin XCBuildConfiguration section */
 		8B7800722F6090E400933221 /* Debug */ = {
 			isa = XCBuildConfiguration;
@@ -414,7 +495,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_ENTITLEMENTS = DomainDig/DomainDig.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 37;
+				CURRENT_PROJECT_VERSION = 38;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -431,7 +512,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 4.5.0;
+				MARKETING_VERSION = 4.6.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -451,7 +532,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_ENTITLEMENTS = DomainDig/DomainDig.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 37;
+				CURRENT_PROJECT_VERSION = 38;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -468,7 +549,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 4.5.0;
+				MARKETING_VERSION = 4.6.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -487,7 +568,7 @@
 				APPLICATION_EXTENSION_API_ONLY = YES;
 				CODE_SIGN_ENTITLEMENTS = DomainDigWidget/DomainDigWidget.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 37;
+				CURRENT_PROJECT_VERSION = 38;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = DomainDigWidget/Info.plist;
@@ -497,7 +578,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 4.5.0;
+				MARKETING_VERSION = 4.6.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigWidget;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
@@ -515,7 +596,7 @@
 				APPLICATION_EXTENSION_API_ONLY = YES;
 				CODE_SIGN_ENTITLEMENTS = DomainDigWidget/DomainDigWidget.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 37;
+				CURRENT_PROJECT_VERSION = 38;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = DomainDigWidget/Info.plist;
@@ -525,7 +606,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 4.5.0;
+				MARKETING_VERSION = 4.6.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigWidget;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
@@ -537,6 +618,62 @@
 			};
 			name = Release;
 		};
+		8BDB0001000000000000000B /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				APPLICATION_EXTENSION_API_ONLY = YES;
+				CODE_SIGN_ENTITLEMENTS = DomainDigShareExtension/DomainDigShareExtension.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 38;
+				DEVELOPMENT_TEAM = ZCNAX3VL9D;
+				GENERATE_INFOPLIST_FILE = NO;
+				INFOPLIST_FILE = DomainDigShareExtension/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 17.6;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+					"@executable_path/../../Frameworks",
+				);
+				MARKETING_VERSION = 4.6.0;
+				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigShareExtension;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SKIP_INSTALL = YES;
+				SWIFT_APPROACHABLE_CONCURRENCY = YES;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		8BDB0001000000000000000C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				APPLICATION_EXTENSION_API_ONLY = YES;
+				CODE_SIGN_ENTITLEMENTS = DomainDigShareExtension/DomainDigShareExtension.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 38;
+				DEVELOPMENT_TEAM = ZCNAX3VL9D;
+				GENERATE_INFOPLIST_FILE = NO;
+				INFOPLIST_FILE = DomainDigShareExtension/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 17.6;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+					"@executable_path/../../Frameworks",
+				);
+				MARKETING_VERSION = 4.6.0;
+				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigShareExtension;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SKIP_INSTALL = YES;
+				SWIFT_APPROACHABLE_CONCURRENCY = YES;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
@@ -567,6 +704,15 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		8BDB0001000000000000000A /* Build configuration list for PBXNativeTarget "DomainDigShareExtension" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				8BDB0001000000000000000B /* Debug */,
+				8BDB0001000000000000000C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 /* End XCConfigurationList section */
 	};
 	rootObject = 8B7800612F6090E300933221 /* Project object */;
diff --git a/DomainDig.xcodeproj/xcuserdata/cmc.xcuserdatad/xcschemes/xcschememanagement.plist b/DomainDig.xcodeproj/xcuserdata/cmc.xcuserdatad/xcschemes/xcschememanagement.plist
index 2e2db5b..5f600f0 100644
--- a/DomainDig.xcodeproj/xcuserdata/cmc.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/DomainDig.xcodeproj/xcuserdata/cmc.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,11 +9,16 @@
 			<key>orderHint</key>
 			<integer>0</integer>
 		</dict>
-		<key>DomainDigWidgetExtension.xcscheme_^#shared#^_</key>
+		<key>DomainDigShareExtension.xcscheme_^#shared#^_</key>
 		<dict>
 			<key>orderHint</key>
 			<integer>1</integer>
 		</dict>
+		<key>DomainDigWidgetExtension.xcscheme_^#shared#^_</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>2</integer>
+		</dict>
 	</dict>
 	<key>SuppressBuildableAutocreation</key>
 	<dict>
diff --git a/DomainDig/AppVersion.swift b/DomainDig/AppVersion.swift
index 608d9c5..3a86f97 100644
--- a/DomainDig/AppVersion.swift
+++ b/DomainDig/AppVersion.swift
@@ -2,6 +2,6 @@ import Foundation
 
 enum AppVersion {
     nonisolated static var current: String {
-        "4.5.0"
+        "4.6.0"
     }
 }
diff --git a/DomainDig/DomainDigApp.swift b/DomainDig/DomainDigApp.swift
index 8716806..d34c51d 100644
--- a/DomainDig/DomainDigApp.swift
+++ b/DomainDig/DomainDigApp.swift
@@ -40,6 +40,7 @@ struct DomainDigApp: App {
                     viewModel.monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule()
                     IntegrationService.shared.processQueueNow()
                     viewModel.refreshWidgetData()
+                    consumeShareInbox()
                 }
                 .onReceive(NotificationCenter.default.publisher(for: .cloudSyncDidApplyChanges)) { _ in
                     viewModel.refreshPersistedData()
@@ -59,6 +60,14 @@ struct DomainDigApp: App {
             viewModel.monitoringStatusMessage = DomainMonitoringScheduler.shared.syncSchedule()
             IntegrationService.shared.processQueueNow()
             viewModel.refreshWidgetData()
+            consumeShareInbox()
         }
     }
+
+    /// Picks up a domain shared via the share extension and routes it into an
+    /// inspection through the intent router (consumed by `RootTabView`).
+    private func consumeShareInbox() {
+        guard let domain = DomainDigShareInbox.consume() else { return }
+        DomainDigIntentRouter.shared.pendingAction = .inspect(domain)
+    }
 }
diff --git a/DomainDig/DomainViewModel.swift b/DomainDig/DomainViewModel.swift
index 3f37cb9..dcae907 100644
--- a/DomainDig/DomainViewModel.swift
+++ b/DomainDig/DomainViewModel.swift
@@ -2834,6 +2834,11 @@ final class DomainViewModel {
         customPortScanTask?.cancel()
         batchTask?.cancel()
 
+        SweepActivityController.shared.begin(
+            title: source == .watchlistRefresh ? "Watchlist Sweep" : "Batch Lookup",
+            total: domains.count
+        )
+
         batchTask = Task { [weak self] in
             guard let self else { return }
             self.notificationsAuthorized = await LocalNotificationService.shared.requestAuthorizationIfNeeded()
@@ -2928,6 +2933,11 @@ final class DomainViewModel {
                 errorMessage: "Lookup cancelled"
             )
             batchCompletedCount += 1
+            SweepActivityController.shared.update(
+                completed: batchCompletedCount,
+                total: batchTotalCount,
+                currentDomain: batchCurrentDomain
+            )
             return
         }
 
@@ -2956,6 +2966,11 @@ final class DomainViewModel {
             errorMessage: payload.snapshot.statusMessage
         )
         batchCompletedCount += 1
+        SweepActivityController.shared.update(
+            completed: batchCompletedCount,
+            total: batchTotalCount,
+            currentDomain: batchCurrentDomain
+        )
     }
 
     private func finishBatchLookup(source: BatchLookupSource) {
@@ -2989,6 +3004,7 @@ final class DomainViewModel {
             generatedAt: Date()
         )
         latestBatchSweepSummary = summary
+        SweepActivityController.shared.end(changed: changedCount, warnings: warningCount)
 
         if source == .workflow, let activeWorkflowRunID, let activeWorkflowRunName {
             let workflowReports: [DomainReport] = summary.results.compactMap { result in
diff --git a/DomainDig/Info.plist b/DomainDig/Info.plist
index 9e7e3bb..41b193b 100644
--- a/DomainDig/Info.plist
+++ b/DomainDig/Info.plist
@@ -23,6 +23,8 @@
 	</array>
 	<key>CKSharingSupported</key>
 	<true/>
+	<key>NSSupportsLiveActivities</key>
+	<true/>
 	<key>UIBackgroundModes</key>
 	<array>
 		<string>fetch</string>
diff --git a/DomainDig/LocalNotificationService.swift b/DomainDig/LocalNotificationService.swift
index eef0567..f417619 100644
--- a/DomainDig/LocalNotificationService.swift
+++ b/DomainDig/LocalNotificationService.swift
@@ -7,8 +7,27 @@ final class LocalNotificationService {
 
     private init() {}
 
+    static let domainUserInfoKey = "domain"
+    static let domainCategoryIdentifier = "domain-event"
+    static let reinspectActionIdentifier = "reinspect"
+
     func configureForegroundPresentation() {
-        UNUserNotificationCenter.current().delegate = NotificationCenterDelegate.shared
+        let center = UNUserNotificationCenter.current()
+        center.delegate = NotificationCenterDelegate.shared
+
+        let reinspect = UNNotificationAction(
+            identifier: Self.reinspectActionIdentifier,
+            title: "Re-inspect",
+            options: []
+        )
+        center.setNotificationCategories([
+            UNNotificationCategory(
+                identifier: Self.domainCategoryIdentifier,
+                actions: [reinspect],
+                intentIdentifiers: [],
+                options: []
+            )
+        ])
     }
 
     func requestAuthorizationIfNeeded() async -> Bool {
@@ -44,7 +63,8 @@ final class LocalNotificationService {
             identifier: "domain-change-\(domain)",
             title: domain,
             body: message,
-            interruptionLevel: severity == .high ? .timeSensitive : .active
+            interruptionLevel: severity == .high ? .timeSensitive : .active,
+            domain: domain
         )
     }
 
@@ -53,7 +73,8 @@ final class LocalNotificationService {
             identifier: "cert-warning-\(domain)",
             title: domain,
             body: "Certificate expires in \(daysRemaining) days",
-            interruptionLevel: .timeSensitive
+            interruptionLevel: .timeSensitive,
+            domain: domain
         )
     }
 
@@ -74,7 +95,8 @@ final class LocalNotificationService {
             identifier: "monitoring-\(domain)-\(UUID().uuidString)",
             title: domain,
             body: message,
-            interruptionLevel: interruptionLevel
+            interruptionLevel: interruptionLevel,
+            domain: domain
         )
     }
 
@@ -103,7 +125,8 @@ final class LocalNotificationService {
             identifier: "monitoring-summary-\(domain)-\(UUID().uuidString)",
             title: domain,
             body: body,
-            interruptionLevel: interruptionLevel
+            interruptionLevel: interruptionLevel,
+            domain: domain
         )
     }
 
@@ -127,13 +150,20 @@ final class LocalNotificationService {
         identifier: String,
         title: String,
         body: String,
-        interruptionLevel: UNNotificationInterruptionLevel
+        interruptionLevel: UNNotificationInterruptionLevel,
+        domain: String? = nil
     ) async {
         let content = UNMutableNotificationContent()
         content.title = title
         content.body = body
         content.sound = .default
         content.interruptionLevel = interruptionLevel
+        if let domain {
+            // Group alerts per domain and let taps/actions route back into it.
+            content.threadIdentifier = domain
+            content.userInfo = [Self.domainUserInfoKey: domain]
+            content.categoryIdentifier = Self.domainCategoryIdentifier
+        }
 
         let request = UNNotificationRequest(
             identifier: identifier,
@@ -154,4 +184,27 @@ private final class NotificationCenterDelegate: NSObject, UNUserNotificationCent
     ) async -> UNNotificationPresentationOptions {
         [.banner, .list, .sound]
     }
+
+    func userNotificationCenter(
+        _ center: UNUserNotificationCenter,
+        didReceive response: UNNotificationResponse
+    ) async {
+        let userInfo = response.notification.request.content.userInfo
+        guard let domain = userInfo[LocalNotificationService.domainUserInfoKey] as? String,
+              !domain.isEmpty
+        else { return }
+
+        let action: DomainDigDeepLink.Action
+        switch response.actionIdentifier {
+        case LocalNotificationService.reinspectActionIdentifier:
+            action = .inspect(domain)
+        default:
+            // Default tap: open the tracked domain's detail.
+            action = .detail(domain)
+        }
+
+        await MainActor.run {
+            DomainDigIntentRouter.shared.pendingAction = action
+        }
+    }
 }
diff --git a/DomainDig/RootTabView.swift b/DomainDig/RootTabView.swift
index 9823d37..db1f524 100644
--- a/DomainDig/RootTabView.swift
+++ b/DomainDig/RootTabView.swift
@@ -1,15 +1,36 @@
 import SwiftUI
 
-private enum RootTab: Hashable {
+private enum RootTab: Hashable, CaseIterable {
     case dashboard
     case audit
     case history
     case inspect
     case settings
+
+    var title: String {
+        switch self {
+        case .dashboard: return "Dashboard"
+        case .audit: return "Audit"
+        case .history: return "History"
+        case .inspect: return "Inspect"
+        case .settings: return "Settings"
+        }
+    }
+
+    var systemImage: String {
+        switch self {
+        case .dashboard: return "square.grid.2x2"
+        case .audit: return "checklist"
+        case .history: return "clock.arrow.trianglehead.counterclockwise.rotate.90"
+        case .inspect: return "magnifyingglass"
+        case .settings: return "gearshape"
+        }
+    }
 }
 
 struct RootTabView: View {
     @Bindable var viewModel: DomainViewModel
+    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
     @State private var purchaseService = PurchaseService.shared
     @State private var intentRouter = DomainDigIntentRouter.shared
     @State private var detailDomain: TrackedDomain?
@@ -18,44 +39,12 @@ struct RootTabView: View {
     var body: some View {
         let _ = purchaseService.currentTier
 
-        TabView(selection: $selectedTab) {
-            NavigationStack {
-                DashboardView(viewModel: viewModel)
-            }
-            .tabItem {
-                Label("Dashboard", systemImage: "square.grid.2x2")
-            }
-            .tag(RootTab.dashboard)
-
-            NavigationStack {
-                AuditListView(viewModel: viewModel)
-            }
-            .tabItem {
-                Label("Audit", systemImage: "checklist")
-            }
-            .tag(RootTab.audit)
-
-            NavigationStack {
-                HistoryView(viewModel: viewModel)
-            }
-            .tabItem {
-                Label("History", systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90")
-            }
-            .tag(RootTab.history)
-
-            ContentView(viewModel: viewModel)
-                .tabItem {
-                    Label("Inspect", systemImage: "magnifyingglass")
-                }
-                .tag(RootTab.inspect)
-
-            NavigationStack {
-                SettingsView(viewModel: viewModel)
-            }
-            .tabItem {
-                Label("Settings", systemImage: "gearshape")
+        Group {
+            if horizontalSizeClass == .regular {
+                splitLayout
+            } else {
+                tabLayout
             }
-            .tag(RootTab.settings)
         }
         .sheet(isPresented: Binding(
             get: { viewModel.isPaywallPresented },
@@ -79,16 +68,16 @@ struct RootTabView: View {
                 }
             )
         }
-        .onChange(of: purchaseService.currentTier) { _, newValue in
-            if newValue != .free, selectedTab == .inspect, viewModel.trackedDomains.isEmpty == false {
-                selectedTab = .dashboard
-            }
-        }
         .sheet(item: $detailDomain) { trackedDomain in
             NavigationStack {
                 TrackedDomainDetailView(viewModel: viewModel, trackedDomain: trackedDomain)
             }
         }
+        .onChange(of: purchaseService.currentTier) { _, newValue in
+            if newValue != .free, selectedTab == .inspect, viewModel.trackedDomains.isEmpty == false {
+                selectedTab = .dashboard
+            }
+        }
         .onOpenURL { url in
             guard let action = DomainDigDeepLink.action(from: url) else { return }
             perform(action)
@@ -101,6 +90,67 @@ struct RootTabView: View {
         }
     }
 
+    // MARK: Layouts
+
+    /// Compact (iPhone, iPad slide-over): the classic tab bar.
+    private var tabLayout: some View {
+        TabView(selection: $selectedTab) {
+            ForEach(RootTab.allCases, id: \.self) { tab in
+                section(for: tab)
+                    .tabItem {
+                        Label(tab.title, systemImage: tab.systemImage)
+                    }
+                    .tag(tab)
+            }
+        }
+    }
+
+    /// Regular width (iPad, large iPhone landscape): two-column split view.
+    private var splitLayout: some View {
+        NavigationSplitView {
+            List(RootTab.allCases, id: \.self, selection: sidebarSelection) { tab in
+                Label(tab.title, systemImage: tab.systemImage)
+                    .tag(tab)
+            }
+            .navigationTitle("DomainDig")
+        } detail: {
+            section(for: selectedTab)
+        }
+    }
+
+    private var sidebarSelection: Binding<RootTab?> {
+        Binding(
+            get: { selectedTab },
+            set: { selectedTab = $0 ?? selectedTab }
+        )
+    }
+
+    @ViewBuilder
+    private func section(for tab: RootTab) -> some View {
+        switch tab {
+        case .dashboard:
+            NavigationStack {
+                DashboardView(viewModel: viewModel)
+            }
+        case .audit:
+            NavigationStack {
+                AuditListView(viewModel: viewModel)
+            }
+        case .history:
+            NavigationStack {
+                HistoryView(viewModel: viewModel)
+            }
+        case .inspect:
+            ContentView(viewModel: viewModel)
+        case .settings:
+            NavigationStack {
+                SettingsView(viewModel: viewModel)
+            }
+        }
+    }
+
+    // MARK: Intent / deep-link routing
+
     private func consume(_ action: DomainDigDeepLink.Action?) {
         guard let action else { return }
         intentRouter.pendingAction = nil
diff --git a/DomainDig/SweepActivityController.swift b/DomainDig/SweepActivityController.swift
new file mode 100644
index 0000000..26fcb2d
--- /dev/null
+++ b/DomainDig/SweepActivityController.swift
@@ -0,0 +1,63 @@
+import ActivityKit
+import Foundation
+
+/// Starts, updates, and ends the sweep Live Activity around a batch run.
+@MainActor
+final class SweepActivityController {
+    static let shared = SweepActivityController()
+
+    private var activity: Activity<SweepActivityAttributes>?
+
+    private init() {}
+
+    func begin(title: String, total: Int) {
+        guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
+        // A previous activity that never ended (e.g. app killed mid-sweep)
+        // would otherwise linger; replace it.
+        end(changed: 0, warnings: 0, immediately: true)
+
+        let state = SweepActivityAttributes.ContentState(
+            completed: 0,
+            total: total,
+            currentDomain: nil,
+            changed: 0,
+            warnings: 0
+        )
+        activity = try? Activity.request(
+            attributes: SweepActivityAttributes(title: title, startedAt: Date()),
+            content: ActivityContent(state: state, staleDate: nil)
+        )
+    }
+
+    func update(completed: Int, total: Int, currentDomain: String?) {
+        guard let activity else { return }
+        let state = SweepActivityAttributes.ContentState(
+            completed: completed,
+            total: total,
+            currentDomain: currentDomain,
+            changed: 0,
+            warnings: 0
+        )
+        Task {
+            await activity.update(ActivityContent(state: state, staleDate: nil))
+        }
+    }
+
+    func end(changed: Int, warnings: Int, immediately: Bool = false) {
+        guard let activity else { return }
+        self.activity = nil
+        let state = SweepActivityAttributes.ContentState(
+            completed: activity.content.state.total,
+            total: activity.content.state.total,
+            currentDomain: nil,
+            changed: changed,
+            warnings: warnings
+        )
+        Task {
+            await activity.end(
+                ActivityContent(state: state, staleDate: nil),
+                dismissalPolicy: immediately ? .immediate : .after(Date().addingTimeInterval(60))
+            )
+        }
+    }
+}
diff --git a/DomainDigShareExtension/DomainDigShareExtension.entitlements b/DomainDigShareExtension/DomainDigShareExtension.entitlements
new file mode 100644
index 0000000..eaa20ba
--- /dev/null
+++ b/DomainDigShareExtension/DomainDigShareExtension.entitlements
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>com.apple.security.application-groups</key>
+    <array>
+        <string>group.net.cleberg.DomainDig</string>
+    </array>
+</dict>
+</plist>
diff --git a/DomainDigShareExtension/Info.plist b/DomainDigShareExtension/Info.plist
new file mode 100644
index 0000000..ac17ff0
--- /dev/null
+++ b/DomainDigShareExtension/Info.plist
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>CFBundleDevelopmentRegion</key>
+    <string>$(DEVELOPMENT_LANGUAGE)</string>
+    <key>CFBundleDisplayName</key>
+    <string>Dig Domain</string>
+    <key>CFBundleExecutable</key>
+    <string>$(EXECUTABLE_NAME)</string>
+    <key>CFBundleIdentifier</key>
+    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+    <key>CFBundleInfoDictionaryVersion</key>
+    <string>6.0</string>
+    <key>CFBundleName</key>
+    <string>$(PRODUCT_NAME)</string>
+    <key>CFBundlePackageType</key>
+    <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
+    <key>CFBundleShortVersionString</key>
+    <string>$(MARKETING_VERSION)</string>
+    <key>CFBundleVersion</key>
+    <string>$(CURRENT_PROJECT_VERSION)</string>
+    <key>NSExtension</key>
+    <dict>
+        <key>NSExtensionAttributes</key>
+        <dict>
+            <key>NSExtensionActivationRule</key>
+            <dict>
+                <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
+                <integer>1</integer>
+            </dict>
+        </dict>
+        <key>NSExtensionPointIdentifier</key>
+        <string>com.apple.share-services</string>
+        <key>NSExtensionPrincipalClass</key>
+        <string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
+    </dict>
+</dict>
+</plist>
diff --git a/DomainDigShareExtension/ShareViewController.swift b/DomainDigShareExtension/ShareViewController.swift
new file mode 100644
index 0000000..411f9e7
--- /dev/null
+++ b/DomainDigShareExtension/ShareViewController.swift
@@ -0,0 +1,65 @@
+import UIKit
+import UniformTypeIdentifiers
+
+/// Receives a web URL from the share sheet, extracts its host, and hands it to
+/// the app through the App Group inbox. Shows a brief confirmation and closes;
+/// the app picks up the domain the next time it becomes active.
+final class ShareViewController: UIViewController {
+    private let label = UILabel()
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        view.backgroundColor = .systemBackground
+
+        label.text = "Saving…"
+        label.font = .preferredFont(forTextStyle: .headline)
+        label.textAlignment = .center
+        label.numberOfLines = 0
+        label.translatesAutoresizingMaskIntoConstraints = false
+        view.addSubview(label)
+        NSLayoutConstraint.activate([
+            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+            label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
+            label.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 24),
+            label.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -24)
+        ])
+
+        extractSharedDomain { [weak self] domain in
+            DispatchQueue.main.async {
+                self?.finish(domain: domain)
+            }
+        }
+    }
+
+    private func extractSharedDomain(completion: @escaping (String?) -> Void) {
+        let providers = (extensionContext?.inputItems ?? [])
+            .compactMap { $0 as? NSExtensionItem }
+            .flatMap { $0.attachments ?? [] }
+
+        guard let provider = providers.first(where: {
+            $0.hasItemConformingToTypeIdentifier(UTType.url.identifier)
+        }) else {
+            completion(nil)
+            return
+        }
+
+        provider.loadItem(forTypeIdentifier: UTType.url.identifier) { item, _ in
+            let url = item as? URL ?? (item as? Data).flatMap { URL(dataRepresentation: $0, relativeTo: nil) }
+            let host = url?.host?.trimmingCharacters(in: .whitespacesAndNewlines)
+            completion(host?.isEmpty == false ? host : nil)
+        }
+    }
+
+    private func finish(domain: String?) {
+        if let domain {
+            DomainDigShareInbox.write(domain: domain)
+            label.text = "Saved \(domain) — open DomainDig to inspect it."
+        } else {
+            label.text = "No web address found."
+        }
+
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1.4) { [weak self] in
+            self?.extensionContext?.completeRequest(returningItems: nil)
+        }
+    }
+}
diff --git a/DomainDigWidget/DomainDigWidgetBundle.swift b/DomainDigWidget/DomainDigWidgetBundle.swift
index 483995c..8c12c90 100644
--- a/DomainDigWidget/DomainDigWidgetBundle.swift
+++ b/DomainDigWidget/DomainDigWidgetBundle.swift
@@ -5,5 +5,6 @@ import WidgetKit
 struct DomainDigWidgetBundle: WidgetBundle {
     var body: some Widget {
         DomainDigPortfolioWidget()
+        SweepLiveActivity()
     }
 }
diff --git a/DomainDigWidget/SweepLiveActivity.swift b/DomainDigWidget/SweepLiveActivity.swift
new file mode 100644
index 0000000..b99a070
--- /dev/null
+++ b/DomainDigWidget/SweepLiveActivity.swift
@@ -0,0 +1,96 @@
+import ActivityKit
+import SwiftUI
+import WidgetKit
+
+struct SweepLiveActivity: Widget {
+    var body: some WidgetConfiguration {
+        ActivityConfiguration(for: SweepActivityAttributes.self) { context in
+            // Lock Screen / banner presentation.
+            SweepActivityBannerView(context: context)
+                .padding()
+                .activityBackgroundTint(Color(.systemBackground).opacity(0.85))
+        } dynamicIsland: { context in
+            DynamicIsland {
+                DynamicIslandExpandedRegion(.leading) {
+                    Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise")
+                        .font(.caption)
+                        .foregroundStyle(.secondary)
+                }
+                DynamicIslandExpandedRegion(.trailing) {
+                    Text("\(context.state.completed)/\(context.state.total)")
+                        .font(.caption)
+                        .monospacedDigit()
+                }
+                DynamicIslandExpandedRegion(.bottom) {
+                    VStack(alignment: .leading, spacing: 4) {
+                        ProgressView(value: context.state.fractionComplete)
+                            .tint(.cyan)
+                        if let current = context.state.currentDomain {
+                            Text(current)
+                                .font(.caption2)
+                                .foregroundStyle(.secondary)
+                                .lineLimit(1)
+                        }
+                    }
+                }
+            } compactLeading: {
+                Image(systemName: "arrow.trianglehead.2.clockwise")
+                    .foregroundStyle(.cyan)
+            } compactTrailing: {
+                Text("\(context.state.completed)/\(context.state.total)")
+                    .font(.caption2)
+                    .monospacedDigit()
+            } minimal: {
+                ProgressView(value: context.state.fractionComplete)
+                    .progressViewStyle(.circular)
+                    .tint(.cyan)
+            }
+        }
+    }
+}
+
+private struct SweepActivityBannerView: View {
+    let context: ActivityViewContext<SweepActivityAttributes>
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 8) {
+            HStack {
+                Label(context.attributes.title, systemImage: "arrow.trianglehead.2.clockwise")
+                    .font(.caption)
+                    .fontWeight(.semibold)
+                    .foregroundStyle(.secondary)
+                Spacer()
+                Text("\(context.state.completed) of \(context.state.total)")
+                    .font(.caption)
+                    .monospacedDigit()
+            }
+
+            ProgressView(value: context.state.fractionComplete)
+                .tint(.cyan)
+
+            HStack {
+                if let current = context.state.currentDomain {
+                    Text(current)
+                        .font(.caption2)
+                        .foregroundStyle(.secondary)
+                        .lineLimit(1)
+                } else {
+                    Text(context.state.completed >= context.state.total ? "Finished" : "Working…")
+                        .font(.caption2)
+                        .foregroundStyle(.secondary)
+                }
+                Spacer()
+                if context.state.changed > 0 {
+                    Text("\(context.state.changed) changed")
+                        .font(.caption2)
+                        .foregroundStyle(.orange)
+                }
+                if context.state.warnings > 0 {
+                    Text("\(context.state.warnings) warnings")
+                        .font(.caption2)
+                        .foregroundStyle(.red)
+                }
+            }
+        }
+    }
+}
diff --git a/Shared/DomainDigShareInbox.swift b/Shared/DomainDigShareInbox.swift
new file mode 100644
index 0000000..3a577e7
--- /dev/null
+++ b/Shared/DomainDigShareInbox.swift
@@ -0,0 +1,28 @@
+import Foundation
+
+/// App Group handoff from the share extension to the app.
+///
+/// Share extensions cannot reliably open their host app, so the extension
+/// drops the shared domain here and the app consumes it the next time it
+/// becomes active, routing it into an inspection.
+enum DomainDigShareInbox {
+    private static let key = "pendingInspectDomain"
+
+    private static var defaults: UserDefaults? {
+        UserDefaults(suiteName: DomainDigWidgetStore.appGroupID)
+    }
+
+    static func write(domain: String) {
+        defaults?.set(domain, forKey: key)
+    }
+
+    /// Returns and clears the pending domain, if any.
+    static func consume() -> String? {
+        guard let defaults,
+              let domain = defaults.string(forKey: key),
+              !domain.isEmpty
+        else { return nil }
+        defaults.removeObject(forKey: key)
+        return domain
+    }
+}
diff --git a/Shared/SweepActivityAttributes.swift b/Shared/SweepActivityAttributes.swift
new file mode 100644
index 0000000..ee03c2d
--- /dev/null
+++ b/Shared/SweepActivityAttributes.swift
@@ -0,0 +1,25 @@
+import ActivityKit
+import Foundation
+
+/// Live Activity contract for an in-flight watchlist sweep or batch lookup.
+///
+/// Lives in `Shared/` because the app starts/updates the activity and the
+/// widget extension renders it.
+struct SweepActivityAttributes: ActivityAttributes {
+    struct ContentState: Codable, Hashable {
+        var completed: Int
+        var total: Int
+        var currentDomain: String?
+        var changed: Int
+        var warnings: Int
+
+        var fractionComplete: Double {
+            guard total > 0 else { return 0 }
+            return Double(completed) / Double(total)
+        }
+    }
+
+    /// User-facing title for the run, e.g. "Watchlist Sweep" or "Batch Lookup".
+    var title: String
+    var startedAt: Date
+}