# Review a flow

This page is for a human, not an agent. Every other page on this site has a companion tool call —
this one doesn't. Reviewing a flow and leaving comments happens in the dashboard
(`packages/dashboard`), alongside the publish/rollback and experiment actions a person can also
fire there (see [Why comments stay narrow](#why-comments-stay-narrow) below).

Sign in to the dashboard the same way you would for [Monitoring](/docs/monitoring/) — with your
Google account, or a bearer token for local/dev use. Then open **Previews** in the sidebar.

> **Rough today (2026-07-31):** `weir release` publishes only
> `config.json` plus `customAssets[]` (see [Concepts → Delivery model](/docs/concepts/#delivery-model))
> — no HTML artifact is uploaded on a normal release. Previewing a *live, released* v4 flow still
> works: the server renders the preview on demand from the stored `config.json`, and the page marks
> it with a visible **"Approximation preview"** banner. That render uses the same webview-era HTML
> renderer the old bundles used, which has no way to draw a `custom` screen's real native
> content — it shows as an empty card in this preview. Native rendering (WeirUI / the RN SDK) is the
> real source of truth for how a `custom` screen actually looks; a native, screenshot-driven
> replacement (real device screenshots uploaded by `weir walk`) is planned per PLAN.md M6 and will
> replace this approximation once it ships. `weir release --draft` is unaffected either way (its own
> `/publish-draft` route still uploads a real HTML build).

## How the preview is authorized

The dashboard opens the preview in an iframe. A browser cannot attach a custom header to an iframe
load. No browser lets an iframe `src` carry your sign-in token. The dashboard works around this
with a short-lived **preview ticket**, never with your sign-in token.

A preview ticket is scoped to one flow and one version. It expires 5 minutes after it is minted.
Switching to a different flow or version needs a fresh ticket. The dashboard mints one
automatically each time you switch — you never see this happen.

**Never put your long-lived app token in a preview URL.** A token in a URL ends up in browser
history. It ends up in anything a reviewer copies and pastes. It can end up in a Referer header. A
ticket cannot cause any of that: it expires in minutes, and it only unlocks the one flow and
version it was minted for.

### Calling the preview API directly

Most integrations never need this section — the dashboard mints and uses tickets for you. Use this
only from a script, a CLI, or your own tooling.

`GET /preview/:flowId` accepts two different credentials:

- **A bearer token.** The same per-app token you use for every other API call. Use this for
  programmatic or CLI access. Never expose this token in a URL a browser will load.
- **A ticket**, passed as `?ticket=...`. Use this only when a browser loads the preview, exactly as
  the dashboard's iframe does.

Mint a ticket by calling `POST /preview-tickets` with your bearer token:

```
curl -X POST https://api.agentwallie.com/preview-tickets \
  -H "Authorization: Bearer $WEIR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"flowId": "onboarding"}'
```

The response is `{ "ticket": "...", "expiresAt": 1784999999999 }`. `expiresAt` is a Unix
millisecond timestamp, about 5 minutes after the call. Pass the ticket back on the preview URL:

```
GET /preview/onboarding?ticket=<ticket>
```

Omit `version` in the `POST /preview-tickets` body to mint a ticket for the flow's current latest
published version. Pass a specific `version` to pin the ticket to that version instead. A ticket's
version cannot be changed after it is minted — a `?version=` query parameter on the preview URL has
no effect once a ticket is present, so mint a new ticket for a different version rather than editing
the URL.

## The preview is the real bundle — or a labeled approximation

A draft preview (`weir release --draft`) renders the exact HTML build that was uploaded — not a
re-render, not a mock. Approving a draft preview means approving the artifact that ships.

A released v4 flow's preview is different: see the callout above. It carries an
"Approximation preview" banner and a stock screen's exact look. A `custom` screen's real native
content is not in it.

## Pick a flow, a version, and an arm

Three pickers sit across the top of the page.

- **Flow.** Every flow this app has published.
- **Version.** Every published version of that flow, newest first.
- **Arm.** Only shown when the flow has a registered experiment. Pick the arm you want to see.

Picking an arm does not fake the result. The dashboard finds a real user id that genuinely buckets
into that arm under the same assignment function every device uses, and previews as that user
would see it. If no id buckets into the arm you picked, the dashboard says so plainly instead of
guessing.

If the flow has no registered experiment, the arm picker is unavailable and the preview shows the
flow unbucketed.

## Walk the flow

The preview runs in a phone-sized frame (390×844, the viewport flows are authored against) on the
left. Step through it the way a user would.

### Native actions are simulated

A purchase, a restore, or a system permission prompt cannot happen for real on the web. When the
flow calls one of these, a modal appears instead of the real system UI.

- **Purchase.** Choose "Complete purchase," "Cancel," or "Fail" to see how the flow handles each
  outcome.
- **Restore.** Choose a restored result or "nothing to restore."
- **Permission.** Choose "Allow" or "Don't allow."

Every modal states plainly that the outcome is simulated. **A simulated purchase is not a real
transaction. It never touches StoreKit, and no money moves.** The same applies to a permission
result — the modal's answer never asks the real OS for anything.

Prices shown on a paywall screen in preview are sample values, clearly labeled as sample. A real
price resolves from StoreKit on the device only; preview never bakes a real price into a bundle.

A haptic call never shows a modal — it fires constantly in a real flow, so the dashboard shows a
small indicator instead and moves on.

Completing or dismissing the flow also shows an acknowledgment, with the reason and any variables
the flow collected.

## Comment on a screen or an element

Leave a comment three ways:

- **On the whole flow.** Post from the composer with no screen selected.
- **On the current screen.** Post while that screen is showing — the comment is scoped to it.
- **On one element, Figma style.** Turn on "Comment on element," click the element, then write the
  comment. A pin marks where you clicked.

### Comments survive iteration

An element-anchored comment does not depend on the element staying in exactly the same place. The
dashboard tries several ways to find it again, from most to least specific, and falls back
automatically. A comment is never dropped because an element moved, and it is never silently
pinned to the wrong element.

If none of those ways finds a match, the comment shows as **unanchored** in the panel, with its
original text and the screen it came from. An unanchored comment still counts — it means "this
element changed or moved, go find it," not "this feedback is gone."

### Comments carry across versions

Reviewing version 3 also shows a still-open comment left on version 2, labeled with that origin
version. Feedback nobody addressed yet does not disappear just because a new version shipped.

## Resolve a comment

Every comment has a resolve toggle. Mark it resolved once it's addressed; reopen it if it wasn't.

## Why comments stay narrow

[Monitoring](/docs/monitoring/#what-the-dashboard-can-change) describes the dashboard's real write
surface: publish/rollback and the experiment lifecycle (Start/Stop/Decide). Flow *authoring* —
composing or editing screen JSON — stays agent-first and repository-owned.

A comment is not any of those. It is review metadata only: a note attached to a screen or an
element. A comment cannot edit a flow's screens or branches, change an experiment's outcome or
decision rule, or alter a published release artifact. The dashboard's operational buttons
(publish/rollback, Start/Stop/Decide) are the write paths for those, and they run the same gated
commands the agent surface does. Commenting adds no new write path to a flow's content, an
experiment's verdict, or a release's artifact — it is review feedback, nothing more.

## Tenancy

Your sign-in scopes you to one app. You cannot preview another app's flows, and you cannot read or
write another app's comments — asking for either fails the same way asking for data that does not
exist would.
