ios: notifications, actionable approvals, Live Activity, and the relay push path

Brings the phone's ambient surfaces (UX_IOS §5/§8) to maturity:

- Notification pipeline (NotificationRouter): local notifications for
  approvals and needs-input transitions while backgrounded; low-risk
  approvals are actionable from the banner (Allow requires device
  auth, high-risk must open the app's Face ID gate); taps deep-link
  to the session; app-icon badge = NEEDS YOU count; resolutions
  withdraw the notification (first-responder-wins). The relay's
  content-free approval.pending tickle localizes via
  Localizable.strings.
- Live Activity: new NucleicRemoteWidgets extension target (lock
  screen + Dynamic Island) rendering one aggregate Activity —
  N running / M waiting + the most urgent session — started/updated/
  ended by LiveActivityManager as session state changes.
- Out-of-band push path: nucleic-edge gains POST /v1/push/register
  (admin) so the host can upload tokens for LAN-only pairings; the
  host's new PushRelayClient (config-gated on NUCLEIC_RELAY_URL +
  NUCLEIC_RELAY_ADMIN_SECRET) mirrors Hello.pushToken to the relay
  and wakes non-connected phones when an approval arrives, throttled
  per device. Everything stays off until the relay is provisioned.

Worker tests (23) and Swift suites pass apart from the pre-existing
fixture gaps and nvrsion flake. Simulated APNs delivery is blocked in
this environment (notification auth can't be granted headlessly).

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-07-02 16:06:33 -07:00
co-authored by Claude Fable 5
parent 480b29207f
commit 761dca5f1d
12 changed files with 643 additions and 3 deletions
@@ -8,12 +8,38 @@
/* Begin PBXBuildFile section */
BF00000000000000000001 /* NucleicProtocol in Frameworks */ = {isa = PBXBuildFile; productRef = PD00000000000000000001 /* NucleicProtocol */; };
BF00000000000000000002 /* NucleicRemoteWidgets.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FR00000000000000000002 /* NucleicRemoteWidgets.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
E7D43FF12FF0806800BF2407 /* app-logo-file.icon in Resources */ = {isa = PBXBuildFile; fileRef = E7D43FF02FF0806800BF2407 /* app-logo-file.icon */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
CP00000000000000000001 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = PJ00000000000000000001 /* Project object */;
proxyType = 1;
remoteGlobalIDString = TG00000000000000000002;
remoteInfo = NucleicRemoteWidgets;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
BP00000000000000000005 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
BF00000000000000000002 /* NucleicRemoteWidgets.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
E7D43FF02FF0806800BF2407 /* app-logo-file.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = "app-logo-file.icon"; sourceTree = "<group>"; };
FR00000000000000000001 /* NucleicRemote.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NucleicRemote.app; sourceTree = BUILT_PRODUCTS_DIR; };
FR00000000000000000002 /* NucleicRemoteWidgets.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NucleicRemoteWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -24,6 +50,13 @@
);
target = TG00000000000000000001 /* NucleicRemote */;
};
EX00000000000000000002 /* Exceptions for "NucleicRemoteWidgets" folder in "NucleicRemoteWidgets" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = TG00000000000000000002 /* NucleicRemoteWidgets */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
@@ -35,6 +68,19 @@
path = NucleicRemote;
sourceTree = "<group>";
};
SG00000000000000000002 /* NucleicRemoteWidgets */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
EX00000000000000000002 /* Exceptions for "NucleicRemoteWidgets" folder in "NucleicRemoteWidgets" target */,
);
path = NucleicRemoteWidgets;
sourceTree = "<group>";
};
SG00000000000000000003 /* Shared */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = Shared;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -46,6 +92,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
BP00000000000000000007 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -53,6 +106,8 @@
isa = PBXGroup;
children = (
SG00000000000000000001 /* NucleicRemote */,
SG00000000000000000002 /* NucleicRemoteWidgets */,
SG00000000000000000003 /* Shared */,
GR00000000000000000002 /* Products */,
E7D43FF02FF0806800BF2407 /* app-logo-file.icon */,
);
@@ -62,6 +117,7 @@
isa = PBXGroup;
children = (
FR00000000000000000001 /* NucleicRemote.app */,
FR00000000000000000002 /* NucleicRemoteWidgets.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -76,13 +132,16 @@
BP00000000000000000002 /* Sources */,
BP00000000000000000003 /* Frameworks */,
BP00000000000000000004 /* Resources */,
BP00000000000000000005 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
TD00000000000000000001 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
SG00000000000000000001 /* NucleicRemote */,
SG00000000000000000003 /* Shared */,
);
name = NucleicRemote;
packageProductDependencies = (
@@ -92,8 +151,37 @@
productReference = FR00000000000000000001 /* NucleicRemote.app */;
productType = "com.apple.product-type.application";
};
TG00000000000000000002 /* NucleicRemoteWidgets */ = {
isa = PBXNativeTarget;
buildConfigurationList = CL00000000000000000003 /* Build configuration list for PBXNativeTarget "NucleicRemoteWidgets" */;
buildPhases = (
BP00000000000000000006 /* Sources */,
BP00000000000000000007 /* Frameworks */,
BP00000000000000000008 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
SG00000000000000000002 /* NucleicRemoteWidgets */,
SG00000000000000000003 /* Shared */,
);
name = NucleicRemoteWidgets;
productName = NucleicRemoteWidgets;
productReference = FR00000000000000000002 /* NucleicRemoteWidgets.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXTargetDependency section */
TD00000000000000000001 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = TG00000000000000000002 /* NucleicRemoteWidgets */;
targetProxy = CP00000000000000000001 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXProject section */
PJ00000000000000000001 /* Project object */ = {
isa = PBXProject;
@@ -105,6 +193,9 @@
TG00000000000000000001 = {
CreatedOnToolsVersion = 27.0;
};
TG00000000000000000002 = {
CreatedOnToolsVersion = 27.0;
};
};
};
buildConfigurationList = CL00000000000000000001 /* Build configuration list for PBXProject "NucleicRemote" */;
@@ -125,6 +216,7 @@
projectRoot = "";
targets = (
TG00000000000000000001 /* NucleicRemote */,
TG00000000000000000002 /* NucleicRemoteWidgets */,
);
};
/* End PBXProject section */
@@ -138,6 +230,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
BP00000000000000000008 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -148,6 +247,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
BP00000000000000000006 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
@@ -256,6 +362,66 @@
};
name = Release;
};
BC00000000000000000005 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = L7UDTQ6F5W;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NucleicRemoteWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NucleicRemoteWidgets;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = "xyz.blakeslee.nucleic-remote.widgets";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
BC00000000000000000006 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = L7UDTQ6F5W;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NucleicRemoteWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NucleicRemoteWidgets;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 0.1.0;
PRODUCT_BUNDLE_IDENTIFIER = "xyz.blakeslee.nucleic-remote.widgets";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -277,6 +443,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CL00000000000000000003 /* Build configuration list for PBXNativeTarget "NucleicRemoteWidgets" */ = {
isa = XCConfigurationList;
buildConfigurations = (
BC00000000000000000005 /* Debug */,
BC00000000000000000006 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */