Documentation
Squiggle turns a running React Native app into one queryable model of its own behaviour, and every feature here is a view over that one model. Written for the beta — where something is unfinished it says so, rather than describing the plan in the present tense.
The idea, in one paragraph
A React Native app renders a tree, runs effects, fires requests, changes routes and reacts to touches. Every tool that watches it throws most of that away and keeps a thin slice: a test runner keeps coordinates, an analytics SDK keeps events you remembered to send, a profiler keeps samples. Squiggle keeps the whole thing as one graph — the Squiggle Runtime Graph — and streams it over a binary protocol to whoever is listening. Testing, replay, profiling, product analytics and agents stop being four products and become four queries.
// index.js — the FIRST line, before anything else importsimport '@squiggle/react-native';That is the entire required setup. No provider to wrap your tree in, no config file, nothing to register, and nothing to remember to turn off — the instrumentation is dormant until a consumer attaches, so an app with nobody watching pays approximately nothing.
What that buys you
- Flows that describe intent
- A selector says which control, not which pixel. When the button moves, the selector re-resolves against the graph and the accessibility tree instead of going red — and the engine writes the repair back into your YAML so you can review it in a diff.
- Runs that are fast because of the graph
- Waits are gated on the app actually being idle rather than on a sleep, and taps, typing and scrolling can be delivered inside the process. One scroll step went from 2.3 s to 17 ms.
- Performance findings with evidence
- Twelve detectors over the recorded session name the component, the span, or the request — and every finding carries the row ids it was derived from, so “why do you think that” is a query rather than an argument.
- Product analytics on the same stream
- Funnels, retention, feature affinity and friction come out of the events already being recorded — the question “which release made checkout slow, and for whom” spans testing, performance and analytics, and here it is one query.
- An agent runtime that is cheap to run
- The model compiles one program and the deterministic runtime executes it, waking the model only at real decision points. Turns become
O(escalations)rather thanO(actions).
The two halves, and how they fail
Squiggle has two halves and they fail differently. The SDK lives in your app and builds the graph — if it is missing, everything is blind, and that failure is loud. The host lives on your Mac and drives the simulator — if the app was launched without it, steps still pass, because a tap acknowledges on delivery rather than on the app reacting.
The corollary for writing flows: end a journey with an assertion about the resulting UI, not with the action itself. An assert: { visible: … } re-observes the app; the tap that preceded it only promises it was delivered.
One store per project
Everything durable lands in a single .squiggle/ directory at your project root, shared by all three front doors:
flows/ your flow YAML — SOURCE, commit thisruns/ one NDJSON artifact per run (per-step timings)recordings/ .sqrec session recordingsperf/ perf.db — the queryable projection of everythinghistory/ every acting command, from any surfaceagent/ route graph, flow library, learned interruptsbench/ gate/ library/ screenshots/What to commit
flows/— commit it- Your flow YAML is source. It is also what self-healing rewrites, so the repairs arrive as reviewable diffs rather than as invisible runtime behaviour.
perf/,runs/,recordings/,bench/— do not- Machine-local evidence about one machine’s runs. Gitignored by default; a baseline you want to gate against belongs in CI artifacts, not in the repository.
config.jsonc— commit it- The project’s settings and its named profiles, so every surface resolves the same bundle id, port and flow directory. Funnel definitions are a different file —
perf/config.json, beside the store they are evaluated against — which is why they travel with the evidence rather than with the repo.
How each surface finds it
The CLI and the MCP server walk up from the working directory, so running from a subdirectory finds the same store. The desktop app holds an explicitly chosen active project instead, because a launched Mac app has no meaningful working directory to walk from.
Every durable record states which surface produced it — cli, desktop or mcp. One store, three front doors, and a shared log that stays honest about who did what.
Scope, honestly
Squiggle is iOS simulator first, on an Apple Silicon Mac running macOS 15+ with Xcode. Android and web ship as compiling stubs behind the same API and are implemented after iOS. A handful of capabilities are marked unsupported rather than faked — Fabric commit/layout timing, image decode, and Reanimated worklet timing have no public seam on RN 0.81 without patching core — and each ships a runtime probe so its collector registers itself the moment that seam appears.
Where to start
Everybody starts at Getting started — the SDK has to be in the app build before any surface can see anything. After that, three routes through the rest:
- You want tests that stop breaking
- Writing flows, then Speed & delivery once the suite is longer than one flow, then Record & replay for the reproduction that comes free with a failure.
- You are chasing something slow
- Performance for the detectors and the evidence chain, and Runtime Inspector for the picture of what is re-rendering and why.
- You want an agent driving the app
- Agents & MCP — and run
squiggle upbefore it does, or the agent will get awaitFortimeout instead of a missing capability.