Run settings
For AI agents: this page is Markdown at https://squiggle.sh/docs/run/settings.md. Every docs page is listed at https://squiggle.sh/llms.txt, and all of them are one file at https://squiggle.sh/llms-full.txt.
Flags change how one run delivers steps and how long it waits. A flow that always needs a setting declares it in its own config block, so nobody has to remember the flag.
Flags
| Flag | What it does |
|---|---|
--direct-scroll | Makes mode: direct the default for scrollUntilVisible. Much faster; skips momentum, paging and snapping. |
--auto-scroll | Scrolls an off-screen control into view instead of failing the step. |
--no-fast-settle | Waits the full fixed time before each step, instead of moving on when the app stops moving. |
--no-in-process | Sends taps, text and swipes as real touches from outside the app. Slower; use it when the touch handling itself is what you test. |
--no-animations | Turns iOS animations off in the app, for runs that must be identical. |
--capture-net | Lets the flow wait for the network and stub requests. |
--strict-assert | Fails the run when a step couldn’t be performed at all. |
--chaos <profile> | Runs the flow on a bad network. See below. |
--stubs <run> | Answers the network from an earlier run. See below. |
--matrix | Runs the flow once in each simulator setup and lists the layout problems each one adds. See below. |
--ax-only | Finds every control through the accessibility tree only, the way VoiceOver reaches it. See below. |
--settle-debug | Prints every wait under the step it belongs to. When a wait runs long, it names what was still animating: the component and its file:line. |
--headless | Runs without a simulator, inside the app’s JavaScript. Taps by position and gestures are reported as unsupported. |
--ignore-flow-config | Ignores the flow’s own config block. |
In Studio, the same settings are in the Flows screen’s Replay options: Direct scroll, Auto-scroll, Fast settle, In-process delivery, Disable animations and Capture network.
Settings a flow declares
Some settings decide whether a flow tests the right thing. A flow that tests infinite scroll needs real drags; one that checks a screen’s own animation timing needs the fixed waits. Declare them in the flow:
flow: product list pagingconfig: scrollMode: gesture # gesture or direct fastSettle: false # use the fixed waits autoScroll: true # scroll an off-screen control into view strictAssert: true # a step that couldn't run fails the run stepDelayFloorMs: 150 # pause after each step, ms (0–2000, default 80) transitionSettleMs: 900 # longest wait before each step, ms (0–10000, default 600) chaos: slow # bad-network profile: slow, flaky, truncated, offline-midstepsteps: …A misspelt key, or a value out of range, stops the flow from loading and lists the valid ones.
When a flag and the flow disagree
For one flow, a flag you type wins: a one-off run is what flags are for. For a folder of flows, the flow wins, because one flag can’t suit every flow in a suite. Try both:
flow: offscreen refusalconfig: autoScroll: falsesteps: …squiggle flow offscreen-refusalAuto-scroll is off
Nobody said otherwise, and the flow and the default agree. Nothing to report.
Either way, the run prints which value it used before the first step:
squiggle flows .squiggle/flows --direct-scroll! flow config: scrollMode=gesture (was direct) — declared by the flow (a batch run never overrides it)A step’s own mode: beats both. Only flags you type count: a default never overrides the flow.
Run a flow on a bad network
--chaos degrades every request the app makes during the run, so you reach the loading, error and retry states without breaking your own connection:
| Profile | What the app gets |
|---|---|
slow | Every response, 2 seconds late. |
flaky | A 500 for the 1st, 6th, 11th… request. The same requests fail on every run. |
truncated | Every response body cut off halfway. |
offline-midstep | Everything works until the middle step (the earlier of the two, for an even number of steps), then every request fails as if the device went offline. |
squiggle flow orders --chaos offline-midstepA flow whose subject is an error state can declare its profile instead: config: { chaos: flaky }. Then --chaos none runs it on a working network.
The run turns on network capture, which happens when the app launches. If the app is already running without it, the run fails before its first step and says so; run it again with --fresh. Requests to Metro and to Squiggle itself are never degraded.
Run a flow without the network
--stubs answers every request the app makes from an earlier run of the flow, so it runs with no server and no test data to maintain. First run it once against the network with --capture-net, then pass that run’s id, the name of its log in .squiggle/runs, or latest for the run just before:
squiggle flow orders --capture-netsquiggle flow orders --stubs latestsquiggle flow orders --stubs mve7k1uh-2-4zs6Each endpoint gets the status it returned most often, its median response time, and a body of the same shape: the same fields, types and list lengths, with placeholder values (title 1, title 2, ids counting up from 1). The run lists every stub before its first step.
A request that never got a response is not stubbed. An endpoint whose shape wasn’t recorded, because it isn’t JSON or the run had network capture off, is answered with its status and an empty body. Stubs combine with --chaos: a stubbed request is answered, and the profile degrades the rest.
Check every screen in each setup
--matrix runs the flow three times, each from a fresh launch: once in the setup the simulator is in, once in the other appearance (light or dark), and once at the largest text size. On the first visit to each screen it checks for controls that overlap, get cut off or end up off-screen, and reports only what a setup adds to the first run, since a problem in every setup is about the screen, not the setup. The simulator’s settings are put back afterwards, even when a run fails or you stop it with Ctrl-C.
squiggle flow settings --matrixThe run lists each setup as as found (…), light or dark, and largest text, with each problem it adds and its source line. It exits 1 when a setup adds any. With --json it prints { cells: [{ cell, ran, screens, findings, added }] }, where each entry in added has a route, check, handle and detail.
--matrix needs an iOS simulator. On a Mac app it stops with E_MATRIX_PLATFORM and exit code 2.
Repair what a setup breaks
When a screen’s spacing is declared with defineTunable, --repair searches it for values that work in every setup. Name the tunables it may move:
squiggle flow layout-lab --matrix --repair layout.gap,layout.padding,layout.minHeight ✗ largest text: 1 screen(s) audited · 1 finding(s) · 1 not in the first cell layout-lab · noClipping · 'layout-sign-out' is cut off by the viewport edge — 67% of it is on screen ✗ before · as found (dark, large): loss 16.7: layout-edit-profile hitTarget 3.3 pt; … ✗ before · light: loss 16.7: layout-edit-profile hitTarget 3.3 pt; … ✗ before · largest text: loss 33.2: layout-sign-out noClipping 33.2 pt ✔ search · grid: layout.gap 0, layout.padding 0, layout.minHeight 44: no finding in any cell after 23 probe(s) ✔ after · as found (dark, large): loss 0 ✔ after · light: loss 0 ✔ after · largest text: loss 0 ✔ repaired: layout.gap 2, layout.padding 2, layout.minHeight 44: no finding in any of 3 cells, 32 probe(s) .squiggle/tune/mvglnkg2-repair-8t5h.json · propose_fix { tuning: 'mvglnkg2-repair-8t5h' } turns it into an editAfter the matrix, it runs the flow once to reach its last screen and measures that screen in each setup, relaunching the app for each one. Each problem is scored by how far it is from fixed, in points: how far a control reaches past the screen’s edge, how deep two controls overlap, and how far a control falls short of 44 pt. The score is 0 only when nothing is wrong, and a control that was on the screen at the start must still be on it. It sets the tunables in the running app and changes nothing else.
It accepts values only when the score is 0 in every setup, including the one the simulator was in, so a fix for the largest text can’t break the default. Then it moves each value onto the nearest of the tunable’s tokens that still works, and flags a value it couldn’t move. It stops after 40 measurements for each setup that had a problem, and exits 1 when nothing it tried clears every setup. A control a scroll view can bring back doesn’t count as lost. --repair-search picks the search: grid (the default) tries the values in order from the smallest; random:<seed> and descent are there to check it against. --repair-control <seeds> runs that check: it counts what the grid, random search over that many seeds and descent each spend to reach a clean layout, on one set of measurements. On the Layout Lab the grid needed 13 measurements, random search 40 on average over 100 seeds, and descent never got there. The control takes thousands of measurements, so it keeps them in .squiggle/tune/ and a stopped run picks up where it left off. The result is saved under .squiggle/tune/, and an agent turns it into edits of each tunable’s default with propose_fix { tuning }.
Run a flow the way VoiceOver reaches it
--ax-only finds the control for every tap, type, swipe and scroll through the accessibility tree alone. A step that passes normally and fails here is a control a screen-reader user can’t reach.
squiggle flow checkout --ax-only- Only
testID,label,textandrolecan be matched, andtextmatches only the accessibility label, which is what VoiceOver reads. A step whose selector usescomponent,callSite,source,within,nth,pointorrouteis reported as unsupported, and fails the run only with--strict-assert. - Two elements that VoiceOver announces the same way fail the step instead of one being picked.
- There is no healing and no navigation fallback: a selector either reaches the control or the step fails.
- Assertions still read the app’s own UI tree, so only how controls are reached changes.
- It needs a simulator; with
--headlessevery action is unsupported.
A step that fails here fails with ax_unreachable. Give the control an accessibilityLabel or a testID that names it uniquely.
Name where a tap lands
A tap that opens another screen can name it with expectRoute. Once that screen appears, the next step starts without waiting out the rest of the transition. Naming the wrong screen only costs the wait. Recorded flows fill it in for you.
- tap: { text: Checkout, expectRoute: OrderConfirmation }Every key is in the Flow syntax reference.