mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
docs: pandoc-based site and v0.1.0 groundwork (#5)
* chore(prose): remove em-dashes from config files * chore(prose): remove em-dashes from android sdk config * docs(spec-api): remove em-dash from README * fix(doctor): reword sidecar-jar error without em-dash * test(sidecar): reword assertion message without em-dash * docs: add CLAUDE.md with project conventions * build: add docs target for pandoc site * docs(site): add pandoc template and stylesheet * docs(site): add pandoc build script * docs(site): add landing pages * docs(manual): add getting-started * docs(manual): add writing-specs * docs(manual): add runs * docs(manual): add cli reference * docs(dev): add design principles * docs(dev): add architecture * ci: deploy docs site to github pages * docs: rewrite README as entry point to docs site
This commit is contained in:
25 files changed
+920
-56
No files matched your search
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Architecture
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
Three processes, two transports.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Go["uatu (Go)"]
|
||||
Bundler[Bundler<br/>esbuild] --> Verifier[Verifier<br/>goja + LTL]
|
||||
Verifier <--> Runner[Runner]
|
||||
Runner --> Trace[Trace writer<br/>JSONL + PNG]
|
||||
Runner <--> Driver[Driver iface]
|
||||
end
|
||||
|
||||
subgraph Sidecar["Maestro Sidecar (JVM)"]
|
||||
Maestro[maestro-client]
|
||||
end
|
||||
|
||||
subgraph Device["Emulator"]
|
||||
subgraph App["Android app (debug)"]
|
||||
SDK[uatu-sdk<br/>pause / hierarchy<br/>logs / coverage]
|
||||
end
|
||||
end
|
||||
|
||||
Driver -- gRPC --> Maestro
|
||||
Maestro -- UIAutomator --> App
|
||||
Runner -- Unix socket --> SDK
|
||||
Trace --> Runs[(runs/)]
|
||||
```
|
||||
|
||||
## Processes
|
||||
|
||||
**uatu (Go).** The top-level binary. Bundles the spec with esbuild, evaluates it in goja, runs the main loop, dispatches actions through the driver, writes the trace.
|
||||
|
||||
**Maestro sidecar (JVM).** A Kotlin process that wraps `maestro-client` and exposes a gRPC surface matching the `driver.Driver` interface. Handles UI input, screenshots, the system accessibility tree, and OS-level alerts.
|
||||
|
||||
**In-app SDK.** A Kotlin (or Swift for iOS) library linked into the app under test. Exposes a Unix socket to the runner. Provides pause and resume, view-hierarchy dumps, coverage reads, log capture, and user-registered state extractors.
|
||||
|
||||
## Transports
|
||||
|
||||
| Channel | Transport | Purpose |
|
||||
|---|---|---|
|
||||
| Go to Maestro sidecar | gRPC (localhost TCP) | UI input, screenshots, system alerts |
|
||||
| Go to in-app SDK | Unix domain socket | Pause / resume, hierarchy, coverage, logs, extractors |
|
||||
|
||||
The split exists for one reason: only real UI events need the cost of crossing process and OS-API boundaries. Introspection is cheap, frequent, and lives on a fast local socket directly to the app.
|
||||
|
||||
## Per-step cycle
|
||||
|
||||
The heart of the system is:
|
||||
|
||||
```
|
||||
pause ─► capture state ─► evaluate properties ─► pick action ─► resume ─► dispatch
|
||||
```
|
||||
|
||||
1. The runner asks the driver to wait until the UI is idle.
|
||||
2. The runner sends `PAUSE` to the SDK over the agent socket. The SDK freezes the main runloop at a safe point.
|
||||
3. The SDK sends back a `STATE` message: view hierarchy, coverage delta, logs since last step, exception list, snapshot values.
|
||||
4. The runner feeds state into goja. Extractors re-read; properties re-evaluate; the action generator returns a weighted tree.
|
||||
5. The runner writes the trace entry for this step.
|
||||
6. The runner picks an action by weight.
|
||||
7. The runner sends `RESUME` to the SDK, then dispatches the action through the driver (gRPC to sidecar, which talks to Maestro, which talks to UIAutomator or XCTest).
|
||||
8. Loop.
|
||||
|
||||
The cycle runs hundreds of times per minute. Every step produces one row in `trace.jsonl` and one screenshot.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Design principles
|
||||
---
|
||||
|
||||
# Design principles
|
||||
|
||||
## 1. The app owns introspection; the driver owns input
|
||||
|
||||
The in-app SDK knows the state: view hierarchy, coverage, logs, exceptions, custom extractors. Maestro causes the state to change through taps, swipes, typed text, and deep links. The Go runner decides what to do.
|
||||
|
||||
Splitting these responsibilities is what makes the system work across iOS and Android with one spec surface. Neither Maestro nor the SDK alone is sufficient.
|
||||
|
||||
- Maestro can read a coarse accessibility tree, but not the real `UIView` or `View` hierarchy, not coverage, not in-process logs.
|
||||
- The SDK can see everything inside the app, but cannot dispatch UI events the way the OS would. Touch injection through Maestro goes through XCTest or UIAutomator, which the OS treats as real input.
|
||||
|
||||
## 2. One TypeScript surface across platforms
|
||||
|
||||
Spec authors write against `state.ax`, `state.logs`, `state.snapshots`, and so on, regardless of iOS or Android. Platform differences (back button semantics, system alerts, coverage format) are absorbed in the Go runner and the SDKs.
|
||||
|
||||
Corollary: if a concept only exists on one platform, it does not belong in the spec API. It belongs behind a feature flag or an extractor.
|
||||
|
||||
## 3. The driver is an interface
|
||||
|
||||
Today `driver.Driver` has one production implementation (`maestro`) and a `mock` for tests. Tomorrow it might be Appium, direct XCTest, or UIAutomator. The runner never knows. This keeps the Maestro dependency contained. If we ever outgrow it, the blast radius is one package.
|
||||
|
||||
## 4. Hot loops bypass Maestro
|
||||
|
||||
Per-step introspection (hierarchy dump, coverage read, pause and resume) goes over a local Unix socket directly to the SDK. Only physical UI events go through Maestro's gRPC.
|
||||
|
||||
A 30-minute run is about 10,000 steps. Every step has at least one hierarchy dump and one coverage read. If those went through Maestro, the JVM sidecar would be the bottleneck. Instead the hot path is a 2 ms round-trip to an in-process Swift or Kotlin SDK.
|
||||
|
||||
## 5. Deterministic where it can be
|
||||
|
||||
A seeded PRNG drives action selection. Spec evaluation is pure given state and snapshots. The bundle hash and seed are recorded in `meta.json`.
|
||||
|
||||
uatu does not attempt byte-exact replay. Animation timing, keyboard popup timing, and system daemons are non-deterministic on mobile, and the cost of trying to suppress that is not worth the payoff. Same seed produces a similar trajectory, which is usually enough to reproduce the bug.
|
||||
|
||||
## 6. Fail honest
|
||||
|
||||
If coverage is not available (release build, instrumentation off), tell the user. Do not pretend exploration is guided when it is random. If the SDK is not linked, say so. If a property is unparseable, fail the run at startup, not step 1000.
|
||||
|
||||
The alternative, graceful degradation that silently weakens guarantees, is how testing tools lose trust.
|
||||
|
||||
## 7. Specs are authoritative; no hidden setup
|
||||
|
||||
There is exactly one authoring surface: the TypeScript spec. There is no separate YAML for login, no fixtures directory, no `setup.sh`. Login, onboarding, permission prompts, and teardown are all expressed as action generators or extractors, evaluated in the same loop as the rest of the spec.
|
||||
|
||||
This is intentional. A test harness with two authoring languages (YAML plus code, JSON plus code) splits concerns in a way that always drifts. Something works in one surface and not the other, and debugging requires holding both in your head.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Development
|
||||
---
|
||||
|
||||
# Development
|
||||
|
||||
- [Design principles](./design-principles.html)
|
||||
- [Architecture](./architecture.html)
|
||||
- v0.1.0 scope: [issue #4](https://github.com/priyanshujain/uatu/issues/4)
|
||||
|
||||
## Building the docs site locally
|
||||
|
||||
```
|
||||
make docs
|
||||
```
|
||||
|
||||
Outputs to `build/site/`. Requires [pandoc](https://pandoc.org/) on your PATH. Preview with:
|
||||
|
||||
```
|
||||
cd build/site && python3 -m http.server 8000
|
||||
```
|
||||
Reference in new issue
Block a user