# Verifying a deck

> What each check sees, what it cannot see, and the one gate to run before you ship

Narro's characteristic failure is **a green build that renders wrong**. Every
release so far has shipped at least one: a reveal that compiled and revealed
nothing, a class that landed on no element, an export that reported success over
an empty directory, a production build that rendered a black rectangle with four
separate checks green over it.

So the honest version of "run `narro check`" is this page: what each gate sees,
what it does not, and which one to run before you hand the deck to anyone.

## The short answer

```bash
narro check deck.md --strict --fit     # markdown mode
npm run build && narro check dist --fit   # React mode
```

`--strict` catches names and syntax. `--fit` is the only one that opens a
browser, and therefore the only one that can tell you the deck renders at all.
Run both. Neither is slow enough to skip.

## What each gate sees

| Gate | Sees | Does not see |
| --- | --- | --- |
| `narro check` | Layout, theme, slot and template names; markdown that reached the slide as literal text; a class or a `.step` that landed on nothing; a preamble import that resolves nowhere | Whether the content fits; whether the page renders |
| `narro check --render` | The compiled element tree, with the classes that actually landed on each element, and every component with its props | Size, colour, position — it has no browser; and what a component *renders*, since it is not run |
| `narro check --render-full` | The same, without shortening long text or large props to fit a terminal line | The same |
| `narro check --strict` | The above, with warnings promoted to errors. The CI gate | The same blind spots |
| `narro check --fit` | Overflow against the 1920×1080 canvas, in a real browser. Errors the page threw while loading, and whether it rendered any slides at all | Anything below the tolerance; whether the content is *right* |
| `narro build` / `vite build` | Type errors, unresolved imports, bundling failures | Whether the result renders — which is why `--fit` on the built directory matters |
| `narro export --png` | The deck as an audience sees it, one file per slide, every fragment revealed | Nothing else checks this; it is a good last look |
| `narro capture-website` | One slide, from a running `preview`, as a picture — the fastest way to answer "it passed every gate and still looks wrong" | Only the slide you asked for, and only if you name it correctly — see below |

## `--fit` is not part of `--strict`

They answer different questions and only one of them needs a browser, so they are
separate flags. That means **a CI job running `--strict` alone ships clipped
slides** — one author's deck had two of ten slides overflowing while `--strict`
reported it clean.

If your CI image has Chromium, run both.

```bash
npx playwright install chromium   # or point NARRO_CHROMIUM at one you have
```

### Which script CI should call

Every template ships two, and the difference is only what a missing browser
means:

| Script | Fit check | For |
| --- | --- | --- |
| `npm run build` | `--fit-if-available` — warns when there is no browser | Authoring. You want the `dist/` even on a machine that cannot measure it |
| `npm run verify` | `--fit` — **fails** when there is no browser | CI. A step that passes having measured nothing is worse than no step |

`lint` and `typecheck` cannot see a clipped slide: both are about names, and a
markdown deck's `typecheck` *is* `--strict`. **`verify` is the one to wire into
a pipeline.** A deck built from `narro new` has no scripts at all — there,
`narro build deck.md` runs the measurement itself.

## "Nothing was measured" is a failure

If no browser is installed, `--fit` says
`--fit needs a browser and none is installed` and **exits non-zero**. It has
measured nothing, and an exit code is the only part of this a CI job reads — a
step that passes having measured zero slides is worse than no step, because it
reports the thing you wanted to know without having asked it.

When that is genuinely fine — you are on a laptop with no browser and you want
the rest of `check` to run — ask for it by name:

```bash
narro check deck.md --fit-if-available   # warns instead of failing
```

That is the only way to get a pass out of an unmeasured deck, which is the point:
it is a decision you make rather than one you arrive at.

## `--fit` measures what you point it at

`narro check deck.md --fit` compiles the deck itself. `narro check dist --fit`
measures **the artifact you are about to publish**, whatever produced it —
markdown mode, React mode, or a bundler you wired up yourself.

Those are different questions, and the second is the one that matters before you
ship. A deck can compile perfectly and still build to a page that throws: point
`--fit` at `dist/` and it will tell you, because a page that renders no slides is
a failure there rather than a deck with nothing to overflow.

```bash
npm run build
narro check dist --fit
```

This is also the only narro-provided verification React mode has, and the reason
`check` takes a directory at all.

## Looking at one slide

When a slide passes every gate and still looks wrong, the next step is a picture
of that slide. `narro export --png` gives you all of them; for a single slide
while you are still editing, screenshot the preview:

```bash
narro build deck.md -o dist
narro preview --outDir dist --port 4599 &
narro capture-website 'http://localhost:4599/#/slide/3' -o slide3.png
```

**The URL must be `#/slide/3`, not `#/3`.** A hash that is not a route is not an
error — the deck loads and shows slide 1 — so `#/3` returns a screenshot of the
wrong slide, silently, as many times as you ask for it. `narro capture-website`
warns when the hash is not a route it recognises, but the warning is easy to
scroll past when you are taking six of them. Slide numbers are 1-based, and a
slide with an `id` answers to `#/slide/<id>` as well. See
[Navigation](/docs/navigation.md) for the full routing rules.

## Two things people expect to be blind spots and are not

- **Content inside a `.step` fragment is measured.** A fragment is hidden
  visually and stays in layout, so `--fit` measures an unrevealed step exactly as
  it measures a revealed one — a 3000px box reports the same overflow either
  way. `--fit` is not weaker on the slides that use fragments, which are usually
  the densest ones.
- **A size authored on a `:::` container reaches the text inside it.** It did
  not always: `.rs-deck p` styles the paragraph directly, and an ancestor's value
  can only arrive by inheritance, which a direct rule beats. `:::{.text-[8rem]}`
  around a paragraph left it at the body size while `check`, `--render` and
  `--fit` all passed. The compiler now marks containers that set a font size,
  line height, font family or letter spacing, and the base stylesheet stands
  down for them.

## What no gate can see

Be clear-eyed about the edge of the box:

- **Whether the slide says the right thing.** Nothing checks your argument.
- **Whether it reads at the back of the room.** `--fit` knows the content is
  inside the canvas; it does not know 14px monospace is unreadable on a
  projector. Export a PNG and look at it at arm's length.
- **Colour and contrast.** Neither the compiler nor the fit checker has an
  opinion about a dark grey on a slightly darker grey.
- **Anything behind a `Motion*` component's own timeline.** Those run on their
  own clock rather than on the deck's steps.

## The habit

While writing: `narro check deck.md --render` after each slide. It is about
90ms, it prints exactly which element each class landed on, and it is the fastest
way to find out that an attribute block attached to something you did not mean.
It shortens long lines so a ten-slide outline stays scannable; when the thing you
are checking *is* a long line — a forty-class utility string, a paragraph you
need to read back — `--render-full` prints it whole.

Before committing: `narro check deck.md --strict --fit`.

Before presenting or sending: `narro export --png` and look at the images. It is
the only step that shows you what an audience sees, and it is the one that has
caught the most.