--- name: ios-sync preamble-tier: 3 version: 1.0.0 description: | Regenerate the iOS debug bridge against the latest upstream gstack templates. Updates StateServer.swift, DebugOverlay.swift, Package.swift, and the typed @Observable state accessors. Use after you upgrade gstack or add new ViewModels/properties that need accessor coverage. Use when asked to "resync the iOS debug bridge", "regenerate iOS accessors", or "update the gstack iOS instrumentation". (gstack) voice-triggers: - "resync the iOS debug bridge" - "regenerate iOS accessors" - "update the gstack iOS instrumentation" allowed-tools: - Bash - Read - Write - Edit - Glob - Grep - AskUserQuestion triggers: - resync the ios debug bridge - regenerate ios accessors - update the gstack ios instrumentation --- {{PREAMBLE}} # Resync the iOS debug bridge After `/ios-qa` is installed in an app, the user may: 1. Add new `@Observable` classes or properties that need accessor coverage. 2. Upgrade gstack to a newer version with hardening fixes. 3. Move the `// @Snapshotable` generator marker comment to a different field. This skill regenerates the relevant artifacts in place. **Templates live in upstream gstack.** The installed `gstack-ios-qa-regen` launcher resolves its own gstack root and copies only the supported bridge files from `ios-qa/templates/`. The fork's HTTP-fetch and wildcard-copy patterns are gone. ## Phase 1: Detect installed version 1. Read `/DebugBridgeGenerated/.gstack-version` (written by /ios-qa during install). If missing, treat the install as "unknown old version". 2. Read upstream version from `$GSTACK_ROOT/VERSION`. 3. If versions match AND no new `@Observable` classes were added, exit early with "already up to date". ## Phase 2: Regenerate codegen output Run the deterministic regenerator once. `--app-source` is the directory the accessor scanner should inspect; `--bridge-dir` is the local Swift package that the app links in Debug builds: ```bash ~/.claude/skills/gstack/bin/gstack-ios-qa-regen \ --app-source "$APP_SOURCE_DIR" \ --bridge-dir "$APP_SOURCE_DIR/DebugBridge" ``` The command removes only the known obsolete generated files from the former flat `DebugBridgeGenerated/` layout before emitting the current accessor. Generation accepts file-scope observable classes and JSON-native scalar, array, String-keyed dictionary, and Optional field types. It rejects custom types, implicitly unwrapped Optionals, nested observable classes, and duplicate snapshot keys before writing a completion marker. The composite-hash cache key handles whether anything actually needs regenerating; if Swift version, generator git rev, lockfile, source content, and platform triple all match the cache, this is a ~50ms no-op. ## Phase 3: Review the generated diff 1. Review changes under `/DebugBridge/` and `/DebugBridgeGenerated/StateAccessor.swift`. 2. Confirm the command did not modify the app's handwritten Swift files. 3. Keep app-specific wiring in the app target; canonical bridge package files are regenerated from upstream and should not be hand-edited. ## Phase 4: Verify 1. `swift build` succeeds against the app's package. 2. `xcodebuild -scheme ` succeeds. 3. Re-launch the app on the device; daemon connects + rotates token. 4. `GET /state/snapshot` returns the new accessor schema hash. ## Failure modes | Symptom | Action | |---|---| | Swift compile fails after regen | Revert via `git restore` + AskUserQuestion: surface the compile error | | Codegen reports an invalid marked declaration | Use a file-scope observable class and a writable instance `var` with an explicit JSON-native type, internal/public setter, and a key unique across models; otherwise remove the `// @Snapshotable` marker. | | Schema hash unchanged after adding new @Observable | No field has the standalone `// @Snapshotable` marker comment — codegen excludes unmarked state correctly. Add the comment immediately above each field that should be snapshotted. | | Scanner sees generated bridge sources | Pass the narrow app source directory; the regenerator automatically excludes `DebugBridgeGenerated` and `StateAccessor.swift`. |