You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -245,6 +245,7 @@ Beyond the slash-command skills, gstack ships standalone CLIs for workflows that
245
245
|`gstack-taste-update`|**Design taste learning** — writes approvals and rejections from `/design-shotgun` into a persistent per-project taste profile. Decays 5%/week. Feeds back into future variant generation so the system learns what you actually pick. |
246
246
|`gstack-ios-qa-daemon`|**iOS QA daemon** — Mac-side broker between an agent and a connected iPhone over USB CoreDevice. Loopback by default; `--tailnet` opens a Tailscale-facing listener with identity-gated capability tiers. Single-instance via flock on `~/.gstack/ios-qa-daemon.pid`. See [docs/howto-ios-testing-with-gstack.md](docs/howto-ios-testing-with-gstack.md). |
247
247
|`gstack-ios-qa-mint`|**iOS allowlist manager** — owner-grant CLI for the tailnet allowlist. `grant`/`revoke`/`list` against `~/.gstack/ios-qa-allowlist.json` (mode 0600). Remote agents never auto-allowlist; this is the explicit-intent path. |
248
+
|`gstack-ios-qa-regen`|**iOS bridge regenerator** — deterministically installs the canonical DebugBridge package, generates typed state accessors, and records the installed gstack version. Safe to rerun after source changes or upgrades. |
248
249
249
250
### Continuous checkpoint mode (opt-in, local by default)
Copy file name to clipboardExpand all lines: docs/howto-ios-testing-with-gstack.md
+71-17Lines changed: 71 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,7 +9,7 @@ Everything below has been verified end-to-end on a real iPhone 17 Pro Max runnin
9
9
- macOS with Xcode 16.0+ installed (`xcrun devicectl --version` must succeed). Xcode 16 ships the CoreDevice tunnel `devicectl` uses to reach the device over USB.
10
10
- A real iPhone running iOS 16 or later. Unlocked, paired with your Mac, with **Developer Mode** enabled in Settings → Privacy & Security.
11
11
- An Apple developer team — the free personal team works fine for live-device debug deploys. You'll need the team ID (e.g. `623FYQ2M88`), not the certificate ID. Find it in Xcode → Settings → Accounts → your Apple ID → team list. The setup signs the app for your device on first deploy via `-allowProvisioningUpdates -allowProvisioningDeviceRegistration`.
12
-
- gstack installed (`./setup` complete; `bin/gstack-ios-qa-daemon` must be on disk and executable).
12
+
- gstack installed (`./setup` complete; `gstack-ios-qa-regen` and `gstack-ios-qa-daemon` must be on PATH).
13
13
- Bun runtime on PATH (`bun --version`). The Mac-side daemon is a bun process.
14
14
15
15
For the optional remote-agent (Tailscale) mode, you'll additionally need Tailscale installed on the Mac with `/var/run/tailscale.sock` readable.
@@ -30,28 +30,49 @@ For the optional remote-agent (Tailscale) mode, you'll additionally need Tailsca
30
30
31
31
The iOS `StateServer` is loopback-only **always**, even in remote mode. Identity validation happens Mac-side because the iPhone has no way to validate a Tailscale identity.
32
32
33
-
## Step 1: Add the DebugBridge templates to your iOS app
33
+
## Step 1: Generate the DebugBridge package
34
34
35
-
The templates live at `~/.claude/skills/gstack/ios-qa/templates/` after `./setup`. The fastest install is to invoke the `/ios-qa` skill in Claude Code from your app's root — it reads your Swift source, codegens typed `@Observable` state accessors, and lays down the templates with your bundle ID. Or do it by hand:
35
+
Run `/ios-qa` from the app root, or invoke the same deterministic regenerator directly:
36
36
37
-
1. Copy these into a `DebugBridge/` SPM package inside your app workspace:
2. Add the package as a local dependency of your app. Depend on the `DebugBridgeUI` product with `condition: .when(configuration: .debug)`. `DebugBridgeCore` and `DebugBridgeTouch` come in transitively.
45
-
3. In your `@main` App init, gate the wiring on `#if DEBUG`:
37
+
```bash
38
+
gstack-ios-qa-regen \
39
+
--app-source "$PWD/Sources/YourApp" \
40
+
--bridge-dir "$PWD/DebugBridge"
41
+
```
42
+
43
+
The command copies an explicit allowlist of canonical templates into the local
44
+
`DebugBridge/` Swift package, generates
45
+
`DebugBridgeGenerated/StateAccessor.swift`, and writes the installed version to
46
+
`DebugBridgeGenerated/.gstack-version`. It excludes generated output from its
47
+
own schema hash, so rerunning it with unchanged source is a fast, byte-stable
48
+
cache hit. It also removes the explicit legacy generated-file set from older
49
+
flat harness layouts so stale bridge sources cannot shadow the package.
50
+
51
+
1. Add `DebugBridge/` as a local package dependency. Depend on the
52
+
`DebugBridgeUI` product only in Debug configuration; `DebugBridgeCore` and
53
+
`DebugBridgeTouch` come in transitively.
54
+
2. Add `DebugBridgeGenerated/StateAccessor.swift` to the app target.
55
+
3. In your `@main` App init, install the UIKit resolvers before starting the
56
+
server, then register the generated accessor. Replace the example
57
+
state/accessor names with the type the generator found:
46
58
47
59
```swift
48
60
#if DEBUG
49
61
importDebugBridgeCore
50
-
StateServer.shared.start()
51
62
#ifcanImport(UIKit)
52
63
importDebugBridgeUI
64
+
#endif
65
+
#endif
66
+
67
+
// Inside App.init(), after appState is initialized:
|`POST /type``{text}`| Set text on the current first responder. | bearer + session, interact tier |
131
161
132
162
Mutating requests require both an `Authorization: Bearer <token>` header AND an `X-Session-Id` header. Read endpoints (`/screenshot`, `/elements`, `GET /state/*`) only need the bearer.
133
163
134
-
The state snapshot is opt-in per field via a `@Snapshotable` property wrapper on your canonical state struct. Fields you don't annotate never appear in the snapshot, which keeps tokens, PII, and auth state out of recorded fixtures by default.
164
+
The state snapshot is opt-in per field via a standalone generator marker
165
+
comment immediately above a property. It is intentionally not a property
166
+
wrapper, so it compiles cleanly with Observation:
167
+
168
+
```swift
169
+
@Observable
170
+
finalclassAppState {
171
+
// @Snapshotable
172
+
var username: String=""
173
+
174
+
var authToken: String=""// never exported
175
+
}
176
+
```
177
+
178
+
Unmarked fields never appear in the snapshot, which keeps tokens, PII, and
179
+
auth state out of recorded fixtures by default. A marked field must be a
180
+
writable instance `var` on a file-scope observable class, with an explicit type
181
+
and an internal or public setter. Supported snapshot types are JSON-native
0 commit comments