โ โVerifying a deck
How do I know my deck is actually correct?
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
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.
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:
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.
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:
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 for the full routing rules.
Two things people expect to be blind spots and are not
- Content inside a
.stepfragment is measured. A fragment is hidden visually and stays in layout, so--fitmeasures an unrevealed step exactly as it measures a revealed one โ a 3000px box reports the same overflow either way.--fitis 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 pstyles 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 whilecheck,--renderand--fitall 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.
--fitknows 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.