pro-visu docs

Configuration

The pro-visu.config file — settings (every field at its default), the managed server, assets, options merging, and the input graph.

For AI / LLMs: View Markdown

Everything pro-visu owns lives in a pro-visu/ folder at your repo root: pro-visu init writes pro-visu/pro-visu.config.ts with two blocks — settings (repo-level CLI behaviour) and assets (what to generate) — and output renders into pro-visu/output/.

pro-visu/
├─ pro-visu.config.ts   # the config
├─ config/              # optional: modules the config is split into
└─ output/              # generated assets + manifest.json (gitignored)
pro-visu/pro-visu.config.ts
import { defineConfig } from "pro-visu";

export default defineConfig({
  // repo-level behaviour — every field is optional (full reference below)
  settings: { outDir: "output" },
  // one entry per thing to generate
  assets: [
    { name: "home-reel", url: "https://your-site.com", generator: "scroll-reel" },
  ],
});

defineConfig is a typed identity helper — it gives you autocomplete and inline docs for every field, but the config is validated again at runtime.

File discovery

Without --config <path>, the CLI looks inside the pro-visu/ folder for the config:

  • pro-visu.config.ts · .js · .mjs · .cjs · .json
  • .pro-visurc / .pro-visurc.json

An explicit --config <path> escapes this convention and is loaded as given (paths resolve against the repo root). A .ts config that imports defineConfig requires the pro-visu package to be resolvable from that folder; a .json config does not. A JSON config gets the same autocomplete + validation from a generated pro-visu.schema.jsonpro-visu init --json writes it (next to the config) and points the config at it via $schema.

Multiple configs

You can keep more than one config in the folder — e.g. a main showcase and a smaller docs set — and run the extra ones by name: pro-visu generate --config pro-visu/docs.config.ts. Give each its own outDir (a nested subfolder such as output/showcase and output/docs is tidiest) so they don't share one manifest.json — each run writes and prunes its own manifest, so a shared output dir would have the two configs clobbering each other's records.

Settings

The settings block controls repo-level CLI behaviour. Fields are ordered the way you'll reach for them: output & run behaviour, then the capture environment (browser + managed server), then capture-mode settings and per-generator defaults.

Every field has a default, so settings can be omitted entirely. Reference is the interactive view; TypeScript is the same shape in code, every field at its default.

enabledboolean | stringdefault true

Which assets to run. true runs every asset that is not individually disabled; false runs none; a group string (e.g. "quick-test") runs only the assets whose own enabled matches it — a fast way to swap between quality passes. Explicit --asset selection on the CLI overrides this.

outDirstringdefault "output"

Output directory, relative to the pro-visu/ config dir (so the default lands in pro-visu/output/). Holds generated assets + manifest.json.

concurrencynumberdefault 1

How many assets to generate in parallel (shared browser, separate contexts). Raise it on a machine with RAM headroom for faster runs.

logLevel"silent" | "error" | "warn" | "info" | "debug"default "info"

Log verbosity for generate and list. --verbose forces "debug" on generate; doctor always reports in full. (Render quality is an iteration choice — set it with --draft, not here.)

cachebooleandefault false

Skip assets whose inputs + options + tool fingerprint are unchanged. Opt-in; can be stale. Same as --cache.

browserobject

Playwright launch controls.

headlessbooleandefault true

Run headless. Set false to watch captures in a visible window.

channelstring

Use an installed browser channel, e.g. "chrome" or "msedge", instead of the managed Chromium. When set, the managed Chromium is not required or installed.

executablePathstring

Absolute path to a browser executable (overrides channel and the managed Chromium).

argsstring[]default []

Extra launch args, e.g. ["--no-sandbox"] on CI.

launchTimeoutMsnumberdefault 30000

Browser launch timeout in ms.

serverobject

Optional managed server — the tool builds, starts, waits for, and stops your site (see below). Every field defaults, so `server: {}` works; build/command fall back to your project's package scripts.

commandstringdefault <pm> start

Command that starts the server, run via the shell. Defaults to your project's start script (e.g. "pnpm start"), detected from the lockfile — so you rarely set it. The tool sets PORT/HOST in its environment to the readiness port/host, so frameworks that honour PORT (Next, Vite, …) bind it automatically. An explicit flag (e.g. next start -p 4000) still wins.

buildstring | falsedefault <pm> build

One-shot build run before starting. Defaults to your project's build script (e.g. "pnpm build"); set false to skip it (already-built or dev-server setups).

urlstring

Health-check URL polled until it responds. Defaults to http://127.0.0.1:<port>.

portnumberdefault 3101

Port the readiness check probes (and derives url from), also passed to the command as PORT so it binds the same port.

cwdstringdefault repo root

Working directory for build + command, relative to the repo root (where the CLI is run), not the pro-visu/ folder — so "next build" runs against your app.

readyTimeoutMsnumberdefault 120000

Max time to wait for the server to become reachable.

reuseExistingbooleandefault true

If a server is already reachable at the URL, use it as-is (don't start or stop one).

captureobject

Applied to every URL-based asset (scroll-reel, screenshots, interaction) and folded into the cache key — any asset can override it via its own `capture` block (see "Per-asset capture overrides"). Two halves: signals into the site, and cleanup the tool applies itself.

signalsobject

Capture-mode flags delivered into the site. The site must READ one and render settled (reveals shown, count-ups final).

queryRecord<string, string>

Query params appended to every URL-based asset, e.g. { capture: "1" } → ?capture=1.

cookies{ name, value }[]

Cookies set on every capture context before navigation (scoped to the asset's origin). SSR-readable and persisted across in-app navigation — the best fit for multi-route reels. Also carries auth (a session cookie) to reach login-gated pages.

localStorageRecord<string, string>

Entries seeded per origin before the page's own scripts run.

initScriptstring

JS run in every page before its own scripts, e.g. window.__PV_CAPTURE__ = true.

cleanupobject

Noise the tool removes itself — no site cooperation needed.

hideSelectorsstring[]default []

Hide elements matching these CSS selectors before capture (cookie banners, chat widgets, …).

clickSelectorsstring[]default []

Click these selectors once after load to dismiss overlays (consent dialogs). Best-effort.

injectCssstring

Extra CSS injected before capture — e.g. hide a sticky promo bar.

hideScrollbarsbooleandefault true

Hide scrollbars so they don't appear in captures.

pauseAnimationsbooleandefault false

Pause CSS animations/transitions for fully static, deterministic frames.

freezeClockbooleandefault false

Freeze Date.now / performance.now / Math.random (seeded) so time/random content is stable.

blockTrackersbooleandefault true

Abort common analytics/ads/session-replay requests during capture (cleaner, faster).

blockHostsstring[]default []

Extra hostname substrings to block during capture.

blockResourceTypesstring[]default []

Playwright resource types to block, e.g. ["media", "font"].

defaultsobjectdefault {}

Per-generator option defaults, keyed by generator id, merged beneath each asset's options. Nested objects merge recursively; arrays and primitives replace wholesale.

settings: {
  // output & run behaviour
  enabled: true,                       // false = run none; "quick-test" = run only that group
  outDir: "output",
  concurrency: 1,
  logLevel: "info",
  cache: false,

  // capture environment
  browser: {
    headless: true,
    // channel: "chrome",              // use an installed browser instead of managed Chromium
    // executablePath: "/path/to/chrome",
    args: [],                          // e.g. ["--no-sandbox"] on CI
    launchTimeoutMs: 30000,
  },
  // server: {}, // managed server (build → `<pm> build`, start → `<pm> start`) — see below

  // capture-mode + generator defaults
  capture: {
    // signals into the site (the site must read one and render settled)
    signals: {
      // query: { capture: "1" },      // → ?capture=1
      // cookies: [{ name: "pv_capture", value: "1" }],   // SSR-readable; also carries auth
      // localStorage: { pvCapture: "1" },
      // initScript: "window.__PV_CAPTURE__ = true",
    },
    // cleanup applied by the tool (no site cooperation needed)
    cleanup: {
      hideSelectors: [],               // e.g. ["#cookie-banner", ".intercom-launcher"]
      clickSelectors: [],              // e.g. ["#onetrust-accept-btn-handler"]
      // injectCss: "…",
      hideScrollbars: true,
      pauseAnimations: false,
      freezeClock: false,
      blockTrackers: true,
      blockHosts: [],
      blockResourceTypes: [],
    },
  },

  defaults: {},                        // per-generator; see below
}

Memory is managed automatically: heavy frame-stepped plans (real media walls) re-exec the CLI with a larger Node heap sized from your machine's RAM, and a watchdog stops a run gracefully before an out-of-memory crash.

capture

A page that animates in — reveal-on-scroll, count-ups, scroll-snap — or shows a cookie banner or chat widget captures as gaps, zeros, or the wrong frame. capture makes every URL capture clean and settled, in two complementary halves:

  • signals into the site (query, cookies, localStorage, initScript) deliver a "capture mode" flag — but the site has to read it and render accordingly. Four channels, so a site can use whichever fits its rendering model; cookies is the best fit for multi-route reels (SSR-readable, persisted across navigation).
  • cleanup applied by the tool (hideSelectors, freezeClock, blockTrackers, …) needs no site cooperation — pro-visu suppresses the noise itself.
settings: {
  capture: {
    signals: { query: { capture: "1" } },      // site reads ?capture=1 and renders settled
    cleanup: {
      hideSelectors: ["#cookie-banner", ".intercom-launcher"],
      clickSelectors: ["#onetrust-accept-btn-handler"],
      freezeClock: true,
    },
  },
}

The site half — reading the signal and rendering settled (hide the header, show reveals, freeze count-ups) — is a copy-paste recipe: Capture mode: render a settled page.

Realtime recordings (the interaction generator) apply the same cleanup but keep media playing — a live recording wants its motion.

Auth: cookies isn't limited to capture-mode signals — a session cookie carries auth, letting captures reach login-gated pages. Keep the value in an env var, not the config; see Troubleshooting.

defaults

Repo-wide defaults per generator. Keys are generator ids — a key that matches no generator is flagged at generate time; the value is an options object merged underneath each asset's own options (the asset wins). Nested objects merge recursively, while arrays and primitives replace wholesale — so a field set here can be omitted on every asset of that generator, and each asset overrides only what differs.

settings: {
  defaults: {
    "scroll-reel": {
      output: {
        width: 1440,
        height: 900,
        fps: 30,
      },
    },
    "screenshots": { output: { format: "jpeg", quality: 90 } },
  },
}

Managed server

When settings.server is set — even as an empty {}pro-visu generate builds your site, starts it, waits for it to respond, runs the capture, then shuts it down, so a project's npm script can be just pro-visu generate. build and command default to your project's own package scripts (<pm> build / <pm> start, detected from the lockfile), so you rarely set either — pro-visu just follows along with whatever those scripts do. Change your build/start scripts and pro-visu follows automatically.

import { defineConfig } from "pro-visu";

export default defineConfig({
  // build → `pnpm build`, start → `pnpm start` (or npm/yarn/bun) — no fields needed:
  settings: { server: {} },
  assets: [
    // url omitted → captures the server root; relative paths resolve against it
    { name: "home", generator: "scroll-reel" },
    { name: "pricing", url: "/pricing", generator: "scroll-reel" },
  ],
});

Override any field only when your setup differs — e.g. command: "next start -p 4000", or build: false to skip the build for an already-built or dev-server target:

settings: {
  server: {
    build: false,             // don't build — just start
    command: "pnpm dev",      // point at a dev server instead of a production start
  },
},

The managed server's URL is the default base for capture targets: a url-based asset that omits url captures the server root, and any relative url (e.g. /pricing) resolves against it. Absolute URLs pass through unchanged.

Set a custom port (or url) only if your server can't read PORT from the environment, or you need a non-default port. Otherwise leave both off — the tool keeps the command, the readiness check, and your asset URLs in sync for you.

Lifecycle and flags

A normal run is: build → start → wait for ready → capture all assets → stop.

The server is skipped automatically when nothing in the selection needs a URL — i.e. every selected asset is a local generator (wall, specimen, palette, palette-reel). So a wall in test mode, or pro-visu generate <a local asset>, renders without paying for a site build/boot it never uses. (A real wall still pulls in its URL-based tile producers, so the server starts for it.)

Two flags adjust the lifecycle explicitly:

  • --skip-server — don't manage a server at all; capture an already-running site at the asset URLs. Pair this with a deployed URL or a dev server you started yourself. (Without a managed server there's no base URL, so assets need absolute urls.)
  • --skip-build — keep the managed server but drop its build step, for fast iteration when the site itself is unchanged.

If a run is killed hard before teardown, the next pro-visu generate stops any orphaned server process tree and cleans up temp directories.

Assets

Each entry in assets describes one thing to generate.

Reference is the interactive view; TypeScript is the same shape in code.

namestringrequired

Unique across the config — used in filenames and the manifest id.

enabledboolean | stringdefault true

Run this asset? true includes it; false leaves it out without deleting or commenting it; a group string (e.g. "quick-test") tags it so settings.enabled set to the same string runs only that group. Dependencies of a running asset are pulled in regardless of their own enabled.

generatorstringrequired

One of the generator ids (/docs/generators).

urlstring

Page to capture. Required by URL-based generators (scroll-reel, interaction, screenshots) unless a managed server is configured — then omitting it captures the server root, and a relative "/path" resolves against it. Local generators (wall, palette, palette-reel, specimen) take no url.

optionsobject

Generator-specific options, merged over settings.defaults for this generator (the asset wins). Validated by the target generator.

captureobject

Per-asset overrides of settings.capture, merged over it for this asset only (see "Per-asset capture overrides" below). Signals merge; cleanup arrays are additive with showSelectors/unblockHosts to subtract; booleans override. Omit to inherit the global capture settings.

assets: [
  {
    name: "home-shots",
    url: "https://your-site.com",   // required by URL-based generators (relative resolves against a managed server)
    generator: "screenshots",
    options: {
      // generator-specific; see /docs/generators/<id>
      viewports: [
        {
          name: "desktop",
          width: 1440,
          height: 900,
        },
      ],
      elements: [{ selector: "header", name: "nav" }],
    },
  },
]

Enabling, disabling & grouping assets

Every asset has an enabled field (default true). Set it to false to leave an asset out of the run without deleting or commenting it out. Set it to a group name to tag the asset, then flip settings.enabled to that same string to run only that group — a fast way to swap between quality passes without touching each asset:

export default defineConfig({
  // Swap this one line to switch passes: true (everything), "quick", or "full".
  settings: { enabled: "quick" },
  assets: [
    { name: "hero-quick", generator: "scroll-reel", url: "/", enabled: "quick" },
    { name: "hero-full",  generator: "scroll-reel", url: "/", enabled: "full", options: { output: { fps: 60 } } },
    { name: "wip",        generator: "screenshots", url: "/pricing", enabled: false }, // never runs
  ],
})
  • settings.enabled: true (default) runs every asset except those set to false.
  • settings.enabled: false runs nothing.
  • settings.enabled: "quick" runs only assets whose own enabled is "quick".
  • An explicit --asset <name> on the CLI overrides all of this and runs exactly what you name (even a disabled one). Dependencies of a running asset are always pulled in, whatever their own enabled.

pro-visu doctor prints the resolved plan and marks which assets will run under the current enabled setting.

Per-asset capture overrides

settings.capture applies to every URL capture, but one asset can override it via its own capture block — the two are merged (global first, the asset on top) for that asset only. The classic case: you hide the cookie banner globally, but want one hero shot that shows it off.

Cleanup arrays are additive — an asset's hideSelectors layer on top of the global ones rather than replacing them — and two subtraction escapes remove inherited entries: showSelectors un-hides globally-hidden elements, unblockHosts un-blocks globally-blocked hosts. Booleans (freezeClock, blockTrackers, …) and injectCss override (CSS is appended); signal records (query, localStorage) merge and cookies merge by name. Omit a key to inherit the global value.

export default defineConfig({
  settings: {
    capture: { cleanup: { hideSelectors: ["#cookie-banner", "#chat-widget"] } },
  },
  assets: [
    // Inherits the global — hides both.
    { name: "home", generator: "scroll-reel", url: "/" },
    // Show the cookie banner off in this one, but keep hiding the chat widget:
    {
      name: "consent-hero",
      generator: "screenshots",
      url: "/",
      capture: { cleanup: { showSelectors: ["#cookie-banner"] } },
    },
    // Let this reel animate live (the global froze the clock elsewhere):
    {
      name: "ticker",
      generator: "scroll-reel",
      url: "/pricing",
      capture: { cleanup: { freezeClock: false } },
    },
  ],
})

The asset graph

Assets can depend on other assets — but you never author the dependency map. A generator derives its dependencies from its own options: the wall treats each asset name in its columns as a producer that must run first (and local files ride in as { src } tiles, no producer needed):

assets: [
  {
    name: "hero-shot",
    url: "/",
    generator: "screenshots",
    options: { fullPage: false },
  },
  {
    name: "wall",
    generator: "wall",
    // hero-shot runs first (named as a tile); the photo is used directly from disk
    options: { columns: [{ tiles: ["hero-shot", { src: "public/img/coat.jpg" }] }, /* …≥3… */] },
  },
]

This forms a DAG; cycles and references to unknown assets are rejected at load time. Selecting an asset with --asset automatically pulls in its dependencies.

Splitting the config

Nothing requires one monolithic file. As a showcase grows, split it into modules under pro-visu/config/ — settings in one file, each asset family in its own — and compose them in pro-visu/pro-visu.config.ts, the way a Payload config imports its collections:

pro-visu/config/settings.ts
import type { ShowcaseSettingsInput } from "pro-visu";

export const settings: ShowcaseSettingsInput = {
  outDir: "output",
  server: {}, // build → `<pm> build`, start → `<pm> start`
};
pro-visu/config/films.ts
import type { AssetSpecInput } from "pro-visu";

export const films: AssetSpecInput[] = [
  {
    name: "home",
    generator: "scroll-reel",
    options: { motion: { autoSections: { durationMs: 14000 } } },
  },
  {
    name: "shop",
    url: "/shop",
    generator: "scroll-reel",
  },
];
pro-visu/pro-visu.config.ts
import { defineConfig } from "pro-visu";
import { settings } from "./config/settings";
import { films } from "./config/films";
import { stills } from "./config/stills";

export default defineConfig({ settings, assets: [...films, ...stills] });

Annotating each module with its input type keeps full type-checking and autocomplete on the value. Every author-facing type is exported from pro-visu, including per-generator option types (ScrollReelOptions, WallOptions, …) and their fragments (WallColumnInput, ChoreographyStepInput, PulseInput, PaletteColorInput, …), so shared recipes and helpers in those modules can be fully typed.

On this page