Skip to content

ios-qa: DebugBridge templates fail clean install on Swift 6.x + leak private SPIs into Release (5 bugs, incl. Apple-rejection class) #1836

@aweevenson-sea

Description

@aweevenson-sea

gstack version: v1.55.0.0
Skill: /ios-qa
Discovered: 2026-06-01, fresh install of DebugBridge into the Dugout Sports iOS app (after our PR #342 had to surgically remove the previous DebugBridge to pass App Store review — see #5 below, which is the same failure mode).

A clean /ios-qa install of the DebugBridge stack currently does not build out of the box on Swift 6.x toolchains, and — most seriously — the generated Obj-C touch-synthesis target compiles private UIKit/IOKit SPIs into Release builds, which Apple's static scanner rejects. Five distinct template bugs below, in increasing severity. We've applied local patches for all five in our working tree; the corrected files are quoted/referenced inline.


1. gen-accessors-tool/Package.swift references a non-existent test target

ios-qa/scripts/gen-accessors-tool/Package.swift declares:

.testTarget(name: "GenAccessorsTests", path: "Tests/GenAccessorsTests")

but Tests/ does not exist in the package, so swift build fails:

error: invalid custom path 'Tests/GenAccessorsTests' for target 'GenAccessorsTests'

Fix: remove the .testTarget(...) block — no tests exist.


2. Package.swift.template puts the tools-version directive on line 21, not line 1

ios-qa/templates/Package.swift.template opens with a multi-line doc-comment block and only declares // swift-tools-version:5.9 on ~line 21. The directive must be the first line of the manifest. Swift 6.0+ rejects the misplaced directive:

error: manifest is backward-incompatible with Swift < 6.0

Fix: move // swift-tools-version:5.9 to line 1 (doc-comment block follows it). Our generated apps/ios/DebugBridge/Package.swift shows the correct ordering — directive on line 1, comment block lines 2–22.


3. Package.swift.template references a non-existent test target

Same template declares:

.testTarget(name: "DebugBridgeCoreTests", path: "Tests/DebugBridgeCoreTests")

/ios-qa never populates a Tests/ folder, so the install fails the same way as #1 (invalid custom path).

Fix: drop the .testTarget(...) block.


4. DebugBridgeWiring.swift.template is broken and redundant — delete it

ios-qa/templates/DebugBridgeWiring.swift.template does not compile and duplicates wiring that Bridges.swift.template already provides:

  • import DebugBridge — there is no DebugBridge module. The modules are DebugBridgeCore, DebugBridgeUI, DebugBridgeTouch.
  • References AccessibilityScanner, SnapshotCapture, MutationDispatcher — none of these symbols exist anywhere in the templates. Bridges.swift.template defines ElementsBridgeImpl, ScreenshotBridgeImpl, MutationBridgeImpl instead.
  • Calls DebugBridgeManager.shared.start(appState:recording:)DebugBridgeManager only defines start(appState:).

Bridges.swift's DebugBridgeUIWiring.installAll() already performs the actual wiring (confirmed in our generated Sources/DebugBridgeUI/Bridges.swift — public enum DebugBridgeUIWiring { static func installAll() ... }).

Fix: delete DebugBridgeWiring.swift.template entirely.


5. ⚠️ DebugBridgeTouch.m.template compiles private SPIs into App Store Release builds

This is the critical one — it is the exact failure that got Dugout Sports v1.0 rejected (App Store Guideline 2.1, private API scan) and forced the surgical DebugBridge removal in our PR #342.

ios-qa/templates/DebugBridgeTouch.m.template gates its iOS implementation block with:

#if TARGET_OS_IOS

instead of:

#if TARGET_OS_IOS && defined(DEBUG)

The block references private UITouch/UIEvent/IOKit SPIs — _setLocationInWindow:resetPrevious:, _addTouch:forDelayedDelivery:, _clearTouches, _setHIDEvent:, _setIsFirstTouchForView:, _touchesEvent, IOHIDEventCreateDigitizer*. With only TARGET_OS_IOS, these compile into iOS Release builds and land in the App Store binary, where Apple's static scanner flags them.

Critically, the Package.swift Swift-side gate does not protect this file:

swiftSettings: [.define("DEBUG", .when(configuration: .debug))]

swiftSettings only affects Swift compile units. The Obj-C .m file is compiled with cSettings, which the template doesn't set — so DEBUG is never defined for this translation unit in a SwiftPM build, and the SPIs leak.

Fix (two parts):

  1. Change the gate to #if TARGET_OS_IOS && defined(DEBUG). The existing #else stub (iOS Release / macOS / Catalyst) already provides the no-op implementations, so the non-DEBUG branch is already handled.
  2. Add a cSettings DEBUG define to the DebugBridgeTouch target in Package.swift.template as belt-and-suspenders:
.target(
    name: "DebugBridgeTouch",
    ...
    cSettings: [
        .define("DEBUG", to: "1", .when(configuration: .debug)),
    ],
    ...
)

Our patched apps/ios/DebugBridge/Sources/DebugBridgeTouch/DebugBridgeTouch.m now gates at line 28 (#if TARGET_OS_IOS && defined(DEBUG)), with the #else stub at line 294 and #endif at line 307; the patched Package.swift carries the cSettings define on the DebugBridgeTouch target.


Verification (passes with all five fixes applied)

cd "$APP/DebugBridge" && swift build -c release
nm -j .build/arm64-apple-macosx/release/DebugBridgeTouch.build/DebugBridgeTouch.m.o \
  | grep -qE '_setLocationInWindow|_addTouch|_clearTouches|_setHIDEvent' \
  && exit 1 || echo "clean"

Expected: swift build -c release succeeds (fixes #1#4) and the nm symbol scan prints clean (fix #5).

We have local patches for all five at apps/ios/DebugBridge/ in the Dugout repo (uncommitted, branch fix/data-accuracy-fixes) and can open a PR against the templates if useful — happy to do so once you confirm the approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions