* chore: delete internal/agent package
* chore(build): remove sdk-android from gradle settings
* chore(makefile): remove sdk-android targets
* chore(ci): remove release-android job from release workflow
* chore(folio): remove sdk-android dependency
* chore(folio): remove SDK initialization from FolioApplication
* chore(folio): delete snapshot extractor files
* feat(folio): add balance to account card content description
* feat(folio): add hierarchy content descriptions to LedgerScreen
* refactor(folio): rewrite spec.ts to use ax extractors
* docs: remove in-app SDK from README
* feat(folio): add focused_input indicator to App
* docs: remove in-app SDK from index
* refactor(runner): remove agent SDK connection and snapshot step
* test(runner): update tests for SDK removal
* docs: remove Android SDK section from getting-started
* refactor(testrun): remove agent SDK connection setup
* docs: remove snapshots from writing-specs
* docs: remove in-app SDK from architecture doc
* docs(folio): update README for SDK removal
* docs: update per-step cycle diagram in architecture doc
* fix(folio): detect screens from unique element presence, not id: selectors
testTag() in Compose is not exposed as resource-id without testTagsAsResourceId.
Use desc: selectors for elements unique to each screen instead of id: path queries.
* feat(folio): add screen root contentDescription for scoped ax selection
Each screen root gets semantics { contentDescription = "ScreenName" } so
sanderling specs can scope element lookups through the screen: desc:LoginScreen > desc:login_submit.
* fix(folio): scope all ax selectors through screen root nodes
Use desc:ScreenName > desc:element path queries so every selector is
rooted at the screen level. focusedInput stays unscoped since it lives
in the app root, outside any screen.
* fix(folio): guard newAccountBalanceIsZero against navigation false positives
Scoped selectors return [] when not on HomeScreen so accounts vanish and
reappear as apparently-new on each visit. Skip the check when prev was empty.
* chore(folio): link @sanderling/spec to local pkg/spec for IDE type checking
* feat(spec): add desc, class, clickable, enabled, checked, focused, selected to AccessibilityElement
Runtime fields set by the verifier were missing from the TypeScript type,
causing linting errors on el.desc and related accesses in specs.
* chore(folio): switch to bun, add tsconfig.json for IDE type checking
- Remove package-lock.json, add bun.lock
- Add tsconfig.json so VSCode resolves @sanderling/spec types
- Fix parseAccount/parseLedgerRow to accept string | undefined
* feat(ios): add simulator management package
* feat(testrun): add iOS platform path (simctl launch + direct TCP)
* feat(cli): add --ios-device flag and IosDevice option
* feat(sdk-ios): add Kotlin Native iOS SDK (TCP agent + POSIX socket + dispatch pauser)
* feat(folio-ios): wire SanderlingIos.start() in MainViewController
* feat(folio-ios): add test-ios justfile recipe
* fix(sdk-ios): remove unavailable C macros; manual byte swap + no-cast warnings
* fix(testrun): simctl-first launch order for iOS; Maestro init after SDK connects
* feat(proto): add env map to LaunchRequest
* feat(driver): add env param to Launch interface + all implementations
* feat(testrun): launch iOS app via XCTest with env vars instead of simctl
* feat(sidecar): add IosDriverBackend using Maestro IOSDriver + env pass-through
* feat(sidecar): wire env map in DriverService + IosDriverBackend in Main
* fix(sidecar): use LocalIOSDevice (WDA+simctl) + stop before relaunch
* fix(sidecar): include exception type in gRPC error description
* fix(sidecar): pick free WDA port instead of hardcoded 9100
Use SocketUtils.nextFreePort to pick a free port in the 22000-23000 range
rather than hardcoding 9100, which only worked if a previous WDA session
left a listener there.
* fix(sdk-ios): check semaphore wait result and throw on snapshot timeout
dispatch_semaphore_wait returns nonzero on timeout; ignoring the return
value caused pauseAndSnapshot to silently return an empty map, sending a
garbage empty STATE frame to the host. Now throws so the agent loop
reconnects instead.
* fix(folio-ios): register snapshot extractors before starting agent
SanderlingIos.start() was called before the snapshot objects were
initialized, so a PAUSE message arriving early produced an empty snapshot.
Move start() to after all extractors are registered.
* chore(ios): remove dead LaunchApp function
LaunchApp had no callers since bff3a49 switched iOS launch to go through
the sidecar driver. Remove the dead code and unused os import.
* test(ios): add unit tests for pickSimulator and iOS flag parsing
Tests for all pickSimulator branches (by name, by UDID, unknown, empty
list, iPhone preference, fallback to first). Also tests BootedUDID on a
canceled context and verifies --platform ios and --ios-device flags are
accepted by parseTestArgs.
* fix(ios): propagate error from BootedUDID instead of silently swallowing
* fix(sidecar): IosDriverBackend.healthy() returns true; WDA liveness checked in open()
* fix(sidecar): warm up WDA after health check to absorb startup race
* fix(runner): surface clear message on WDA connection drop
* docs(testrun): note WDA warmup location above WaitForHealth
* fix(sidecar): extract warmup + add one-shot WDA reconnect on IOException
* fix(runner): fatal on permanent WDA drop during hierarchy fetch
* fix(sidecar): walk cause chain in withReconnect to catch Maestro-wrapped IOException
* fix(sidecar): explicit Unit return in pressKey and waitForIdle withReconnect lambdas
* fix(sidecar): serialize WDA reconnect with ReentrantLock to prevent concurrent xcodebuild races
* fix web examples package config
* Revert "fix web examples package config"
This reverts commit 70c10ade27.
* chore: gitignore built sanderling binary
* fix(hierarchy): parse iOS [x1,y1][x2,y2] bounds + match iOS merged desc labels
* refactor(folio-spec): replace bloated spec with two focused properties
Login is opportunistic. Two concrete properties:
1. every new account starts with balance 0
2. every new txn changes ledger balance by exactly its signed amount
Actions: directed login -> addAccount -> addTxn -> back weighted flow.
* feat(proto): add env map to LaunchRequest
* feat(driver): add env param to Launch interface and all implementations
* feat(sidecar): add IosDriverBackend using Maestro IOSDriver + env pass-through
* feat(testrun): launch iOS app via XCTest with env vars instead of simctl
* fix(ios): replace LaunchApp with BootedUDID; simctl launch moved to XCTest path
* test(cli): add tests for ios platform flag and ios-device flag parsing
* fix(sdk-ios): check semaphore wait result; resolve port from args and env; register extractors before start
* fix(runner): allow nil connection for web platform
* fix(testrun): skip SDK handshake for web platform
* feat(folio-web): add React/Vite web sample app
* feat(folio-web): add sanderling spec
* fix(chrome): use InsertText for multi-char text input
* feat(hierarchy): add Screen field populated from sanderling-screen attr
* fix(chrome): auto-detect viewport from CSS vars, fix InputText accumulation, expose route as screen
* fix(runner): fall back to hierarchy root screen when snapshot screen is empty
* fix(folio-web): broaden loggedIn extractor to all authenticated pages