feat(ios): distinguish debug app builds (#100222)

* feat(ios): distinguish debug app builds

* feat(ios): distinguish debug app builds

* feat(ios): emphasize debug app icons

* feat(ios): emphasize debug app icons
This commit is contained in:
Peter Steinberger
2026-07-05 02:01:26 -07:00
committed by GitHub
parent 3957ccba4e
commit e60c510560
56 changed files with 199 additions and 23 deletions
+1
View File
@@ -2,6 +2,7 @@
## Unreleased
- Debug builds now use separate app, extension, widget, and Watch identifiers plus a distinct debug icon, so they can remain installed beside release builds.
- Animated the OpenClaw mascot (float, blink, antenna wiggle, claw snaps) across onboarding, sidebar, and command center, matching openclaw.ai.
## 2026.6.11 - 2026-07-01
+6
View File
@@ -19,6 +19,12 @@ OPENCLAW_WATCH_APP_PROFILE =
#include? "../.local-signing.xcconfig"
#include? "../LocalSigning.xcconfig"
// Debug products install alongside release products and keep their app-group data separate.
OPENCLAW_DEBUG_APP_BUNDLE_ID = $(OPENCLAW_APP_BUNDLE_ID).debug
OPENCLAW_DEBUG_ACTIVITY_WIDGET_BUNDLE_ID = $(OPENCLAW_DEBUG_APP_BUNDLE_ID).activitywidget
OPENCLAW_DEBUG_WATCH_APP_BUNDLE_ID = $(OPENCLAW_DEBUG_APP_BUNDLE_ID).watchkitapp
OPENCLAW_DEBUG_APP_GROUP_ID = $(OPENCLAW_APP_GROUP_ID).debug
CODE_SIGN_STYLE = $(OPENCLAW_CODE_SIGN_STYLE)
CODE_SIGN_IDENTITY = $(OPENCLAW_CODE_SIGN_IDENTITY)
DEVELOPMENT_TEAM = $(OPENCLAW_DEVELOPMENT_TEAM)
+1 -1
View File
@@ -42,6 +42,6 @@
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
</dict>
<key>OpenClawAppGroupIdentifier</key>
<string>$(OPENCLAW_APP_GROUP_ID)</string>
<string>$(OPENCLAW_ACTIVE_APP_GROUP_ID)</string>
</dict>
</plist>
@@ -4,7 +4,7 @@
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>$(OPENCLAW_APP_GROUP_ID)</string>
<string>$(OPENCLAW_ACTIVE_APP_GROUP_ID)</string>
</array>
</dict>
</plist>
+7
View File
@@ -25,3 +25,10 @@ OPENCLAW_WATCH_APP_PROFILE =
// so later assignments in local files override the defaults above.
#include? ".local-signing.xcconfig"
#include? "LocalSigning.xcconfig"
// Debug products install alongside release products and keep their app-group data separate.
OPENCLAW_DEBUG_APP_BUNDLE_ID = $(OPENCLAW_APP_BUNDLE_ID).debug
OPENCLAW_DEBUG_SHARE_BUNDLE_ID = $(OPENCLAW_DEBUG_APP_BUNDLE_ID).share
OPENCLAW_DEBUG_ACTIVITY_WIDGET_BUNDLE_ID = $(OPENCLAW_DEBUG_APP_BUNDLE_ID).activitywidget
OPENCLAW_DEBUG_WATCH_APP_BUNDLE_ID = $(OPENCLAW_DEBUG_APP_BUNDLE_ID).watchkitapp
OPENCLAW_DEBUG_APP_GROUP_ID = $(OPENCLAW_APP_GROUP_ID).debug
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -4,16 +4,16 @@
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>$(OPENCLAW_APP_BUNDLE_ID).bgrefresh</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).bgrefresh</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>OpenClaw</string>
<string>$(OPENCLAW_APP_DISPLAY_NAME)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconName</key>
<string>AppIcon</string>
<string>$(ASSETCATALOG_COMPILER_APPICON_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
@@ -31,7 +31,7 @@
<string>ai.openclawfoundation.app</string>
<key>CFBundleURLSchemes</key>
<array>
<string>openclaw</string>
<string>$(OPENCLAW_URL_SCHEME)</string>
</array>
</dict>
</array>
@@ -79,7 +79,7 @@
<key>NSSupportsLiveActivities</key>
<true/>
<key>OpenClawAppGroupIdentifier</key>
<string>$(OPENCLAW_APP_GROUP_ID)</string>
<string>$(OPENCLAW_ACTIVE_APP_GROUP_ID)</string>
<key>OpenClawCanonicalVersion</key>
<string>$(OPENCLAW_IOS_VERSION)</string>
<key>OpenClawPushMode</key>
+1 -1
View File
@@ -6,7 +6,7 @@
<string>$(OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT)</string>
<key>com.apple.security.application-groups</key>
<array>
<string>$(OPENCLAW_APP_GROUP_ID)</string>
<string>$(OPENCLAW_ACTIVE_APP_GROUP_ID)</string>
</array>
</dict>
</plist>
+3 -3
View File
@@ -125,7 +125,7 @@ final class ScreenWebViewCoordinator: NSObject {
// MARK: - Navigation Delegate
/// Handles navigation policy to intercept openclaw:// deep links from canvas
/// Handles navigation policy to intercept OpenClaw deep links from canvas.
@MainActor
private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
weak var controller: ScreenController?
@@ -140,8 +140,8 @@ private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
return
}
// Intercept openclaw:// deep links.
if url.scheme?.lowercased() == "openclaw" {
let scheme = url.scheme?.lowercased()
if scheme == "openclaw" || scheme == "openclaw-debug" {
decisionHandler(.cancel)
self.controller?.onDeepLink?(url)
return
@@ -0,0 +1,116 @@
{
"images": [
{
"idiom": "watch",
"role": "notificationCenter",
"subtype": "38mm",
"size": "24x24",
"scale": "2x",
"filename": "watch-notification-38@2x.png"
},
{
"idiom": "watch",
"role": "notificationCenter",
"subtype": "42mm",
"size": "27.5x27.5",
"scale": "2x",
"filename": "watch-notification-42@2x.png"
},
{
"idiom": "watch",
"role": "companionSettings",
"size": "29x29",
"scale": "2x",
"filename": "watch-companion-29@2x.png"
},
{
"idiom": "watch",
"role": "companionSettings",
"size": "29x29",
"scale": "3x",
"filename": "watch-companion-29@3x.png"
},
{
"idiom": "watch",
"role": "appLauncher",
"subtype": "38mm",
"size": "40x40",
"scale": "2x",
"filename": "watch-app-38@2x.png"
},
{
"idiom": "watch",
"role": "appLauncher",
"subtype": "40mm",
"size": "44x44",
"scale": "2x",
"filename": "watch-app-40@2x.png"
},
{
"idiom": "watch",
"role": "appLauncher",
"subtype": "41mm",
"size": "46x46",
"scale": "2x",
"filename": "watch-app-41@2x.png"
},
{
"idiom": "watch",
"role": "appLauncher",
"subtype": "44mm",
"size": "50x50",
"scale": "2x",
"filename": "watch-app-44@2x.png"
},
{
"idiom": "watch",
"role": "appLauncher",
"subtype": "45mm",
"size": "51x51",
"scale": "2x",
"filename": "watch-app-45@2x.png"
},
{
"idiom": "watch",
"role": "quickLook",
"subtype": "38mm",
"size": "86x86",
"scale": "2x",
"filename": "watch-quicklook-38@2x.png"
},
{
"idiom": "watch",
"role": "quickLook",
"subtype": "42mm",
"size": "98x98",
"scale": "2x",
"filename": "watch-quicklook-42@2x.png"
},
{
"idiom": "watch",
"role": "quickLook",
"subtype": "44mm",
"size": "108x108",
"scale": "2x",
"filename": "watch-quicklook-44@2x.png"
},
{
"idiom": "watch",
"role": "quickLook",
"subtype": "45mm",
"size": "117x117",
"scale": "2x",
"filename": "watch-quicklook-45@2x.png"
},
{
"idiom": "watch-marketing",
"size": "1024x1024",
"scale": "1x",
"filename": "watch-marketing-1024.png"
}
],
"info": {
"version": 1,
"author": "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

+4 -4
View File
@@ -20,10 +20,6 @@
<string>$(OPENCLAW_MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(OPENCLAW_BUILD_VERSION)</string>
<key>WKApplication</key>
<true/>
<key>WKCompanionAppBundleIdentifier</key>
<string>$(OPENCLAW_APP_BUNDLE_ID)</string>
<key>UIAppFonts</key>
<array>
<string>RedHatDisplay[wght].ttf</string>
@@ -33,5 +29,9 @@
<string>JetBrainsMono-Medium.ttf</string>
<string>JetBrainsMono-SemiBold.ttf</string>
</array>
<key>WKApplication</key>
<true/>
<key>WKCompanionAppBundleIdentifier</key>
<string>$(OPENCLAW_COMPANION_APP_BUNDLE_ID)</string>
</dict>
</plist>
+47 -7
View File
@@ -113,10 +113,14 @@ targets:
swiftlint lint --config "$SRCROOT/.swiftlint.yml" --use-script-input-file-lists
settings:
base:
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
CODE_SIGN_IDENTITY: "$(OPENCLAW_CODE_SIGN_IDENTITY)"
CODE_SIGN_ENTITLEMENTS: "$(OPENCLAW_CODE_SIGN_ENTITLEMENTS)"
CODE_SIGN_STYLE: "$(OPENCLAW_CODE_SIGN_STYLE)"
DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)"
OPENCLAW_ACTIVE_APP_GROUP_ID: "$(OPENCLAW_APP_GROUP_ID)"
OPENCLAW_APP_DISPLAY_NAME: OpenClaw
OPENCLAW_URL_SCHEME: openclaw
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_APP_BUNDLE_ID)"
PROVISIONING_PROFILE_SPECIFIER: "$(OPENCLAW_APP_PROFILE)"
TARGETED_DEVICE_FAMILY: "1,2"
@@ -127,8 +131,15 @@ targets:
ENABLE_APP_INTENTS_METADATA_GENERATION: NO
configs:
Debug:
ASSETCATALOG_COMPILER_APPICON_NAME: AppIconDebug
CODE_SIGN_STYLE: Automatic
OPENCLAW_ACTIVE_APP_GROUP_ID: "$(OPENCLAW_DEBUG_APP_GROUP_ID)"
OPENCLAW_APP_DISPLAY_NAME: OpenClaw Debug
OPENCLAW_CODE_SIGN_ENTITLEMENTS: Sources/OpenClaw.entitlements
OPENCLAW_APNS_ENTITLEMENT_ENVIRONMENT: development
OPENCLAW_URL_SCHEME: openclaw-debug
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_DEBUG_APP_BUNDLE_ID)"
PROVISIONING_PROFILE_SPECIFIER: ""
OPENCLAW_PUSH_MODE: localSandbox
OPENCLAW_PUSH_RELAY_BASE_URL: ""
Release:
@@ -139,15 +150,15 @@ targets:
info:
path: Sources/Info.plist
properties:
CFBundleDisplayName: OpenClaw
CFBundleIconName: AppIcon
CFBundleDisplayName: "$(OPENCLAW_APP_DISPLAY_NAME)"
CFBundleIconName: "$(ASSETCATALOG_COMPILER_APPICON_NAME)"
CFBundleURLTypes:
- CFBundleURLName: ai.openclawfoundation.app
CFBundleURLSchemes:
- openclaw
- "$(OPENCLAW_URL_SCHEME)"
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
OpenClawCanonicalVersion: "$(OPENCLAW_IOS_VERSION)"
OpenClawAppGroupIdentifier: "$(OPENCLAW_APP_GROUP_ID)"
OpenClawAppGroupIdentifier: "$(OPENCLAW_ACTIVE_APP_GROUP_ID)"
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
UILaunchScreen: {}
UIApplicationSceneManifest:
@@ -157,7 +168,7 @@ targets:
- location
- remote-notification
BGTaskSchedulerPermittedIdentifiers:
- "$(OPENCLAW_APP_BUNDLE_ID).bgrefresh"
- "$(PRODUCT_BUNDLE_IDENTIFIER).bgrefresh"
NSLocalNetworkUsageDescription: OpenClaw discovers and connects to your OpenClaw gateway on the local network.
NSAppTransportSecurity:
NSAllowsArbitraryLoadsInWebContent: true
@@ -214,17 +225,24 @@ targets:
DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)"
ENABLE_APPINTENTS_METADATA: NO
ENABLE_APP_INTENTS_METADATA_GENERATION: NO
OPENCLAW_ACTIVE_APP_GROUP_ID: "$(OPENCLAW_APP_GROUP_ID)"
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_SHARE_BUNDLE_ID)"
PROVISIONING_PROFILE_SPECIFIER: "$(OPENCLAW_SHARE_PROFILE)"
TARGETED_DEVICE_FAMILY: "1,2"
SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: complete
configs:
Debug:
CODE_SIGN_STYLE: Automatic
OPENCLAW_ACTIVE_APP_GROUP_ID: "$(OPENCLAW_DEBUG_APP_GROUP_ID)"
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_DEBUG_SHARE_BUNDLE_ID)"
PROVISIONING_PROFILE_SPECIFIER: ""
info:
path: ShareExtension/Info.plist
properties:
CFBundleDisplayName: OpenClaw Share
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
OpenClawAppGroupIdentifier: "$(OPENCLAW_APP_GROUP_ID)"
OpenClawAppGroupIdentifier: "$(OPENCLAW_ACTIVE_APP_GROUP_ID)"
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
NSExtension:
NSExtensionPointIdentifier: com.apple.share-services
@@ -263,6 +281,11 @@ targets:
SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: complete
SUPPORTS_LIVE_ACTIVITIES: YES
configs:
Debug:
CODE_SIGN_STYLE: Automatic
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_DEBUG_ACTIVITY_WIDGET_BUNDLE_ID)"
PROVISIONING_PROFILE_SPECIFIER: ""
info:
path: ActivityWidget/Info.plist
properties:
@@ -310,17 +333,25 @@ targets:
DEVELOPMENT_TEAM: "$(OPENCLAW_DEVELOPMENT_TEAM)"
ENABLE_APPINTENTS_METADATA: NO
ENABLE_APP_INTENTS_METADATA_GENERATION: NO
OPENCLAW_COMPANION_APP_BUNDLE_ID: "$(OPENCLAW_APP_BUNDLE_ID)"
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)"
PROVISIONING_PROFILE_SPECIFIER: "$(OPENCLAW_WATCH_APP_PROFILE)"
SWIFT_STRICT_CONCURRENCY: complete
SWIFT_VERSION: "6.0"
configs:
Debug:
ASSETCATALOG_COMPILER_APPICON_NAME: AppIconDebug
CODE_SIGN_STYLE: Automatic
OPENCLAW_COMPANION_APP_BUNDLE_ID: "$(OPENCLAW_DEBUG_APP_BUNDLE_ID)"
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_DEBUG_WATCH_APP_BUNDLE_ID)"
PROVISIONING_PROFILE_SPECIFIER: ""
info:
path: WatchApp/Info.plist
properties:
CFBundleDisplayName: OpenClaw
CFBundleShortVersionString: "$(OPENCLAW_MARKETING_VERSION)"
CFBundleVersion: "$(OPENCLAW_BUILD_VERSION)"
WKCompanionAppBundleIdentifier: "$(OPENCLAW_APP_BUNDLE_ID)"
WKCompanionAppBundleIdentifier: "$(OPENCLAW_COMPANION_APP_BUNDLE_ID)"
WKApplication: true
UIAppFonts:
- RedHatDisplay[wght].ttf
@@ -356,6 +387,9 @@ targets:
SWIFT_STRICT_CONCURRENCY: complete
TEST_HOST: "$(BUILT_PRODUCTS_DIR)/OpenClaw.app/OpenClaw"
BUNDLE_LOADER: "$(TEST_HOST)"
configs:
Debug:
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_DEBUG_APP_BUNDLE_ID).tests"
info:
path: Tests/Info.plist
properties:
@@ -383,6 +417,9 @@ targets:
SWIFT_EMIT_CONST_VALUE_PROTOCOLS: ""
SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: complete
configs:
Debug:
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_DEBUG_APP_BUNDLE_ID).logic-tests"
info:
path: Tests/Info.plist
properties:
@@ -412,6 +449,9 @@ targets:
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD: NO
TEST_TARGET_NAME: OpenClaw
SWIFT_VERSION: "5.0"
configs:
Debug:
PRODUCT_BUNDLE_IDENTIFIER: "$(OPENCLAW_DEBUG_APP_BUNDLE_ID).ui-tests"
info:
path: UITests/Info.plist
properties:
@@ -215,7 +215,7 @@ public struct AgentDeepLink: Codable, Sendable, Equatable {
public enum DeepLinkParser {
public static func parse(_ url: URL) -> DeepLinkRoute? {
guard let scheme = url.scheme?.lowercased(),
scheme == "openclaw"
scheme == "openclaw" || scheme == "openclaw-debug"
else {
return nil
}
@@ -16,6 +16,11 @@ private func setupCode(from payload: String) -> String {
#expect(DeepLinkParser.parse(url) == .dashboard)
}
@Test func debugDashboardDeepLinkParses() {
let url = URL(string: "openclaw-debug://dashboard")!
#expect(DeepLinkParser.parse(url) == .dashboard)
}
@Test func gatewayDeepLinkRejectsInsecureNonLoopbackWs() {
let url = URL(
string: "openclaw://gateway?host=attacker.example&port=18789&tls=0&token=abc")!