vitrinka.docs

Sets: capture and publish

A set is a directory of files with a share URL. vitrinka board is how one gets made, filled and sent — each shot uploads once, and whatever did not land while offline goes with the next push.

Start a set

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

vitrinka board init

Capture a shot

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 --states (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.

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

--note and --route are required: a shot with neither is a picture nobody can place.

Capture flags

FlagWhat it does
--routeRequired. The app route the shot was taken on.
--noteRequired. The caption.
--titleHuman title — also names the file.
--labelA stable shot label. Journey wiring targets it.
--nextJourney branch: the target shot's label. Repeatable.
--actionWhat 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.
--openA deep link to open before capturing — simulators, emulators and macOS; never with --file.
--settleSeconds to wait before capturing. Default 4 with --open, otherwise 0.
--viewportWxH or WxH@scale, e.g. 393x852@3. For a web shot, the page's CSS viewport.
--hidpiWeb: hold the file to 2× when --viewport is known. Default: the repository's capture.hidpi, on; --hidpi=false excuses this one shot — below.
--deviceDevice name.
--udidiOS simulator UDID.
--regionmacOS capture region, x,y,w,h.
--fileAdopt an existing image instead of capturing one.
--hqFull-fidelity asset: no lossy re-encode, no resize.
--boxesRunner text map in source-image pixels (default: the adopted image's .boxes.json) — review toolkit.
--srcThe implementing source file. Repeatable or comma-separated.
--stateNotable app or auth state.
--urlProvenance URL — beats --open in the manifest.
--rootCapture root. Default .vitrinka/screenshots.

Web captures at 2×

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.

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

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.

vitrinka board push --title "Checkout — pass 3"
FlagWhat it does
--rootCapture root. Default .vitrinka/screenshots.
--titleHuman title, re-sent on every push.
--sourceCross-link a source set: project/branchSlug/key.
--no-fixSkip the doc.json preflight autofix (JSON damage; a legacy {meta, blocks} file rewritten as the doc element).
--no-renderSkip the server's post-publish smoke render and its render.png download.
--no-designSkip sending the repo's DESIGN.md with an artifact push.
--pruneMirror the folder: delete the set's files it no longer holds.
--revivePublish onto an artifact that was deleted: its page comes back, same address, instead of the push being refused.

Pull the set

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.

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

Name and frame it

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.

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

Documents and pages

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; how documents nest and lay out is composing boards.

Artifacts

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.

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

On this page