Remove widget extension, keep the keep-alive banner in-app only

Drops the StikDebugWidgets extension target, the shared ActivityAttributes,
and the KeepAliveLiveActivity controller — no Dynamic Island / Lock Screen
presentation. The banner stays as the existing in-app overlay in HomeView,
now also shown when holding an app from the "Other" tab. project.pbxproj,
Info.plist, and AppBootstrapper.swift are back to their pre-widget state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EpCxAimrBo9RA8v6BxSEXT
This commit is contained in:
Claude 2026-07-15 10:54:26 +00:00
parent 23e8052c7b
commit e4f02100ee
No known key found for this signature in database
9 changed files with 1 additions and 428 deletions

View file

@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
68D569BE2E1B415700A5BA36 /* CodeEditorView in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BD2E1B415700A5BA36 /* CodeEditorView */; };
68D569C02E1B415700A5BA36 /* LanguageSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 68D569BF2E1B415700A5BA36 /* LanguageSupport */; };
F00DCAFE000000000000E00B /* StikDebugWidgets.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F00DCAFE000000000000E002 /* StikDebugWidgets.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -27,13 +26,6 @@
remoteGlobalIDString = DC6F1D362D94EADD0071B2B6;
remoteInfo = StikDebug;
};
F00DCAFE000000000000E00D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DC6F1D2F2D94EADD0071B2B6 /* Project object */;
proxyType = 1;
remoteGlobalIDString = F00DCAFE000000000000E001;
remoteInfo = StikDebugWidgets;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -43,7 +35,6 @@
dstPath = "";
dstSubfolderSpec = 13;
files = (
F00DCAFE000000000000E00B /* StikDebugWidgets.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
@ -56,7 +47,6 @@
DC6F1D372D94EADD0071B2B6 /* StikDebug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StikDebug.app; sourceTree = BUILT_PRODUCTS_DIR; };
DC6F1D482D94EADF0071B2B6 /* StikDebugTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StikDebugTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StikDebugUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F00DCAFE000000000000E002 /* StikDebugWidgets.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = StikDebugWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@ -67,13 +57,6 @@
);
target = DC6F1D362D94EADD0071B2B6 /* StikDebug */;
};
F00DCAFE000000000000E00E /* Exceptions for "StikDebugWidgets" folder in "StikDebugWidgets" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = F00DCAFE000000000000E001 /* StikDebugWidgets */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
@ -85,19 +68,6 @@
path = StikDebug;
sourceTree = "<group>";
};
F00DCAFE000000000000E006 /* StikDebugWidgets */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
F00DCAFE000000000000E00E /* Exceptions for "StikDebugWidgets" folder in "StikDebugWidgets" target */,
);
path = StikDebugWidgets;
sourceTree = "<group>";
};
F00DCAFE000000000000E007 /* StikDebugShared */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = StikDebugShared;
sourceTree = "<group>";
};
DC6F1D4B2D94EADF0071B2B6 /* StikDebugTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = StikDebugTests;
@ -134,13 +104,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F00DCAFE000000000000E004 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -148,8 +111,6 @@
isa = PBXGroup;
children = (
DC6F1D392D94EADD0071B2B6 /* StikDebug */,
F00DCAFE000000000000E007 /* StikDebugShared */,
F00DCAFE000000000000E006 /* StikDebugWidgets */,
DC6F1D4B2D94EADF0071B2B6 /* StikDebugTests */,
DC6F1D552D94EADF0071B2B6 /* StikDebugUITests */,
DC6F1D752D94EB620071B2B6 /* Frameworks */,
@ -163,7 +124,6 @@
DC6F1D372D94EADD0071B2B6 /* StikDebug.app */,
DC6F1D482D94EADF0071B2B6 /* StikDebugTests.xctest */,
DC6F1D522D94EADF0071B2B6 /* StikDebugUITests.xctest */,
F00DCAFE000000000000E002 /* StikDebugWidgets.appex */,
);
name = Products;
sourceTree = "<group>";
@ -192,11 +152,9 @@
buildRules = (
);
dependencies = (
F00DCAFE000000000000E00C /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
DC6F1D392D94EADD0071B2B6 /* StikDebug */,
F00DCAFE000000000000E007 /* StikDebugShared */,
);
name = StikDebug;
packageProductDependencies = (
@ -207,29 +165,6 @@
productReference = DC6F1D372D94EADD0071B2B6 /* StikDebug.app */;
productType = "com.apple.product-type.application";
};
F00DCAFE000000000000E001 /* StikDebugWidgets */ = {
isa = PBXNativeTarget;
buildConfigurationList = F00DCAFE000000000000E008 /* Build configuration list for PBXNativeTarget "StikDebugWidgets" */;
buildPhases = (
F00DCAFE000000000000E003 /* Sources */,
F00DCAFE000000000000E004 /* Frameworks */,
F00DCAFE000000000000E005 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
F00DCAFE000000000000E006 /* StikDebugWidgets */,
F00DCAFE000000000000E007 /* StikDebugShared */,
);
name = StikDebugWidgets;
packageProductDependencies = (
);
productName = StikDebugWidgets;
productReference = F00DCAFE000000000000E002 /* StikDebugWidgets.appex */;
productType = "com.apple.product-type.app-extension";
};
DC6F1D472D94EADF0071B2B6 /* StikDebugTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = DC6F1D5F2D94EADF0071B2B6 /* Build configuration list for PBXNativeTarget "StikDebugTests" */;
@ -298,9 +233,6 @@
CreatedOnToolsVersion = 16.2;
TestTargetID = DC6F1D362D94EADD0071B2B6;
};
F00DCAFE000000000000E001 = {
CreatedOnToolsVersion = 16.2;
};
};
};
buildConfigurationList = DC6F1D322D94EADD0071B2B6 /* Build configuration list for PBXProject "StikDebug" */;
@ -323,7 +255,6 @@
projectRoot = "";
targets = (
DC6F1D362D94EADD0071B2B6 /* StikDebug */,
F00DCAFE000000000000E001 /* StikDebugWidgets */,
DC6F1D472D94EADF0071B2B6 /* StikDebugTests */,
DC6F1D512D94EADF0071B2B6 /* StikDebugUITests */,
);
@ -352,13 +283,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F00DCAFE000000000000E005 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -383,13 +307,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F00DCAFE000000000000E003 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -403,11 +320,6 @@
target = DC6F1D362D94EADD0071B2B6 /* StikDebug */;
targetProxy = DC6F1D532D94EADF0071B2B6 /* PBXContainerItemProxy */;
};
F00DCAFE000000000000E00C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = F00DCAFE000000000000E001 /* StikDebugWidgets */;
targetProxy = F00DCAFE000000000000E00D /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@ -756,64 +668,6 @@
};
name = Release;
};
F00DCAFE000000000000E009 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = StikDebugWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = StikDebug;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1.6;
PRODUCT_BUNDLE_IDENTIFIER = com.stik.stikdebug.widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
F00DCAFE000000000000E00A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = StikDebugWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = StikDebug;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.1.6;
PRODUCT_BUNDLE_IDENTIFIER = com.stik.stikdebug.widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -853,15 +707,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F00DCAFE000000000000E008 /* Build configuration list for PBXNativeTarget "StikDebugWidgets" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F00DCAFE000000000000E009 /* Debug */,
F00DCAFE000000000000E00A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */

View file

@ -13,9 +13,6 @@ enum AppBootstrapper {
startConfiguredKeepAliveServices()
applyDocumentPickerCopyWorkaround()
NetworkPathMonitor.shared.start()
// No hold is active at launch, so clear any Live Activity a previous
// (possibly force-quit) run left showing in the Dynamic Island.
KeepAliveLiveActivity.endStaleActivities()
}
private static func registerDefaultSettings() {

View file

@ -21,10 +21,6 @@
<string>_stikdebug._tcp</string>
<string>_stikdebug._udp</string>
</array>
<key>NSSupportsLiveActivities</key>
<true/>
<key>NSSupportsLiveActivitiesFrequentUpdates</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>

View file

@ -72,12 +72,7 @@ final class BackgroundAliveManager: ObservableObject {
lock.unlock()
let name = displayName ?? bundleID
DispatchQueue.main.async {
self.activeAppName = name
// Surface the hold in the Dynamic Island / Lock Screen so it stays
// visible while the user is in the held app.
KeepAliveLiveActivity.start(appName: name, bundleID: bundleID)
}
DispatchQueue.main.async { self.activeAppName = name }
LogManager.shared.addInfoLog("Starting background keep-alive for \(name)")
DispatchQueue.global(qos: .userInitiated).async {
@ -106,7 +101,6 @@ final class BackgroundAliveManager: ObservableObject {
DispatchQueue.main.async {
if stillCurrent {
self.activeAppName = nil
KeepAliveLiveActivity.end()
}
if !succeeded {
showAlert(

View file

@ -1,114 +0,0 @@
//
// KeepAliveLiveActivity.swift
// StikDebug
//
// App-side controller for the background keep-alive Live Activity. Starts a
// Live Activity when a hold begins so it shows in the Dynamic Island (on
// compatible iPhones) and on the Lock Screen, and ends it when the hold stops.
//
import Foundation
#if canImport(ActivityKit)
import ActivityKit
#endif
enum KeepAliveLiveActivity {
private static let lock = NSLock()
/// Type-erased storage for the current `Activity`, so this enum has no
/// availability requirement of its own and callers don't need to guard.
private static var storage: Any?
/// Start (or replace) the keep-alive Live Activity for the given app.
static func start(appName: String, bundleID: String) {
#if canImport(ActivityKit)
guard #available(iOS 16.1, *) else { return }
startActivity(appName: appName, bundleID: bundleID)
#endif
}
/// Update the app name shown by the current Live Activity, if any.
static func update(appName: String) {
#if canImport(ActivityKit)
guard #available(iOS 16.1, *) else { return }
updateActivity(appName: appName)
#endif
}
/// End the current keep-alive Live Activity, if any.
static func end() {
#if canImport(ActivityKit)
guard #available(iOS 16.1, *) else { return }
endActivity()
#endif
}
/// End any keep-alive activities left running by a previous launch (for
/// example if the app was force-quit or crashed mid-hold). Call this once at
/// startup, when no hold is active.
static func endStaleActivities() {
#if canImport(ActivityKit)
guard #available(iOS 16.1, *) else { return }
for activity in Activity<KeepAliveActivityAttributes>.activities {
Task { await activity.end(nil, dismissalPolicy: .immediate) }
}
lock.lock()
storage = nil
lock.unlock()
#endif
}
#if canImport(ActivityKit)
@available(iOS 16.1, *)
private static func startActivity(appName: String, bundleID: String) {
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
LogManager.shared.addInfoLog("Keep-alive Live Activity not started: Live Activities are disabled in Settings")
return
}
// Only one hold runs at a time, so make sure any stale activity is gone.
endActivity()
let attributes = KeepAliveActivityAttributes(appName: appName, bundleID: bundleID)
let state = KeepAliveActivityAttributes.ContentState(appName: appName)
do {
let activity = try Activity.request(
attributes: attributes,
content: ActivityContent(state: state, staleDate: nil)
)
lock.lock()
storage = activity
lock.unlock()
} catch {
LogManager.shared.addErrorLog("Keep-alive Live Activity failed to start: \(error.localizedDescription)")
}
}
@available(iOS 16.1, *)
private static func updateActivity(appName: String) {
lock.lock()
let activity = storage as? Activity<KeepAliveActivityAttributes>
lock.unlock()
guard let activity else { return }
let state = KeepAliveActivityAttributes.ContentState(appName: appName)
Task {
await activity.update(ActivityContent(state: state, staleDate: nil))
}
}
@available(iOS 16.1, *)
private static func endActivity() {
lock.lock()
let activity = storage as? Activity<KeepAliveActivityAttributes>
storage = nil
lock.unlock()
guard let activity else { return }
Task {
await activity.end(nil, dismissalPolicy: .immediate)
}
}
#endif
}

View file

@ -1,29 +0,0 @@
//
// KeepAliveActivityAttributes.swift
// StikDebug
//
// Shared between the app (which starts/updates/ends the activity) and the
// widget extension (which renders it in the Dynamic Island / on the Lock
// Screen). Keep this file in the StikDebugShared group so both targets compile
// the same type.
//
import Foundation
#if canImport(ActivityKit)
import ActivityKit
@available(iOS 16.1, *)
struct KeepAliveActivityAttributes: ActivityAttributes {
/// The parts of the activity that change while it is live.
struct ContentState: Codable, Hashable {
/// The display name of the app currently being held alive.
var appName: String
}
/// The app being held alive when the activity started.
var appName: String
/// The bundle identifier of that app.
var bundleID: String
}
#endif

View file

@ -1,11 +0,0 @@
<?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>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>

View file

@ -1,88 +0,0 @@
//
// KeepAliveLiveActivityWidget.swift
// StikDebugWidgets
//
// Live Activity presentation for the background keep-alive session. Shows the
// held app in the Dynamic Island (compact, expanded, and minimal) and on the
// Lock Screen.
//
import WidgetKit
import SwiftUI
import ActivityKit
@available(iOS 16.1, *)
struct KeepAliveLiveActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: KeepAliveActivityAttributes.self) { context in
// Lock Screen / banner presentation.
KeepAliveLockScreenView(appName: context.state.appName)
.activityBackgroundTint(Color.black.opacity(0.55))
.activitySystemActionForegroundColor(.green)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Image(systemName: "bolt.circle.fill")
.font(.title2)
.foregroundStyle(.green)
.padding(.leading, 4)
}
DynamicIslandExpandedRegion(.trailing) {
Text("JIT")
.font(.caption.weight(.bold))
.foregroundStyle(.green)
.padding(.trailing, 4)
}
DynamicIslandExpandedRegion(.center) {
Text(context.state.appName)
.font(.headline)
.lineLimit(1)
.minimumScaleFactor(0.7)
}
DynamicIslandExpandedRegion(.bottom) {
Text("Held alive in the background")
.font(.caption)
.foregroundStyle(.secondary)
}
} compactLeading: {
Image(systemName: "bolt.circle.fill")
.foregroundStyle(.green)
} compactTrailing: {
Text(context.state.appName)
.font(.caption2)
.lineLimit(1)
.frame(maxWidth: 64)
} minimal: {
Image(systemName: "bolt.circle.fill")
.foregroundStyle(.green)
}
.keylineTint(.green)
}
}
}
@available(iOS 16.1, *)
private struct KeepAliveLockScreenView: View {
let appName: String
var body: some View {
HStack(spacing: 12) {
Image(systemName: "bolt.circle.fill")
.font(.title)
.foregroundStyle(.green)
VStack(alignment: .leading, spacing: 2) {
Text("Keeping \(appName) alive")
.font(.headline)
.lineLimit(1)
.minimumScaleFactor(0.7)
Text("Held in the background")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 0)
}
.padding()
}
}

View file

@ -1,17 +0,0 @@
//
// StikDebugWidgetsBundle.swift
// StikDebugWidgets
//
// Widget extension bundle. Hosts the keep-alive Live Activity so it can be
// rendered in the Dynamic Island and on the Lock Screen.
//
import WidgetKit
import SwiftUI
@main
struct StikDebugWidgetsBundle: WidgetBundle {
var body: some Widget {
KeepAliveLiveActivityWidget()
}
}