mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-18 15:50:11 +02:00
f4f8b9f966
End-to-end verification on a connected iPhone 17 Pro Max via CoreDevice tunnel exposed three bugs the TS-stubbed and macOS-XCTest layers missed: 1. acceptLocalOnly=true was too tight. Network.framework's "local" gate only allows ::1 / 127.0.0.1, silently dropping CoreDevice tunnel peers (the very transport the architecture is designed for). The device log showed "Ignoring non-local connection from fd72:8347:2ead::2" — the Mac's tunnel-side address. Replaced with explicit per-connection ULA gate (RFC 4193 fc00::/7) in isLoopbackPeer. 2. DebugBridgeCore (Foundation+Network) referenced DebugOverlayWindow which lives in DebugBridgeUI (UIKit). Backwards module dep. Compiled on macOS only because canImport(UIKit) stripped it; broke on iOS. Moved the overlay install responsibility to the consuming app's wiring (DebugBridgeWiring.swift.template already shows the pattern). 3. @Observable macro + @Snapshotable property wrapper conflict. Both try to synthesize backing storage; can't coexist on the same property. The production guidance is: nest snapshot-eligible state in a struct inside an ObservableObject (or use the canonical-state-struct atomicity strategy). Fixture switched to a plain class to demonstrate. Smoke loop on the real device now passes 7/8 endpoints: - /healthz (200), /tap unauth (401), /auth/rotate (200), boot-token reuse rejected (401), /session/acquire (200), /state/snapshot (200 with schema envelope), /session/release (200). /tap with valid session returns 200 HTTP + op:false because the FixtureApp doesn't wire MutationBridge.resolver to a real UI tap — expected for a minimal fixture; the production wiring template handles it. Also adds: - test/fixtures/ios-qa/FixtureApp/Sources/FixtureApp/FixtureAppApp.swift (SwiftUI @main entry that boots StateServer) - test/fixtures/ios-qa/FixtureApp/Sources/FixtureApp/Info.plist - test/fixtures/ios-qa/FixtureApp/project.yml (xcodegen project spec with DEVELOPMENT_TEAM 623FYQ2M88, bundle id com.gstack.iosqa.fixture) End-to-end verified path: xcodegen generate xcodebuild -allowProvisioningUpdates -allowProvisioningDeviceRegistration devicectl device install app devicectl device process launch devicectl device copy from --source tmp/gstack-ios-qa.token curl -6 http://[<corodevice-ipv6>]:9999/...
50 lines
1.8 KiB
Plaintext
50 lines
1.8 KiB
Plaintext
// AUTO-GENERATED from gstack/ios-qa/templates/DebugBridgeManager.swift.template
|
|
//
|
|
// Bootstraps StateServer on app launch. Lives in DebugBridgeCore (no UIKit
|
|
// dependency). The DebugOverlay install is wired separately by the consuming
|
|
// app — it lives in DebugBridgeUI which depends on DebugBridgeCore (not the
|
|
// other way around). Everything is #if DEBUG-gated; this file does not exist
|
|
// in Release builds.
|
|
|
|
#if DEBUG
|
|
|
|
import Foundation
|
|
|
|
@MainActor
|
|
public final class DebugBridgeManager {
|
|
public static let shared = DebugBridgeManager()
|
|
|
|
public func start(appState: AppState) {
|
|
// 1. Register the canonical AppState struct + accessor wiring.
|
|
// AppStateAccessor.register(_:) is generated by gen-accessors-tool.
|
|
AppStateAccessor.register(appState)
|
|
|
|
// 2. Boot the StateServer.
|
|
StateServer.shared.start()
|
|
|
|
// 3. The consuming app installs DebugOverlayWindow separately. See
|
|
// the example in DebugBridgeWiring.swift.template:
|
|
//
|
|
// #if canImport(UIKit)
|
|
// DebugOverlayWindow.shared.install(recording: recording)
|
|
// #endif
|
|
}
|
|
}
|
|
|
|
// Placeholder. gen-accessors-tool emits the real `AppStateAccessor` enum next
|
|
// to the app's canonical state struct. Apps that haven't run codegen get a
|
|
// stub that registers no accessors (snapshot is empty, restore returns
|
|
// missing-key for every key).
|
|
@MainActor
|
|
public enum AppStateAccessor {
|
|
public static var register: (Any) -> Void = { _ in }
|
|
}
|
|
|
|
// Apps declare their canonical state struct; codegen reads it and emits
|
|
// AppStateAccessor.register. The app's struct must be `@Observable` and
|
|
// must hold all snapshot-eligible state in `@Snapshotable`-marked fields.
|
|
@MainActor
|
|
public protocol AppState: AnyObject {}
|
|
|
|
#endif // DEBUG
|