Why Squiggle

Your app already knows what it's doing. Nothing was asking.

A React Native app renders a tree, runs effects, fires requests, changes routes and reacts to touches — and every tool that watches it throws most of that away and keeps a thin slice. Squiggle keeps the whole thing as one graph, and everything else is a view over it.

tools/list

75
agent tools, over one warm session

squiggle --schema-json

63
CLI leaves, from one typed tree

@squiggle/perf-insights

12
perf detectors, each carrying its evidence

SRGNodeKind

11
kinds of node in the runtime graph

01 — The model

One graph, not four exports

The Squiggle Runtime Graph is a live model of the running app: components and their state, routes, network, storage, gestures and the causal edges between them — which interaction caused which render, which render caused which frame drop.

It matters because the alternative is four products with four schemas that cannot answer a question spanning two of them. When testing, profiling and analytics read the same graph, “which release made checkout slow, and for whom” is a query rather than a cross-referencing exercise.

  • A UI test runner that sees pixels and coordinatesFlows that name what a control means, resolved against the live graph
  • A profiler with its own SDK and its own session idsFindings written into the same store the flows already wrote to
  • An analytics SDK you tag by hand, screen by screenScreens, actions and identities the graph was already recording
  • An agent that taps, screenshots, and guesses againOne compiled intent program, woken only at real decisions

02 — Testing

Flows that describe intent, not coordinates

A flow names what a control means. The engine resolves that against the live graph and the accessibility tree, so it survives the layout change that breaks a coordinate-based test — and when it genuinely cannot resolve something, it says which selector failed and what it saw instead.
flows/login.yaml
flow: log inenv:  USER: guest@acme.comsteps:  - type: { into: { testID: email-input }, value: '${USER}' }  - type: { into: { testID: password-input }, value: hunter2 }  - tap: { testID: sign-in, label: submit credentials }  - waitFor: { route: checklist, timeout: 8000 }  - assert:      visible: { text: '${USER}' }
  • Record instead of writing. Drive the app by hand, then turn the recording into a draft flow — including an honest list of the taps nothing durable could identify.
  • Replay is deterministic. A run is captured to a file with its inputs and its responses, so the failure you saw is the failure a colleague replays.
  • A pass means it happened. Assertions re-observe the UI, because a dispatched tap is not the same thing as a tap that had an effect.

03 — Performance

Every insight carries its evidence

Squiggle records render storms, memo misses, long tasks, duplicate and waterfall requests, slow queries, heap growth and startup regressions — and each finding names the component, request or query behind it and links to the session and event it was derived from. You can open the thing being complained about.
  • Baselines, not vibes. Snapshot a branch, diff it, and fail the build when a number moves the wrong way.
  • Down to the frame. Flamegraphs, heap diffs, a network waterfall and a scrub-synced timeline, all against the same recorded session.
  • Native crashes too. Scanned and symbolicated, with the JS frames resolved rather than left as addresses.
Terminal
squiggle perf snapshot --name main# … make your change, run the flow again …squiggle perf diff main --assert

04 — Product

Analytics from the runtime, not from a second SDK

The events are already there — the graph knows the screens, the actions and the identities. So product analytics is a reader over the same store, which means no tagging sprint, no divergence between what QA drives and what analytics counts.
  • Funnels that know the UI

    A step is a screen or an action the graph already records, so a funnel keeps working when the route changes — and you can evaluate it against a recorded session instead of waiting a week for traffic.

  • Friction, not just events

    Rage taps, dead zones and confusion loops are detected from the touch tracks, then rolled up across sessions and pointed at the exact control by testID, role or label.

  • Retention and adoption

    Cohort retention, feature affinity and release annotations come from the same event stream the flows and the profiler write to. One store, not a second SDK.

05 — Agents

The model compiles a plan. The runtime executes it.

Most agent tooling makes the model an interpreter: look, decide, tap, look again — one round trip per action, and a wrong guess halfway through costs the whole run.

Squiggle makes it a compiler. The model emits one goal-conditioned intent program with branches, escalation points and prerequisites; a deterministic engine runs it and wakes the model only when something genuinely needs a decision. Turns scale with the number of escalations, not the number of taps.

  • Claims you can check

    Every statement the agent makes carries a session and event coordinate, so a citation is mechanically verifiable rather than a summary you have to trust.

  • A deny rail below the model

    Destructive actions are refused at delivery time, not by prompt. Approval is explicit, scoped, single-use and logged — and an app can declare which of its controls are destructive rather than leaving it to inference.

Getting in

It’s a private beta

Squiggle is used daily on a real React Native app, which is why the parts above are in the present tense. It is not on npm yet. If you build React Native on a Mac and any of this sounds like your week, come and break it.