Weir docsv4
View as Markdown

Sandbox preview & branch verification

Permalink to Sandbox preview & branch verification

This 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 --serve

weir_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 persona

A 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:

KeyValuesControls
notificationsgrantdenythe system notification-permission prompt result
trackinggrantdenythe ATT prompt result (iOS; unavailable on Android)
health, camera, locationgrantdenythe matching permission prompt result
purchasesuccesscancelfailhow a paywall purchase.start resolves
variant<experimentId>:<variantId>force this preview onto one arm of an experiment declared in the flow's experiments[]
userIdany stringpin 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 arm

A 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/walk

weir_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/walk

weir_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 path

To 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 code

walk-report.json fields

Permalink to walk-report.json fields

Each report records what the persona actually did:

FieldMeaning
persona / personaConfigthe persona's label and its full parsed config (permissions, purchase outcome, answers, forced variant)
paththe ordered list of screen ids this persona visited
screensone entry per visited screen: index, screen id, type, and the screenshot file path
eventsevery native-call event the walk made (the raw envelopes, not just complete/dismiss)
appliedAnswersthe subset of your answerMaps that were actually submitted through a real DOM control
variablesthe value of every flow variable when the walk ended
branchDecisionseach next.branches evaluation and the target it chose
reachedCompletewhether 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 screenshots

The 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 --answerMaps did not drive the answer the branch needs. Check branchDecisions in the report: it shows which condition evaluated and why the other target won. Confirm the answer key matches the screen's variable id.
  • "A variable is undefined downstream." A screen interpolates {{x}}, but no screen on this persona's path wrote x. Either the writing screen is on a different branch, or x is only computesd by a loader with no default (it renders blank in preview). Give it a default, 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).

Generated from this repo's source at build time — packages/mcp/src/tools/index.ts and packages/spec/src/events.ts are the ground truth for the tool and event tables above. llms-full.txt