Skip to content
Closed
1 change: 1 addition & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- 'README.md'
- 'AGENTS.md'
- 'CHANGELOG.md'
- 'changelog.d/**'
- 'CONTEXT.md'
- 'CONTRIBUTING.md'
- 'LICENSE'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- 'README.md'
- 'AGENTS.md'
- 'CHANGELOG.md'
- 'changelog.d/**'
- 'CONTEXT.md'
- 'CONTRIBUTING.md'
- 'LICENSE'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- 'README.md'
- 'AGENTS.md'
- 'CHANGELOG.md'
- 'changelog.d/**'
- 'CONTEXT.md'
- 'CONTRIBUTING.md'
- 'LICENSE'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- 'README.md'
- 'AGENTS.md'
- 'CHANGELOG.md'
- 'changelog.d/**'
- 'CONTEXT.md'
- 'CONTRIBUTING.md'
- 'LICENSE'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- 'README.md'
- 'AGENTS.md'
- 'CHANGELOG.md'
- 'changelog.d/**'
- 'CONTEXT.md'
- 'CONTRIBUTING.md'
- 'LICENSE'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- 'README.md'
- 'AGENTS.md'
- 'CHANGELOG.md'
- 'changelog.d/**'
- 'CONTEXT.md'
- 'CONTRIBUTING.md'
- 'LICENSE'
Expand Down
45 changes: 3 additions & 42 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,9 @@
# Changelog

## Unreleased
## 0.15.1 – 0.21.13

These releases did not split the changelog per version.

- Fixed (android): snapshot nodes and `get attrs` carry the accessibility `heading` flag and the
`roleDescription` an app set on a node. React Native puts a header, a tab, a tab list, a link, or a
menu on a plain `android.view.View` and tells the accessibility tree what it is through these two
facts; the helper never serialized either, so every one of them was a nameless `View` to an agent.
The helper now writes `heading` when the node reports it (API 28 or later) and `role-description`
when the app set one, and the parser, the Android hierarchy node, and the published snapshot node
carry them to `get attrs` and the selector digest. The class stays the `type`.
- Fixed (ios): `perf cpu profile report --kind xctrace` on Xcode 27 no longer fails with
`Apple xctrace CPU report contained no samples` on a trace that holds thousands of samples. Xcode
27 exports each `time-profile` sample stack as `<tagged-backtrace>` instead of `<backtrace>`, and
the parser read only the old element, so every row resolved no stack at all. Both spellings now
parse through the same `id`/`ref` resolution, so a profile recorded with an older Xcode reports
what it did before. (#2860)
- Fixed (ios): `alert get`, `accept`, or `dismiss` with no alert on screen no longer reads every
element of the app to look for a popover's dismiss region. That walk cost one XCTest round trip
per element, plus XCTest's retry cycle for each element that vanished mid-walk. On a loading
WebView it outran the 10 s alert budget and kept the runner's main thread busy for more than 30 s
after the command failed, so later commands failed with `RUNNER_BUSY`. The dismiss region is now
found with one predicate query per window set. (#2491)
- Changed (apple): a read-only runner command is resent inside the same request only when the
runner refused it as `RUNNER_BUSY`. Before, any `COMMAND_FAILED` carrying `details.retriable:
true` was sent up to three times. That flag tells a caller's own poll, such as `wait`, to try
Expand All @@ -39,25 +22,11 @@
- Fixed (mobile): a read taken right after a `scroll`, `swipe`, or `gesture swipe` no longer reports
a definite miss when the surface never settled. When post-gesture stabilization ran out of budget
on a surface still moving, `is visible` answered a plain `selector_not_found` and `is absent`
passed. The capture now carries `postGestureOutcome` (`{ kind, gesture: { action, positionals } }`)
with `kind: "unsettled"`, and so does a re-capture taken at once to recover or widen it. A proven
no-effect gesture rides the same field with `kind: "no-effect"`; before, its warning reached only
`snapshot`. `is`, `get`, `find`, `wait`, and every interaction that captured it (`click`, `press`,
`fill`, and the other touch and gesture commands) report the field in `data` or `error.details`
with an appended warning; `snapshot` appends the warning. `is absent` refuses an unsettled capture
with `observation: "unsettled"`, `wait absent` keeps polling, and the next read captures afresh.
A failed read also carries `targetActivation` in `error.details`, and a failed interaction now
keeps the disclosure sentences in its hint.
passed. That capture now carries `unsettledGesture`: `is`, `get`, `find`, and `wait` report it (in
`error.details` or `data`) with an appended warning, `snapshot` appends the warning, `is absent`
refuses with `observation: "unsettled"`, `wait absent` keeps polling, and the next read captures
afresh. Click, press, and fill by selector do not disclose it yet. A failed read now also carries
`targetActivation` in `error.details`, the same place as `unsettledGesture`.
- Fixed (ios): a synthesized tap step inside a runner `sequence` (for example `press x y --count N`)
now follows the standalone tap's policy instead of its own. When accessibility is unavailable or no
app window resolves, the step now falls back to an XCTest coordinate tap instead of failing the
step with `UNSUPPORTED_OPERATION`. One helper now owns the synthesize-then-fallback decision at
every synthesized tap site (#2788).
- Fixed (ios): `open` on a local Simulator now waits for the launched app's discovery before it
decides whether the app is observable. On a loaded host `simctl spawn launchctl list` outlasts one
1.5 s discovery wait slice, and the launch observation read that slice as an unobservable app, so
Expand Down Expand Up @@ -113,14 +82,6 @@
report no UIKit class names — the XCTest runner, whose own queries answered 76 nodes for that same
state, plus `appium-source` and `limrun-ios-tree` — never trigger the cut. All 39 flows of React Navigation's Maestro suite pass on an iPhone 17 Simulator running
iOS 26.2 with this change, including two that never passed on the bridge.
- Fixed (ios): runtime clang builds no longer compile with `-Werror`, so a new warning from a future
Xcode SDK cannot break the AX bridge or fold on a user's machine that this repository cannot fix
for them. The fold helper is now built through the same content- and toolchain-keyed build cache
as the AX bridge, so a fold call after the first serves a cached binary instead of recompiling
`Fold.m` on every call, and switching `DEVELOPER_DIR` busts the cache instead of serving a binary
built against a different SDK. A darwin-only CI step (`.github/workflows/ios.yml`) compiles each
build's production argv with `-Werror` appended whenever its sources change, so a new warning still
fails CI (#2796).
- Fixed (ios): a local Simulator snapshot taken through the host AX bridge once again publishes the
geometric `hittable` fact, so `is hittable` and a `hittable:` selector resolve the same controls on
the bridge and the XCTest runner. The snapshot capability table has declared `hittable =
Expand Down
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ so name the version the CI lanes install and the published helper matches the CI
`pnpm package:npm` is a release guard, not a routine development command. Use the specific commands
above while iterating.

### Changelog fragments, not `CHANGELOG.md` edits

A user-visible change adds a `changelog.d/<slug>.md` fragment; see `changelog.d/README.md` for the
format. Only the `npm version` release commit writes `CHANGELOG.md`.

### The version on main never equals a published version

`release:publish` runs `release:mark-dev` right after `npm publish`, moving `package.json` (and the
Expand Down
6 changes: 6 additions & 0 deletions changelog.d/2491-alert-popover-dismiss-query.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- Fixed (ios): `alert get`, `accept`, or `dismiss` with no alert on screen no longer reads every
element of the app to look for a popover's dismiss region. That walk cost one XCTest round trip
per element, plus XCTest's retry cycle for each element that vanished mid-walk. On a loading
WebView it outran the 10 s alert budget and kept the runner's main thread busy for more than 30 s
after the command failed, so later commands failed with `RUNNER_BUSY`. The dismiss region is now
found with one predicate query per window set. (#2491)
5 changes: 5 additions & 0 deletions changelog.d/2788-sequence-tap-fallback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Fixed (ios): a synthesized tap step inside a runner `sequence` (for example `press x y --count N`)
now follows the standalone tap's policy instead of its own. When accessibility is unavailable or no
app window resolves, the step now falls back to an XCTest coordinate tap instead of failing the
step with `UNSUPPORTED_OPERATION`. One helper now owns the synthesize-then-fallback decision at
every synthesized tap site (#2788).
8 changes: 8 additions & 0 deletions changelog.d/2796-fold-helper-no-werror.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- Fixed (ios): runtime clang builds no longer compile with `-Werror`, so a new warning from a future
Xcode SDK cannot break the AX bridge or fold on a user's machine that this repository cannot fix
for them. The fold helper is now built through the same content- and toolchain-keyed build cache
as the AX bridge, so a fold call after the first serves a cached binary instead of recompiling
`Fold.m` on every call, and switching `DEVELOPER_DIR` busts the cache instead of serving a binary
built against a different SDK. A darwin-only CI step (`.github/workflows/ios.yml`) compiles each
build's production argv with `-Werror` appended whenever its sources change, so a new warning still
fails CI (#2796).
6 changes: 6 additions & 0 deletions changelog.d/2860-xctrace-tagged-backtrace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- Fixed (ios): `perf cpu profile report --kind xctrace` on Xcode 27 no longer fails with
`Apple xctrace CPU report contained no samples` on a trace that holds thousands of samples. Xcode
27 exports each `time-profile` sample stack as `<tagged-backtrace>` instead of `<backtrace>`, and
the parser read only the old element, so every row resolved no stack at all. Both spellings now
parse through the same `id`/`ref` resolution, so a profile recorded with an older Xcode reports
what it did before. (#2860)
8 changes: 8 additions & 0 deletions changelog.d/2864-post-gesture-outcome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- Changed (mobile): the post-gesture field that 0.21.13 added as `unsettledGesture` is now
`postGestureOutcome` (`{ kind, gesture: { action, positionals } }`), with `kind: "unsettled"` for
a surface that never settled. A re-capture taken at once to recover or widen that tree carries the
field too. A proven no-effect gesture rides the same field with `kind: "no-effect"`; before, its
warning reached only `snapshot`. Every interaction that captured the tree (`click`, `press`,
`fill`, and the other touch and gesture commands) now reports the field in `data` or
`error.details` with an appended warning, and a failed interaction keeps the disclosure sentences
in its hint. (#2864)
7 changes: 7 additions & 0 deletions changelog.d/2915-android-heading-role-description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- Fixed (android): snapshot nodes and `get attrs` carry the accessibility `heading` flag and the
`roleDescription` an app set on a node. React Native puts a header, a tab, a tab list, a link, or a
menu on a plain `android.view.View` and tells the accessibility tree what it is through these two
facts; the helper never serialized either, so every one of them was a nameless `View` to an agent.
The helper now writes `heading` when the node reports it (API 28 or later) and `role-description`
when the app set one, and the parser, the Android hierarchy node, and the published snapshot node
carry them to `get attrs` and the selector digest. The class stays the `type`.
42 changes: 42 additions & 0 deletions changelog.d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# changelog.d/

A PR with a user-visible change adds one fragment file here instead of editing `CHANGELOG.md`
directly. PRs never edit `CHANGELOG.md`. Only the `npm version` release commit writes it, by
running `scripts/changelog-release.ts` to fold every fragment present into a new version section
and delete the fragments it consumed.

This file is the only non-fragment file kept in this directory, so the directory stays in place
when no fragments are pending. The assembler ignores dotfiles such as `.DS_Store`; every other
entry must be a valid fragment.

## Adding a fragment

Create `changelog.d/<slug>.md`, where `<slug>` matches `^[a-z0-9][a-z0-9-]*$`. By convention, base
it on the branch name: `<issue-number>-<short-kebab>`, for example
`changelog.d/2799-macos-fullscreen-surfaces.md`. The PR number is not required in the name — it is
not known before the PR opens.

A fragment holds one or more bullets. Each bullet starts on a line matching:

```
^- (Breaking|Added|Changed|Deprecated|Removed|Fixed|Security)( \([^)]+\))?: \S
```

Continuation lines are indented by two spaces. Blank lines between bullets are allowed. Anything
else fails validation, including a leading non-bullet line or an unknown kind.

Example:

```md
- Fixed (macos): `screenshot --fullscreen` on the `desktop`, `menubar`, or `frontmost-app`
surface now refuses with `INVALID_ARGS` instead of being ignored. (#2849)
```

## Assembly

`npm version` runs the assembler as part of its `version` lifecycle script. It sorts bullets by
kind rank (Breaking, Removed, Changed, Deprecated, Added, Fixed, Security), then by fragment file
name, then by position within the fragment — deterministic regardless of the order the fragments
were added in — and writes the result under a new `## <version>` heading. `release:prepare` runs
the assembler with `--check`, which fails if any fragment is still present: that means a release
skipped assembly.
4 changes: 4 additions & 0 deletions docs/agents/pull-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ Gross diff budget: 1,000 lines by `git diff --stat origin/main...HEAD`. Rename-o
`refactor(move)` are exempt when `git diff -M90% --stat origin/main...HEAD` proves no material
content change.

A user-visible change adds a `changelog.d/<issue>-<slug>.md` fragment (see `changelog.d/README.md`)
instead of editing `CHANGELOG.md`. PRs never edit `CHANGELOG.md`; only the `npm version` release
commit writes it, by folding pending fragments into a new version section.

## Commits

Use conventional commit prefixes; no `[codex]` tags. Implementation commits come first. Enforcement
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
"prepare:publish-assets": "node scripts/prepare-publish-assets.mjs",
"build:package": "pnpm build && pnpm build:xcuitest:ios && pnpm build:xcuitest:macos && pnpm build:xcuitest:tvos && pnpm build:xcuitest:visionos && pnpm build:macos-helper:clean && pnpm prepare:publish-assets",
"package:npm": "pnpm build:package && pnpm check:package",
"release:prepare": "node scripts/release-mark-dev.mjs --check-release-version && rm -rf .tmp/release && pnpm check:mcp-metadata && pnpm build:package && pnpm check:package -- --pack-destination .tmp/release",
"release:prepare": "node scripts/release-mark-dev.mjs --check-release-version && node --experimental-strip-types scripts/changelog-release.ts --check && rm -rf .tmp/release && pnpm check:mcp-metadata && pnpm build:package && pnpm check:package -- --pack-destination .tmp/release",
"release:publish": "pnpm release:prepare && npm publish --ignore-scripts .tmp/release/*.tgz && pnpm release:mark-dev",
"release:mark-dev": "node scripts/release-mark-dev.mjs",
"ad": "node bin/agent-device.mjs",
Expand Down Expand Up @@ -166,7 +166,7 @@
"check:quick": "pnpm lint && pnpm typecheck",
"sync:mcp-metadata": "node scripts/sync-mcp-metadata.mjs",
"check:mcp-metadata": "node scripts/sync-mcp-metadata.mjs --check",
"version": "pnpm sync:mcp-metadata && git add server.json",
"version": "pnpm sync:mcp-metadata && node --experimental-strip-types scripts/changelog-release.ts && git add server.json CHANGELOG.md changelog.d",
"check:tooling": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm check:layering && pnpm depgraph:test && pnpm check:gate-manifest:test && pnpm check:gate-manifest && pnpm check:production-exports && pnpm check:tmpdir-leaks:test && pnpm check:xctest-selection && pnpm check:packaged-runner-swift && pnpm check:mcp-metadata && pnpm build && pnpm check:bundle-owner-files && pnpm check:package",
"check:unit": "pnpm test:unit && pnpm check:tmpdir-leaks && pnpm test:smoke",
"check": "pnpm check:tooling && pnpm check:fallow && pnpm check:unit",
Expand Down
Loading
Loading