# dv show

`dv show` constructs a capturable render URL for a page, a block, or a side-by-side comparison of theme targets. It never takes a screenshot. The command prints a URL, and an agent or browser tool opens that URL to capture what it renders. Keeping capture out of the CLI means `dv show` has no browser dependency and stays fast, while the agent that already drives a browser does the part it is good at.

The URL it prints is short-lived and signed. Every render runs through a server-issued preview token, so the link renders the selected theme target and nothing else, then expires. This is the same preview-token model the rest of Divine uses, scoped down to one render.

This page covers the three subcommands, the flags that shape the output, and the three resolution layers that let the command work against a real local WordPress install rather than only inside a wired-up test harness.

## The Three Subcommands

`dv show` has one job split across three target shapes. A page renders a worktree or app page as a standalone document. A block renders a single block. A compare renders several targets together so a human or agent can see them next to each other.

| Subcommand | What it renders |
| --- | --- |
| `dv show page <slug>` | One page from the app or a worktree. |
| `dv show block <name>` | One block from the app or a worktree. |
| `dv show compare <targets...>` | A labeled grid of two or more targets. |

You run the command from inside the theme directory, so the current directory tells Divine which app you are working in. The slug or block name is the only required positional argument for `page` and `block`.

To get a capturable URL for the `home` page of the current theme:

```bash
dv show page home
```

To get one for a block named `hero`:

```bash
dv show block hero
```

### Compare Targets

`dv show compare` takes two or more target specifiers rather than a bare slug. Each specifier names a kind and an id, and may pin the target to the app or to a specific worktree with an `@` suffix. The suffix is how you compare the same page across variants in one URL.

| Target form | Meaning |
| --- | --- |
| `page:<slug>` | A page from the default app target. |
| `block:<name>` | A block from the default app target. |
| `page:<slug>@app` | A page explicitly from the app theme. |
| `page:<slug>@<worktree>` | A page from the named worktree. |
| `block:<name>@<worktree>` | A block from the named worktree. |

A specifier without an `@` suffix, or with `@app`, resolves to the app theme. Any other suffix is treated as a worktree id. Compare always renders the virtual document of each target, so `--live` and `--worktree` are rejected here; you express the worktree per target through the suffix instead.

To compare the app's home page against the same page in a `redesign` worktree:

```bash
dv show compare page:home@app page:home@redesign
```

Each tile in the compare grid is labeled. A label is the variant, the kind, and the id joined together, where the variant is the worktree id or the word `app`. The two targets above are labeled `app page home` and `redesign page home`.

## Render Modes

A page or block normally renders as a virtual document. Divine renders the snapshot of the selected theme target into a standalone HTML document and serves it from a signed Divine REST route. This is the default because it isolates the target: it shows the theme files as they stand, independent of the live site's posts and options.

A page can also render in live mode with `--live`. Instead of the virtual document, this issues a signed preview URL onto the real site URL for that page, so the capture shows the page rendered against live site data with the selected theme active for that request. Live mode is page-only by design. Blocks have no live site URL of their own, so `dv show block --live` is rejected, and compare URLs are always virtual.

| Mode | Subcommands | What it shows |
| --- | --- | --- |
| `virtual` | page, block, compare | The theme target rendered as an isolated document. |
| `live` | page only, with `--live` | The page on the real site URL with the theme active for that request. |

## Flags

The flags shape which target is resolved, how wide it renders, how long the URL lives, and how the result is returned. None of them are required; the defaults match the most common capture.

| Flag | Effect |
| --- | --- |
| `--worktree <id>` | Render the target from a worktree theme instead of the app theme. |
| `--live` | Page only. Issue the live signed site URL instead of the virtual render. |
| `--width <px>` | Set the render width passed into the URL. Defaults to 1440, and is clamped to a sane range. |
| `--site <slug\|id>` | On multisite, select the target subsite by network-site slug or numeric blog id. |
| `--url <url>` | On multisite, select the target subsite by its site URL. |
| `--ttl <seconds>` | Set the token lifetime in seconds. Defaults to 3600, clamped between 60 and one day. |
| `--json` | Emit the URL plus metadata as JSON instead of a bare URL. |
| `--output <path>` | Also write the isolated HTML document to a file at the given path. |

A few combinations are rejected on purpose. `--live` is only valid for pages, never for blocks or compare. `--worktree` is not valid for compare, because compare targets carry their own `@` suffix. `--site` and `--url` are mutually exclusive; pass one or the other. `--output` cannot be combined with `--live`, because there is no isolated document to write when the URL points at the live site.

### Default Output

By default the command prints a single line containing the URL, which is all an agent needs to hand to a browser tool:

```bash
dv show page home
```

```text
https://example.test/wp-json/divine/v1/apps/acme/show/page/home?divine-show-token=...&width=1440
```

### JSON Output

With `--json`, the command prints a structured payload instead. The payload carries the `url`, the render `mode`, the token `expires_at` as a Unix timestamp, and a `targets` array describing each resolved target. This is the form to prefer when the caller needs the label, the resolved app slug, or the expiry rather than just the link.

```bash
dv show page home --json
```

```json
{
  "url": "https://example.test/wp-json/divine/v1/apps/acme/show/page/home?divine-show-token=...&width=1440",
  "mode": "virtual",
  "expires_at": 1750000000,
  "targets": [
    {
      "kind": "page",
      "id": "home",
      "app_slug": "acme",
      "worktree_id": null,
      "label": "app page home",
      "mode": "virtual"
    }
  ]
}
```

### Writing The Document To A File

`--output` writes the isolated HTML document to a file in addition to printing the URL. For a page or block this is the single rendered document; for compare it is the full labeled grid. Missing parent directories are created, and relative paths are resolved against the current directory.

```bash
dv show compare page:home@app page:home@redesign --output ./build/compare.html
```

The file is the same HTML the virtual URL would serve, so `--output` is useful when you want the artifact on disk without an HTTP round trip. It cannot be combined with `--live`, which has no isolated document.

## Resolving A DB-Connected WordPress

`dv show` reads themes, tokens, and site state from a live WordPress, so before it resolves anything it has to reach a WordPress that is connected to the database. It does not assume it is already running inside WordPress. Instead it walks an ordered set of strategies and uses the first one that reaches a DB-connected runtime.

The command tries these in order:

1. If WordPress is already loaded in the current process, it uses that. This is the case when `dv show` runs inside an existing bootstrap, such as a wp-env or test context.
2. If `DIVINE_WORDPRESS_BOOTSTRAP` points at a readable file, it requires that file. Set this to a `wp-load.php` you know reaches the database when the other strategies cannot find one.
3. It tries to delegate the whole command to `wp-cli`. It first probes with `wp option get siteurl`; if that succeeds, it re-runs `dv show` inside a `wp eval-file` so the command executes in WP-CLI's already-connected runtime.
4. It walks up from the current directory looking for `wp-load.php`, up to eight levels, and requires the first one it finds.

If none of those reach a DB-connected runtime, the command fails with exit code 2 and prints the strategies it probed, so you can see exactly where resolution stopped.

The `wp-cli` delegation matters most on local installs. On stacks like Local by Flywheel the database is reachable through a socket that WP-CLI is configured for but a raw `require 'wp-load.php'` from a plain CLI PHP process is not. By probing and then handing the command to WP-CLI, `dv show` reaches the database the same way `wp` does, instead of failing to connect. The delegated process is marked so it does not try to delegate again, which keeps the handoff from recursing.

## Resolving The Right Stylesheet

A Divine theme directory does not have to be named the same as the stylesheet WordPress has installed. A symlinked theme, or a worktree directory, can sit at a path whose basename differs from the registered stylesheet. `dv show` resolves the stylesheet from WordPress rather than trusting the directory name.

It compares the real, canonical path of the current directory against the stylesheet directories that WordPress reports. It checks the active stylesheet and template first when a site is explicitly selected, then falls back to scanning every theme from `wp_get_themes()` and matching by real path. The match is on resolved paths, so a symlinked theme whose directory name differs from its installed stylesheet still resolves to the correct stylesheet. If you pass `--app` explicitly, that wins and no path matching is needed.

## Resolving The Right Subsite

On a single-site install there is one site, and `dv show` targets it without any extra input. On multisite, the command has to target the subsite whose active theme is the one you are building.

When exactly one network site runs the theme, `dv show` resolves that subsite automatically from the app slug. When you need to be explicit, or when more than one site could match, you select the subsite directly:

| Selector | Resolves by |
| --- | --- |
| `--site <slug>` | The network-site slug. |
| `--site <id>` | The numeric blog id. |
| `--url <url>` | The site URL, matched by host and path. |

If a selector matches no site, the command reports that the site was not found rather than guessing. When several sites could match an ambiguous selector, it surfaces the candidates so you can pick one precisely. `--site` and `--url` are mutually exclusive, and both require a multisite install; passing `--site` on a single-site install is an error.

## Exit Codes

`dv show` follows the same exit-code convention as the rest of the `dv` CLI. A successful run prints the URL or JSON and exits 0. A usage error, a failed WordPress resolution, or an internal error exits 2 with a message on standard error. There is no findings-style exit 1 here, because `dv show` resolves a URL rather than validating a theme.

| Exit code | Meaning |
| --- | --- |
| `0` | A URL or JSON payload was produced. |
| `2` | Usage error, WordPress could not be resolved, or an internal error occurred. |
