Sandbox preview & branch verification
Permalink to Sandbox preview & branch verificationThis is the single step an unsupervised agent must not skip: proving the flow renders and every branch resolves before shipping it. Two tools do it, both fully local, both needing no account, no server, and no tokens.
Preview it in a browser: weir dev
Permalink to Preview it in a browser: weir dev
weir dev --specPath weir/flows/onboarding.json --serveweir_dev serves the spec in-browser with mocked native behavior for every stock screen type. The mock provides fake permission dialogs, a mock localized product catalog (including an intro-offer product and a non-USD tier), a safe-area/notch overlay, and keyboard-inset simulation — so a browser shows what the device shows for stock screens. (A custom screen has no browser preview — see Quickstart.) With --serve it prints an http:// URL; without it, a file:// path.
Force a native-call outcome with a persona
Permalink to Force a native-call outcome with a personaA persona pins the outcome of each native call, so you can look at one specific path:
--persona "notifications=deny,purchase=cancel"The persona string is comma-separated key=value pairs. The keys are the native-call decisions:
| Key | Values | Controls | ||
|---|---|---|---|---|
notifications | grant | deny | the system notification-permission prompt result | |
tracking | grant | deny | the ATT prompt result (iOS; unavailable on Android) | |
health, camera, location | grant | deny | the matching permission prompt result | |
purchase | success | cancel | fail | how a paywall purchase.start resolves |
variant | <experimentId>:<variantId> | force this preview onto one arm of an experiment declared in the flow's experiments[] | ||
userId | any string | pin the bucketing key an experiment's arm assignment hashes — normally left unset |
Any key not in this table (a typo'd permission type, for example) is rejected with the list of valid keys — a persona string is validated, not silently ignored. Any decision you leave out uses the mock's default. A persona changes only native-call outcomes — it does not answer select/input screens for you.
Force a specific experiment arm
Permalink to Force a specific experiment armA screen with a variant gate only renders for users assigned to one of its showFor arms (see Reference → Experiments and the variant gate). Both weir_dev and weir_walk pick the default userId ("eval-user") unless told otherwise, so an ungated preview/walk always resolves to whichever arm that userId happens to bucket into — usually control. To see a specific arm, force it directly:
weir dev --specPath weir/flows/onboarding.json --persona "variant=paywall_copy_v1:treatment" --serve
weir walk --specPath weir/flows/onboarding.json --variant paywall_copy_v1:treatment --outDir .eval-out/walkweir_walk also accepts variant as a top-level field (applied to every persona at once) instead of repeating it inside each persona string. Either form searches for a userId that actually buckets into the requested arm and pins it — it does not change the flow spec's gate. An unknown experiment or variant id fails with the full list of ids the flow spec actually declares.
Walk every branch headlessly: weir walk
Permalink to Walk every branch headlessly: weir walk
weir walk --specPath weir/flows/onboarding.json --outDir .eval-out/walkweir_walk drives the same mocked preview with Playwright for one or more personas, screenshots every screen on each persona's path (including the fake dialogs), and writes one walk-report.json per persona. This is how you confirm a specific path resolves — not just that the tool exited 0.
Drive typed answers to reach a conditional path
Permalink to Drive typed answers to reach a conditional pathTo reach the branch a specific answer triggers, drive the select/input answers, not just the native-call outcomes. Pass answers as repeated --answerMaps JSON values, one map per persona:
--answerMaps '{"goal":"maintain"}' --answerMaps '{"goal":"cut"}'The first map takes the maintainNote branch; the second falls through to weightInput. Run both to prove both arms of goalSelect.next render. Remember array fields need a repeated flag — two --answerMaps flags, not one flag with two values.
Read the output — do not just check the exit code
Permalink to Read the output — do not just check the exit codewalk-report.json fields
Permalink to walk-report.json fields
Each report records what the persona actually did:
| Field | Meaning |
|---|---|
persona / personaConfig | the persona's label and its full parsed config (permissions, purchase outcome, answers, forced variant) |
path | the ordered list of screen ids this persona visited |
screens | one entry per visited screen: index, screen id, type, and the screenshot file path |
events | every native-call event the walk made (the raw envelopes, not just complete/dismiss) |
appliedAnswers | the subset of your answerMaps that were actually submitted through a real DOM control |
variables | the value of every flow variable when the walk ended |
branchDecisions | each next.branches evaluation and the target it chose |
reachedComplete | whether the walk reached complete() |
Check three things: the path includes the screens that answer's branch should reach, the branchDecisions show the condition you expect firing, and variables has the interpolated variables set to real values (not blank).
Inspect the screenshots
Permalink to Inspect the screenshotsThe screenshots land under the --outDir you passed, one PNG per screen per persona (.eval-out/walk/**/*.png). Open them. A coding agent should read the PNGs directly as images — this is the step that catches a screen that validates but renders wrong (an undefined variable showing blank, a branch that never gets visited, copy overflowing the safe area). The CLI prints a placeholder line for image blocks and the file path; use the path to open the real PNG.
Troubleshooting the walk
Permalink to Troubleshooting the walk- "My branch never gets visited." The persona's
--answerMapsdid not drive the answer the branch needs. CheckbranchDecisionsin the report: it shows which condition evaluated and why the other target won. Confirm the answer key matches the screen'svariableid. - "A variable is undefined downstream." A screen interpolates
{{x}}, but no screen on this persona'spathwrotex. Either the writing screen is on a different branch, orxis onlycomputesd by a loader with nodefault(it renders blank in preview). Give it adefault, or make sure the persona's path reaches the screen that sets it. - The walk exits 0 but a screen looks wrong. Exit 0 means the walk completed, not that the screens are correct. Reading the screenshots is the only check for that.
Once every branch renders and walk-report.json shows the paths and variables you expect, ship it — Quickstart (weir_release, which runs the full weir_conform gate before it publishes).