# Sets: capture and publish (https://docs.vitrinka.ai/dev/cli/sets)



## Start a set [#init]

Creates a `.vitrinka` set config in the repository so pushes know where they are going.

```bash
vitrinka board init
```

## Capture a shot [#capture]

Captures a screenshot straight into the set and uploads it in the background — only the new file, which joins the set's board within seconds. It drives an iOS simulator, an Android emulator or macOS, or adopts an image you already have. A web shot is always adopted: take it with your own browser tooling — the browser MCP, a Playwright script — and hand the file over with `--file`. `capture safari --url <page>` drives Mobile Safari on the iOS Simulator through Appium instead, in one of six `--state`s (`default`, `keyboard`, `scrolled`, `scroll-end`, `landscape`, `overscroll`) — the keyboard really opens, the page really scrolls — and the measured safe-area insets travel beside the shot.

```bash
vitrinka board capture web \
  --file checkout.png \
  --viewport 1440x900 \
  --route /checkout \
  --note "discount applies twice" \
  --url https://localhost:3000/checkout
```

<Callout type="warn">
  `--note` and `--route` are required: a shot with neither is a picture nobody can place.
</Callout>

## Capture flags [#capture-flags]

| Flag         | What it does                                                                                                                                                 |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--route`    | **Required.** The app route the shot was taken on.                                                                                                           |
| `--note`     | **Required.** The caption.                                                                                                                                   |
| `--title`    | Human title — also names the file.                                                                                                                           |
| `--label`    | A stable shot label. Journey wiring targets it.                                                                                                              |
| `--next`     | Journey branch: the target shot's label. Repeatable.                                                                                                         |
| `--action`   | What was done on this screen; after `--next`, that branch's wire label.                                                                                      |
| `--target`   | `&#123;x,y,w,h&#125;` JSON bbox of the clicked element — follows a `--next`.                                                                                 |
| `--open`     | A deep link to open before capturing — simulators, emulators and macOS; never with `--file`.                                                                 |
| `--settle`   | Seconds to wait before capturing. Default 4 with `--open`, otherwise 0.                                                                                      |
| `--viewport` | `WxH` or `WxH@scale`, e.g. `393x852@3`. For a web shot, the page's CSS viewport.                                                                             |
| `--hidpi`    | Web: hold the file to 2× when `--viewport` is known. Default: the repository's `capture.hidpi`, on; `--hidpi=false` excuses this one shot — [below](#hidpi). |
| `--device`   | Device name.                                                                                                                                                 |
| `--udid`     | iOS simulator UDID.                                                                                                                                          |
| `--region`   | macOS capture region, `x,y,w,h`.                                                                                                                             |
| `--file`     | Adopt an existing image instead of capturing one.                                                                                                            |
| `--hq`       | Full-fidelity asset: no lossy re-encode, no resize.                                                                                                          |
| `--boxes`    | Runner text map in source-image pixels (default: the adopted image's `.boxes.json`) — [review toolkit](/qa/review-toolkit#boxes).                            |
| `--src`      | The implementing source file. Repeatable or comma-separated.                                                                                                 |
| `--state`    | Notable app or auth state.                                                                                                                                   |
| `--url`      | Provenance URL — beats `--open` in the manifest.                                                                                                             |
| `--root`     | Capture root. Default `.vitrinka/screenshots`.                                                                                                               |

## Web captures at 2× [#hidpi]

Web shots are 2× device scale — Playwright's `deviceScaleFactor: 2` — so a board read on a retina screen stays sharp. With `--viewport WxH` given, `capture web` refuses a file narrower than twice that width with `quality_gate`, and the reply names the recapture and both ways out; without `--viewport` the scale cannot be judged and the shot passes. The rule belongs to the repository: `capture.hidpi` in the committed `vitrinka.config.json`, on unless set. Simulator, emulator and macOS captures are already native resolution and are never checked or downscaled.

```bash
vitrinka config hidpi        # show the repo's setting
vitrinka config hidpi off    # let 1× web files through for everyone in this repo
```

## Push the set [#push]

Syncs the capture root with the set, file by file: only what the deployment does not already hold is uploaded. A push only adds — shots the set holds but the folder lacks (a fresh worktree, a cleaned folder) are kept, and the push says how many. Idempotent — re-running sends nothing new. A background upload that failed, while offline or refused, leaves its diagnosis and fix in the capture root; the next capture prints it once, and the next push sends only what is missing. Artifact sets (a `doc.json`) still go as one archive, and so does every push to a deployment without the per-file door — which replaces the whole set with the folder.

To delete shots, remove them locally and push with `--prune`: the set then mirrors the folder. When the prune would delete shots that board cards still show, the deployment refuses it and names `board pull`; `--confirm` deletes them anyway.

A push to an artifact someone deleted is refused (`artifact_deleted`) — deleting it was a decision, and re-running a push must not undo it. The refusal links the artifact's page, where **Restore** brings it back; or push with `--revive` to publish it again on the same page. After its 30 days in the trash it is purged, and the key is free again.

```bash
vitrinka board push --title "Checkout — pass 3"
```

| Flag          | What it does                                                                                                     |
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
| `--root`      | Capture root. Default `.vitrinka/screenshots`.                                                                   |
| `--title`     | Human title, re-sent on every push.                                                                              |
| `--source`    | Cross-link a source set: `project/branchSlug/key`.                                                               |
| `--no-fix`    | Skip the doc.json preflight autofix (JSON damage; a legacy `{meta, blocks}` file rewritten as the doc element).  |
| `--no-render` | Skip the server's post-publish smoke render and its `render.png` download.                                       |
| `--no-design` | Skip sending the repo's `DESIGN.md` with an artifact push.                                                       |
| `--prune`     | Mirror the folder: delete the set's files it no longer holds.                                                    |
| `--revive`    | Publish onto an artifact that was deleted: its page comes back, same address, instead of the push being refused. |

## Pull the set [#pull]

A capture root lives in one worktree; the set outlives it. `pull` restores the set's files into the root, so a fresh worktree continues the set instead of starting from an empty folder. Files already in the root are never overwritten, and `manifest.json` is merged — the set's shots first, then the folder's own. Without a descriptor in the root, `--project`, `--branch` and `--key` name the set, as `board init` does.

```bash
vitrinka board pull --branch feature/checkout --key 2026-09-26
```

## Name and frame it [#meta]

`meta` sets the journey header — kicker, title, accent, intro and chips. `rename` gives the set a stable custom name so a readable URL works alongside the numeric one.

```bash
vitrinka board meta --title "Checkout" --kicker "pass 3"
vitrinka board rename myapp/main/7 payout-flow
```

## Documents and pages [#docs]

A set can carry a document instead of a gallery: a `doc.json` of elements at the set root, with sibling pages under `pages/<slug>.json`. Push handles the whole tree in one request:

* **Preflight** — `doc.json` is validated against this deployment's element vocabulary before upload; JSON damage is autofixed and a legacy `{meta, blocks}` file is rewritten as the doc element unless you pass `--no-fix`, and a real contract error stops the push with the offending element named.
* **Pages become cards** — each `pages/<slug>.json` is minted as a sibling document card, `page:` references inside `ref` elements are rewritten to card ids, and a re-push keeps the ids, so links stay stable.
* **Smoke render** — an artifact push answers with a `render.png` of the result, so an agent can look at what it shipped without opening a browser.

The vocabulary itself — every element kind with its payload — is the [elements reference](/reference/elements); how documents nest and lay out is [composing boards](/dev/compose).

## Artifacts [#artifact]

An artifact is a self-contained interactive page rather than a gallery. `init` scaffolds one with its set config; `create` builds a report from an existing screenshot set.

```bash
vitrinka board artifact init --slug checkout-report
vitrinka board artifact create --slug checkout-report --from ./captures
```
