# Technical Documentation: quarto-dev/quarto-cli

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/quarto-dev/quarto-cli)

### Overview (README.md)

[](https://github.com/quarto-dev/quarto-cli/releases/latest)
[](https://github.com/quarto-dev/quarto-cli/releases/)
[](https://github.com/quarto-dev/quarto-cli/actions/workflows/create-release.yml)
[](https://github.com/quarto-dev/quarto-cli/actions/workflows/test-smokes-parallel.yml)
[](https://deepwiki.com/quarto-dev/quarto-cli)

# Quarto

Quarto is an open-source scientific and technical publishing system built on [Pandoc](https://pandoc.org). Quarto documents are authored using [Markdown](https://en.wikipedia.org/wiki/Markdown), an easy to write plain text format.

In addition to the core capabilities of Pandoc, Quarto includes:

1.  Embedding code and output from Python, R, Julia, and JavaScript via integration with [Jupyter](https://jupyter.org/), [Knitr](https://yihui.org/knitr/), and [Observable](https://github.com/observablehq/).

2.  A variety of extensions to Pandoc Markdown useful for technical writing including cross-references, sub-figures, layout panels, hoverable citations and footnotes, callouts, and more.

3.  A project system for rendering groups of documents at once, sharing options across documents, and producing aggregate output like [websites](https://quarto.org/docs/websites/) and [books](https://quarto.org/docs/books/).

4.  Authoring using a wide variety of editors and notebooks including [JupyterLab](https://quarto.org/docs/tools/jupyter-lab.html), [RStudio](https://quarto.org/docs/tools/rstudio.html), and [VS Code](https://quarto.org/docs/tools/vscode.html).

5.  A [visual Markdown editor](https://quarto.org/docs/visual-editor/) that provides a productive writing interface for composing long-form documents.

Learn more about Quarto at <https://quarto.org>.

## Development Version

To install the development version of the Quarto CLI, clone the `quarto-cli` repository then run the configure script for your platform (`configure.sh` for Linux/macOS or `configure.cmd` for Windows). For example:

```bash
git clone https://github.com/quarto-dev/quarto-cli
cd quarto-cli
./configure.sh
```

The `./configure.sh` script should add a symlink to `quarto` to your path. You can also run quarto by running `package/dist/bin/quarto`.

To update to the latest development version, run `git pull` from the local repo directory:

```bash
cd quarto-cli
git pull
```

## Running Tests

To run all unit tests, execute the script in the test directory.

```bash
cd tests
./run-tests.sh
```

To run a specific unit test, specify the script name.

```bash
cd tests
./run-tests.sh smoke/extensions/extension-render-doc.test.ts
./run-tests.sh smoke/extensions/
```

## License <a name="license-1-ov-file"/>

Quarto is open source software available under the MIT license (<https://opensource.org/license/mit/>).

---

### Tests Docs Axe Scan README (`tests/docs/axe-scan/README.md`)

# Fixtures for `quarto call axe`

Deliberately inaccessible sites. Every violation is planted on purpose, so the
scanner's grouping, matrix and baseline behaviour can be asserted against known
intent rather than against whatever a real site happens to contain.

**Do not fix the accessibility problems in these directories.** They are the
test data.

## One site per concern

There were once one site and one smoke test. Every concern shared one page list,
so adding a page moved assertions in unrelated verifiers. That happened twice.
Each site below now carries one concern, and each has its own smoke test.

| site | concern | smoke test |
|---|---|---|
| `sites/findings/` | grouping, systemic against localized, the impact spread, baseline reconciliation, the report, and a page Quarto did not render | `tests/smoke/axe/axe-findings.test.ts` |
| `sites/matrix/` | the viewport and mode axes: conditional failures each reachable by exactly one route | `tests/smoke/axe/axe-matrix.test.ts` |
| `sites/brand-light-only/` | a `_brand.yml` with light values only, which must not read as a dark mode | `tests/smoke/axe/axe-brand.test.ts` |
| `sites/darkly/` | a single dark-coloured theme: one `default` cell, annotated dark-coloured | `tests/smoke/axe/axe-darkly.test.ts` |

`cells/` holds raw per-cell payloads captured from two of these sites. The unit
tests read them, so the aggregate stage can be exercised without a browser.

## Ground rules for adding a fixture

Add the page to the site whose concern it belongs to. If it belongs to no site,
add a site. A page in the wrong site is how the coupling came back last time.

Keep each planted violation reachable by one route only. A finding that appears
in every cell proves nothing about the matrix.

The sites are source only. Each smoke test renders its site, scans it, then
removes the render and the scan artifacts. A committed render would carry
`site_libs/` and go stale on every Quarto change.

## Regenerating a site's observed table

Each site's README records what that site actually produces. axe chooses a
minimal unique selector from the DOM as it finds it, so a signature is
discovered, not decreed.

```bash
cd tests/docs/axe-scan/sites/<site>
quarto render
quarto call axe _site          # writes _axe-checks/ next to _quarto.yml
```

Then update the table from `_axe-checks/findings.json`, and read the diff. A
change there is either a fixture change or a signature-scheme change, and the
two need different responses.

---

### Tests Docs Manual README (`tests/docs/manual/README.md`)

# Manual Tests

Tests that require interactive sessions, external services, or browser access that cannot run in automated CI.

## Test Suites

| Directory / File | Area | Skill | Description |
|-----------------|------|-------|-------------|
| `preview/` | `quarto preview` | `/quarto-preview-test` | Live preview server behavior: URL routing, file watching, live reload, transient file cleanup |
| `publish-connect-cloud/` | `quarto publish` | — | Posit Connect Cloud publishing with OAuth flow |
| `mermaid-svg-pdf-tooling.qmd` | `quarto render` | — | Mermaid SVG rendering to PDF with external tooling (rsvg-convert) |

## Running Tests

Each suite has its own README with test matrix and execution instructions. Test fixtures live alongside the README in each directory.

For preview tests, use the `/quarto-preview-test` skill which automates the start-verify-cleanup cycle.

---

### Tests Docs Axe Scan Cells README (`tests/docs/axe-scan/cells/README.md`)

# Captured per-cell axe payloads

Cells from scans of the fixture sites, saved verbatim as `tests/unit/` fixtures.
They let the aggregate stage run against genuine axe-core output without
launching a browser.

**Verbatim is the point.** These are not trimmed to the fields the aggregate
stage happens to read. Keeping the whole payload means an axe upgrade that
changes its shape — how `color-contrast` reports its colour pair, how
`failureSummary` is worded — surfaces in a fast unit test rather than only in
the browser-driven smoke tests.

One directory per site, and the unit tests aggregate each set separately,
exactly as a real scan of either site would. Do not merge them. A union of two
sites is a scan that never happened, and its page list and cell counts would
mean nothing.

## `findings/` — from `../sites/findings`

| cell | why it is here |
|---|---|
| `index__1440x900__light` | the desktop and light baseline |
| `index__390x844__light` | a second cell of the same page, for the matrix union |
| `index__1440x900__dark` | with the two above and below, one page in all four cells |
| `index__390x844__dark` | so a finding's per-page cell list can be asserted in full |
| `about__1440x900__light` | a second page carrying the systemic `image-alt` |
| `static_legacy__1440x900__default` | hand-written non-Quarto DOM: one-mode (`default`) cell, and the digit-run and volatile-attribute cases |

## `matrix/` — from `../sites/matrix`

| cell | why it is here |
|---|---|
| `media__1440x900__light` | the baseline the other three are compared against |
| `media__1440x900__dark` | the dark-only `color-contrast` failure |
| `media__390x844__light` | the mobile-only `color-contrast` failure |
| `media__390x844__dark` | both conditional failures in one cell |

`theme.qmd`'s failure — reachable only by seeding Quarto's colour-scheme
localStorage key — has no capture. Nothing about it is visible in an
aggregated payload, because mode selection is a property of the scan and not
of the result. `tests/smoke/axe/axe-matrix.test.ts` covers it.

## Regenerating

Re-scan the site, then copy the matching files out of its `_axe-checks/cells/`:

```bash
cd tests/docs/axe-scan/sites/findings
quarto render
quarto call axe _site
cp _axe-checks/cells/index__1440x900__light.json ../../cells/findings/
```

If the set changes, update this file and the site's observed table. The two
describe the same scan.

---

### Tests Docs Books Visualization Curriculum Index (`tests/docs/books/visualization-curriculum/index.md`)

# Introduction {.unnumbered}

A data visualization curriculum of interactive notebooks, using [Vega-Lite](https://vega.github.io/vega-lite/) and [Altair](https://altair-viz.github.io/). This book contains a series of Python-based Jupyter notebooks, a corresponding set of JavaScript notebooks are available online on [Observable](https://observablehq.com/@uwdata/data-visualization-curriculum).

::: {.callout-note appearance="simple"}
This book was originally created using [Jupyter Book](https://jupyterbook.org/) and published at <https://uwdata.github.io/visualization-curriculum/>. This site is a port of the original book source to the [Quarto](https://quarto.org) publishing system in order to provide an example of it's use.
:::

## Getting Started

The visualization curriculum can be used either online or on your local computer. You can view and interact with the plots directly in this Jupyter Book. If you want to modify the code, you have a few different options:

- To read JavaScript notebooks online using [Observable](https://observablehq.com/), navigate to the "Observable" page above and click the corresponding notebook.
- To read Python notebooks online using [Colab](https://colab.research.google.com/), click the corresponding section in this book, hover over the little rocket ship at the top of the page, and select "Colab" from the menu.
- To read Python notebooks locally, follow the instructions below.

### Local Installation

1. [Install Altair and a notebook environment](https://altair-viz.github.io/getting_started/installation.html). The most recent versions of these notebooks use _Altair version 4_.
2. Download the notebooks from the [releases page](https://github.com/uwdata/visualization-curriculum/releases). Typically you will want to use the most recent release.  (If you wish to use notebooks for Altair version 3, download the [Altair v3.2 release](https://github.com/uwdata/visualization-curriculum/releases/tag/altair-v3).)
3. Open the notebooks in your local notebook environment. For example, if you have JupyterLab installed (v1.0 or higher is required), run `jupyter lab` within the directory containing the notebooks.

Depending on your programming environment (and whether or not you have a live internet connection), you may want to specify a particular [renderer](https://altair-viz.github.io/user_guide/display_frontends.html) for Altair.

## Credits

Developed at the University of Washington by Jeffrey Heer, Dominik Moritz, Jake VanderPlas, and Brock Craft. Thanks to the [UW Interactive Data Lab](https://idl.cs.washington.edu/) and Arvind Satyanarayan for their valuable input and feedback! Thanks also to the students of [UW CSE512 Spring 2019](https://courses.cs.washington.edu/courses/cse512/19sp/), the first group to use these notebooks within an integrated course curriculum.

---

### Tests Docs Manual Preview README (`tests/docs/manual/preview/README.md`)

# Manual Preview Tests

Tests for `quarto preview` behavior that require an interactive session with file-save events. These cannot run in automated smoke tests.

## Automation

Use the `/quarto-preview-test` command for the general workflow of starting preview, verifying with browser automation, and checking logs/filesystem. It documents the tools and patterns.

For browser interaction, `/agent-browser` is preferred over Chrome DevTools MCP (more token-efficient). See the command for details.

## Test Matrix: quarto_ipynb Accumulation (#14281)

After every test involving Jupyter execution (Python/Julia cells), verify:
1. `ls *.quarto_ipynb*` — at most one `{name}.quarto_ipynb` (no `_1`, `_2` variants)
2. After Ctrl+C exit — no `.quarto_ipynb` files remain (unless `keep-ipynb: true`)

For tests without Jupyter execution (T9, T10, T11), verify no `.quarto_ipynb` files are created at all.

### P1: Critical

#### T1: Single .qmd with Python — re-render accumulation

- **Setup:** `test.qmd` with Python code cell
- **Steps:** `quarto preview test.qmd`, save 5 times, check files, Ctrl+C
- **Expected:** At most one `.quarto_ipynb` at any time. Zero after exit.
- **Catches:** `invalidateForFile()` not deleting transient file before cache eviction

#### T2: Single .qmd with Python — startup duplicate

- **Setup:** Same `test.qmd`
- **Steps:** `quarto preview test.qmd`, check files immediately after first render (before any saves), Ctrl+C
- **Expected:** Exactly one `.quarto_ipynb` during render. Zero after exit.
- **Catches:** `cmd.ts` not passing ProjectContext to `preview()`

#### T3: .qmd in project — project-level preview

- **Setup:** Website project (`_quarto.yml` with `type: website`), `index.qmd` with Python cell
- **Steps:** `quarto preview` (project dir), save `index.qmd` 3 times, check files, Ctrl+C
- **Expected:** At most one `index.quarto_ipynb`. Zero after exit.
- **Catches:** Fix works when `projectContext()` finds a real project

#### T4: .qmd in project — single file preview

- **Setup:** Same project as T3
- **Steps:** `quarto preview index.qmd`, save 3 times, check files, Ctrl+C
- **Expected:** Same as T3. May redirect to project preview (expected behavior).
- **Catches:** Context passing works for files inside serveable projects

### P2: Important

#### T5: .qmd with Julia code cells

- **Setup:** `julia-test.qmd` with Julia cell
- **Steps:** Same as T1
- **Expected:** Same as T1. Julia uses the same Jupyter engine path.

#### T6: Rapid successive saves

- **Setup:** Same `test.qmd` as T1
- **Steps:** Save 5 times within 2-3 seconds (faster than render completes)
- **Expected:** At most one `.quarto_ipynb`. Debounce/queue coalesces saves.
- **Catches:** Race condition in invalidation during in-progress render

#### T7: `keep-ipynb: true`

- **Setup:** `test.qmd` with `keep-ipynb: true` in YAML
- **Steps:** Preview, save 3 times, Ctrl+C, check files
- **Expected:** `test.quarto_ipynb` persists after exit (not cleaned up). No `_1` variants during preview.
- **Catches:** `invalidateForFile()` respects the `transient = false` flag set by `cleanupNotebook()`

#### T8: `--to pdf` format

- **Setup:** Same `test.qmd` (requires TinyTeX)
- **Steps:** `quarto preview test.qmd --to pdf`, save 3 times
- **Expected:** Same as T1. Transient notebook logic is format-independent.

#### T9: Plain .qmd — no code cells (regression)

- **Setup:** `plain.qmd` with only markdown content
- **Steps:** Preview, save 3 times, check for `.quarto_ipynb` files
- **Expected:** No `.quarto_ipynb` files ever created.
- **Catches:** Fix is a no-op when no Jupyter engine is involved

#### T10: .qmd with R/knitr engine (regression)

- **Setup:** `r-test.qmd` with R code cell and `engine: knitr`
- **Steps:** Preview, save 3 times, check for `.quarto_ipynb` files
- **Expected:** No `.quarto_ipynb` files. Knitr doesn't use Jupyter intermediate.

#### T10b: File excluded from project inputs (regression)

- **Setup:** Website project with `_quarto.yml`. Create `_excluded.qmd` with a Python cell (files starting with `_` are excluded from project inputs by default)
- **Steps:** `quarto preview _excluded.qmd`, save 3 times, check files, Ctrl+C
- **Expected:** Falls back to single-file preview (not project preview). At most one `.quarto_ipynb`.
- **Catches:** Context reuse from cmd.ts incorrectly applying project semantics to excluded files

### P3: Nice-to-Have

#### T11: Native .ipynb file

- **Setup:** `notebook.ipynb` (native Jupyter notebook)
- **Steps:** Preview, save 3 times
- **Expected:** No transient `.quarto_ipynb` — the `.ipynb` is the source, not transient.

#### T12: File with spaces in name

- **Setup:** `my document.qmd` with Python cell
- **Steps:** `quarto preview "my document.qmd"`, save 3 times
- **Expected:** At most one `my document.quarto_ipynb`. Path normalization handles spaces.

#### T13: File in subdirectory

- **Setup:** `subdir/deep/test.qmd` with Python cell
- **Steps:** Preview from parent dir, save 3 times
- **Expected:** At most one transient notebook in `subdir/deep/`.

#### T14: Change code cell content

- **Setup:** `test.qmd` with `x = 1; print(x)`
- **Steps:** Change to `x = 2`, save; change to `x = 3`, save
- **Expected:** At most one `.quarto_ipynb`. Code changes trigger re-execution but file is cleaned.

#### T15: Change YAML metadata

- **Setup:** `test.qmd` with `title: "Test"`
- **Steps:** Change title, save; add `theme: cosmo`, save
- **Expected:** Same as T1. Metadata changes go through the same render/invalidation path.

#### T16: Multiple .qmd files in project

- **Setup:** Website with `index.qmd` (Python), `about.qmd` (Python), `plain.qmd` (no code)
- **Steps:** Preview project, edit index, navigate to about, edit about
- **Expected:** At most one `.quarto_ipynb` per Jupyter-using file. No accumulation.

## Test Matrix: Single-file Preview Root URL (#14298)

After every change to preview URL or handler logic, verify that single-file previews serve content at the root URL and print the correct Browse URL.

### P1: Critical

#### T17: Single-file preview — root URL accessible

- **Setup:** `plain.qmd` with only markdown content (no code cells)
- **Steps:** `quarto preview plain.qmd --port XXXX --no-browser`, then `curl -s -o /dev/null -w "%{http_code}" http://localhost:XXXX/`
- **Expected:** HTTP 200. Browse URL prints `http://localhost:XXXX/` (no filename appended).
- **Catches:** `projectHtmlFileRequestHandler` used for single files (defaultFile=`index.html` instead of output filename), or `previewInitialPath` returning filename instead of `""`

#### T18: Single-file preview — named output URL also accessible

- **Setup:** Same `plain.qmd`
- **Steps:** `quarto preview plain.qmd --port XXXX --no-browser`, then `curl -s -o /dev/null -w "%{http_code}" http://localhost:XXXX/plain.html`
- **Expected:** HTTP 200. The output filename path also serves the rendered content.
- **Catches:** Handler regression where only root or only named path works

### P2: Important

#### T19: Project preview — non-index file URL correct

- **Setup:** Website project with `_quarto.yml`, `index.qmd`, and `about.qmd`
- **Steps:** `quarto preview --port XXXX --no-browser`, navigate to `http://localhost:XXXX/about.html`
- **Expected:** HTTP 200. Browse URL may include path for non-index files in project context.
- **Catches:** `isSingleFile` guard accidentally excluding real project files from path computation

#### T33: knitr doc in a subdirectory — preview from that subdirectory (#14683)

- **Setup:** Fixture `knitr-subdir-14683/` — a website project with a knitr doc at `labs/doc.qmd` (an `{r}` chunk plus a `QUARTO_DOCUMENT_PATH` echo). Requires R + `rmarkdown`/`knitr`.
- **Steps:** From `knitr-subdir-14683/labs/` (the doc's own directory), run `quarto preview doc.qmd --to html --no-watch-inputs --no-browser --port XXXX` — the shape RStudio's Render button uses (bare filename, cwd = the doc's subdirectory).
- **Expected:** Renders without `Error in rmarkdown:::abs_path(input)`; `_site/labs/doc.html` is produced showing `[1] 2` (the R chunk executed). The echoed `QUARTO_DOCUMENT_PATH` is the doc's absolute subdirectory (ends in `labs`), not `.` / the project root (#12401).
- **Catches:** `fileExecutionEngineAndTarget` not normalizing the input to absolute — a cwd-relative `target.input`/`source` in the shared preview cache makes the knitr R subprocess (cwd = project dir) fail `abs_path` on the subdirectory-relative filename.
- **Automated coverage:** `tests/unit/preview-subdir-knitr-cwd.test.ts` exercises this same scenario end-to-end (real R/knitr subprocess) plus a deterministic cache-key guard. Manual rerun of this test is optional — only needed to hand-verify against a real `quarto preview` CLI/browser session (e.g. after changing the preview HTTP/format-resolution layer), since the automated test calls the internal render functions directly rather than spinning up the actual preview server.

## Test Matrix: Format Change Detection (#14533)

After every change to `previewRenderRequestIsCompatible` or the
`fileInformationCache` invalidation path, verify that a frontmatter
format edit is detected on the **first** render request.

### P1: Critical

#### T20: Single .qmd — `format: html` → `format: typst`

- **Setup:** `14533-format-change-detection.qmd` with `format: html`
- **Steps:** `quarto preview tests/docs/manual/preview/14533-format-change-detection.qmd`, edit frontmatter to `format: typst`, save, trigger one Preview render
- **Expected:** Output switches to typst on the first request. Preview process restarts.
- **Catches:** Stale `fileInformationCache` entry leaking previous format into `previewRenderRequestIsCompatible`

#### T21: Single .qmd — `format: typst` → `format: html`

- **Setup:** Same doc but starting at `format: typst`
- **Steps:** Same as T20 but reverse direction
- **Expected:** Same as T20 — direction-independent
- **Catches:** Asymmetric format-resolution path that only fires for html start state

### P2: Important

#### T22: Multi-format frontmatter — first-format change detected

- **Setup:** `format: [html, pdf]` initially
- **Steps:** Edit to `format: [typst, pdf]`, save, trigger one Preview render
- **Expected:** First format (typst) drives the compatibility check, preview restarts
- **Catches:** `previewFormat` picking up the wrong array element after invalidation

#### T23: Format change while preview is mid-render

- **Setup:** Doc with code cell long enough to render for several seconds
- **Steps:** Trigger preview, immediately edit frontmatter format mid-render, save
- **Expected:** No deadlock. Next render request after the in-flight one finishes resolves the new format.
- **Catches:** Cache-invalidation racing with cache-population in `renderForPreview`

### P3: Nice-to-Have

#### T24: File with spaces in name

- **Setup:** `my doc.qmd` with `format: html`
- **Steps:** Same as T20
- **Expected:** Same as T20. Path normalization handles the cache key correctly.

### Regression Guard

#### T25: Unchanged frontmatter — no spurious restart

- **Setup:** Any preview-able `.qmd`
- **Steps:** Save the file without editing the frontmatter format, trigger a Preview render
- **Expected:** No preview process restart. Render runs in the same process.
- **Catches:** Over-eager invalidation triggering 404 when the format is in fact unchanged

## Test Matrix: Brand Detection (#14593)

After every change to `projectResolveBrand` or the `project.brandCache` guard,
verify that a sibling `_brand.yml` added or removed **while a preview is running**
takes effect on the next render — without restarting the preview process. Fixture:
`brand-detection/` (`report.qmd`, `brand-imperial.yml`); drive it with the
`/quarto-preview-test` workflow. The
brand signal in the kept `report.typ` is the line
`#show link: set text(fill: rgb("#bc1e22")` (present ⇒ brand applied). See
`brand-detection/README.md` for the on/off check.

### P1: Critical

#### T28: Single .qmd — `_brand.yml` added mid-preview

- **Setup:** `brand-detection/report.qmd` (`format: typst`, `keep-typ: true`), no `_brand.yml` present
- **Steps:** `quarto preview report.qmd --to typst --no-browser`, copy `brand-imperial.yml` to `_brand.yml`, force one re-render (e.g. touch `report.qmd`), grep `report.typ`
- **Expected:** `report.typ` gains the brand link-color line. Brand applies without restarting preview.
- **Catches:** Stale `project.brandCache` serving "no brand" after a `_brand.yml` appeared

#### T29: Single .qmd — `_brand.yml` removed mid-preview

- **Setup:** Same fixture with `_brand.yml` present and the link colored
- **Steps:** Remove `_brand.yml`, force one re-render, grep `report.typ`
- **Expected:** The brand link-color line disappears — output reverts to no-brand
- **Catches:** Stale `project.brandCache` holding the previous brand after the file was removed

### P2: Important

#### T30: HTML format — `_brand.yml` added mid-preview

- **Setup:** `report.qmd` with `format: html`, no `_brand.yml`
- **Steps:** As T28, inspect the rendered HTML/CSS for the brand primary color
- **Expected:** Brand applies. `projectResolveBrand` is format-independent, so the same guard covers HTML.
- **Catches:** A fix that only worked for the typst code path

#### T31: Project (`_quarto.yml`) — `_brand.yml` added/removed mid-preview

- **Setup:** Website/book project with `_quarto.yml`, no project-level `_brand.yml`
- **Steps:** `quarto preview` the project, add then remove a `_brand.yml`, force re-renders between
- **Expected:** Brand applies on add and reverts on remove, both without restart
- **Catches:** The project (multi-file) `ProjectContext` sharing the same stale `brandCache`

### Regression Guard

#### T32: Unchanged `_brand.yml` — no spurious re-resolve cost

- **Setup:** A preview with a stable `_brand.yml`
- **Steps:** Save the source repeatedly without touching `_brand.yml`
- **Expected:** Brand stays applied; the source-state token matches so the cache is reused (only cheap stat calls per resolve)
- **Catches:** A guard that re-loads the brand on every render even when nothing changed

## Test Matrix: Sass Cache Reuse (#14594)

After every change to `SassCache.cleanup()` or the `_sassCache` registry in
`src/core/sass/cache.ts`, verify that a book/website preview survives repeated
re-renders instead of crashing with `BadResource: Bad resource ID`. On each
re-render the serve/watch reload path runs `refreshProjectConfig`
(`src/project/serve/watch.ts`, ~line 277 for HTML; also on config changes,
~line 305), which calls `project.cleanup()` and closes the session Sass KV
handle; the next render must open a fresh handle rather than reuse the closed
one. Fixture: `sass-cache-crash-14594/` (a flat multi-chapter book with
`theme: cosmo` + `css:`, which routes the theme through the session Sass cache);
drive it with the `/quarto-preview-test` workflow. Deterministic unit coverage
lives in `tests/unit/sass-cache.test.ts` — this matrix is the interactive
regression guide that path can't cover.

### P1: Critical

#### T34: Book project preview — `_quarto.yml` edit does not crash on Sass recompile

- **Setup:** `sass-cache-crash-14594/` book (`theme: cosmo`, `css: styles.css`), no prior `.quarto/` or `_book/`
- **Steps:** From the fixture dir, `quarto preview --no-browser --port XXXX`; after the first render, edit `_quarto.yml` (tweak the title or append a comment line) and save 3-5 times, watching the preview log
- **Expected:** Every re-render completes and reloads. No `BadResource: Bad resource ID` in the log. The cosmo theme + custom CSS keep recompiling/serving.
- **Catches:** `SassCache.cleanup()` closing the KV handle but leaving the stale instance in `_sassCache`, so the next resolve reuses a closed handle (#14594, #13955)

### P2: Important

#### T35: Book project preview — chapter content edit (content re-render path)

- **Setup:** Same fixture
- **Steps:** `quarto preview --no-browser --port XXXX`; after the first render, edit a chapter body (`index.qmd` / `chapter1.qmd`) and save 3-5 times
- **Expected:** Same as T34 — no crash, pages keep reloading
- **Catches:** The HTML reload path (`refreshProjectConfig` at watch.ts ~277) closes the session KV on a plain content re-render too, so a content save must not reuse a closed handle either

### Regression Guard

#### T36: Single-file preview — unaffected by the registry cleanup

- **Setup:** `plain.qmd` (markdown-only, no project)
- **Steps:** `quarto preview plain.qmd --no-browser --port XXXX`, save 3-5 times
- **Expected:** No crash and no behavior change — the fix is a no-op for the common single-file preview path
- **Catches:** The registry-entry deletion in cleanup accidentally breaking or churning the non-project preview path

## Extension Format PDF Preview (#14582)

### P1: Core functionality

#### T26: Book preview with extension PDF format
- **Setup**: `extension-pdf-book/` fixture
- **Steps**: `quarto preview . --to dummy-extension-pdf --no-browser --port 4444`
- **Expected**: Browse URL contains `web/viewer.html`. PDF.js viewer loads in browser with live-reload.
- **Catches**: `isFormatTo` startsWith check failing for extension format strings.

#### T27: Book preview with extension HTML format (no regression)
- **Setup**: Same fixture
- **Steps**: `quarto preview . --to dummy-extension-html --no-browser --port 4444`
- **Expected**: Normal HTML preview with live-reload. No PDF.js viewer.
- **Catches**: Fix doesn't accidentally enable PDF mode for non-PDF extension formats.

## Test Matrix: Book `.llms.md` Cross-References (#14975)

Book chapters resolve cross-references in project post-render, and their
`.llms.md` files are converted after that resolution. Verify that a
preview re-render keeps this: the edited chapter's `.llms.md` is
regenerated with resolved numbers and cross-chapter links, and the served
HTML still has no `llms-txt`-only content. Fixture:
`book-llms-crossrefs-14975/` (three-chapter book, `llms-txt: true`).
Rendered-output coverage lives in
`tests/smoke/project/project-book-llms-crossrefs.test.ts`.

### P1: Critical

#### T37: Book preview — chapter edit regenerates resolved `.llms.md`

- **Setup**: `book-llms-crossrefs-14975/` book, no prior `.quarto/` or `_book/`
- **Steps**: From the fixture dir, `quarto preview --no-browser`; after the first render, check `_book/second.llms.md`. Append the line `PREVIEWEDIT see @tbl-three.` to `second.qmd`, save, and wait for the reload. Check `_book/second.llms.md` again.
- **Expected**: Before and after the edit, `second.llms.md` has `[Equation 1.1](#eq-one)` and `[Table 2.1](third.llms.md#tbl-three)` (with a no-break space after the prefix). After the edit, it has the `PREVIEWEDIT` line, with its reference resolved to `Table 2.1`.
- **Catches**: The preview re-render path skipping the book post-render step that converts held `.llms.md` pages, or not holding them, so `.llms.md` goes stale or unresolved.

### Regression Guard

#### T38: Book preview — served HTML has no `llms-txt`-only content after a re-render

- **Setup**: Same fixture, preview from T37 still running
- **Steps**: Edit `third.qmd` (append a sentence), save, wait for reload, then fetch the served page: `curl -s http://localhost:<port>/third.html`.
- **Expected**: The response contains `HTMLONLYCHAPTERTEXT`. It contains neither `LLMSONLYCHAPTERTEXT` nor the classes `llms-conditional-content` / `llms-hidden-content`. `_book/third.llms.md` contains `LLMSONLYCHAPTERTEXT`.
- **Catches**: Conditional-content cleanup of the HTML being skipped or delayed for books.

## Test File Templates

**Minimal Python .qmd:**
```yaml
---
title: "Preview Test"
---
```

````
```{python}
print("Hello from Python")
```
````

```
Edit this line to trigger re-renders.
```

**Minimal website project (`_quarto.yml`):**
```yaml
project:
  type: website
```

**keep-ipynb variant:**
```yaml
---
title: "Keep ipynb Test"
execute:
  keep-ipynb: true
---
```

---

### Tests Docs Manual Publish Connect Cloud README (`tests/docs/manual/publish-connect-cloud/README.md`)

# Manual Testing: quarto publish posit-connect-cloud

Testing protocol for the Posit Connect Cloud publish provider.

## Prerequisites

- Dev quarto built (`./configure.cmd` completed)
- Browser available for OAuth flow

## Setup

To test against staging instead of production, set the environment:

```bash
export POSIT_CONNECT_CLOUD_ENVIRONMENT=staging
```

Dev quarto path (from repo root):

```bash
# Windows
./package/dist/bin/quarto.cmd

# Linux/macOS
./package/dist/bin/quarto
```

Token storage location:
- Windows: `%LOCALAPPDATA%\quarto\publish\accounts\posit-connect-cloud\accounts.json`
- macOS/Linux: `~/.local/share/quarto/publish/accounts/posit-connect-cloud/accounts.json`

---

## Test 1: First-Time Authorization

Verify the OAuth device code flow works end-to-end.

**Pre-condition:** No existing posit-connect-cloud accounts. Check with:

```bash
./package/dist/bin/quarto.cmd publish accounts
```

**Run:**

```bash
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd
```

**Expected behavior:**

1. Prompt: "Authorize" confirmation
2. Authorization code displayed (green, bold)
3. Browser opens to login page (the code is pre-filled in URL)
4. After approving in browser, terminal resumes automatically
5. If no publishable accounts: browser opens account creation, spinner shows "Waiting for account setup"
6. If one account: auto-selected with debug message
7. If multiple accounts: interactive selection prompt
8. Confirmation: "Authorized to publish as **account-name**"
9. Rendering happens
10. Spinner: "Uploading files"
11. Spinner: "Publishing document"
12. Final message: `Published: https://...connect.posit.cloud/<account>/content/<id>`
13. Browser opens to published content

**Check:**

- [ ] Auth code shown in terminal
- [ ] Browser opens automatically
- [ ] No manual code entry required (pre-filled URL)
- [ ] Account selected/created
- [ ] Document published and accessible at URL
- [ ] Content renders correctly in browser (title, formatting, link)
- [ ] Account shows server URL in prompt (e.g. `cderv (https://connect.posit.cloud)`)

**Record:** Account name: ___ | Content ID: ___ | URL: ___

---

## Test 2: Token Persistence

Verify re-publish works without re-authorization.

**Pre-condition:** Test 1 completed. Close the terminal entirely.

**Run (in fresh terminal):**

```bash
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd
```

**Check:**

- [ ] No authorization flow triggered
- [ ] Existing target detected from `_publish.yml`
- [ ] Publish succeeds
- [ ] URL unchanged from Test 1

---

## Test 3: Publish RevealJS Slides

Verify single-document revealjs publishing works (reveal.js assets bundled correctly).

**Run:**

```bash
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/revealjs-slides/slides.qmd
```

**Check in browser:**

- [ ] Slides load with reveal.js navigation (arrow keys, progress bar)
- [ ] Slide 3 has aquamarine background color
- [ ] Slide 4 has syntax-highlighted Python code block
- [ ] No broken assets (check browser console for 404s)
- [ ] All reveal.js transitions and controls functional

**Record:** Content ID: ___ | URL: ___

---

## Test 4: Publish a Website

Verify multi-page site publishing.

**Run:**

```bash
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/simple-website
```

**Check in browser:**

- [ ] Home page loads with correct title
- [ ] Navbar present with "Home" and "About" links
- [ ] Click "About" -> about page loads
- [ ] Click "Home" -> home page loads
- [ ] Both pages render correctly
- [ ] Published as new content (different URL from Test 1)

**Record:** Content ID: ___ | URL: ___

---

## Test 5: Update Existing Content

Verify re-publishing updates content in-place.

**Setup:** Edit `simple-website/index.qmd` — add a line:

```markdown
This content was updated for Test 5.
```

**Run:**

```bash
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/simple-website
```

**Check:**

- [ ] URL is same as Test 4
- [ ] Hard-refresh (Ctrl+F5) shows updated text
- [ ] About page still works
- [ ] No duplicate content created on dashboard

**Cleanup:** Revert the edit:

```bash
git checkout tests/docs/manual/publish-connect-cloud/simple-website/index.qmd
```

---

## Test 6: Account Management

Verify accounts listing and removal.

**Run:**

```bash
./package/dist/bin/quarto.cmd publish accounts
```

**Check:**

- [ ] Posit Connect Cloud account(s) listed
- [ ] Account name from Test 1 shown
- [ ] Server URL displayed next to account name

**Remove the account** (select it from the interactive menu, confirm removal).

**Verify removed**, then **re-authorize** (needed for remaining tests):

```bash
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd
```

---

## Test 7: CI/CD Mode (Environment Token)

Verify non-interactive publishing with environment variables.

**Setup:** Extract the stored token:

```bash
cat "$LOCALAPPDATA/quarto/publish/accounts/posit-connect-cloud/accounts.json"
```

Note the `accessToken` and `accountId` values.

**Set env vars and remove stored account:**

```bash
export POSIT_CONNECT_CLOUD_ACCESS_TOKEN="<accessToken>"
export POSIT_CONNECT_CLOUD_ACCOUNT_ID="<accountId>"
```

Remove stored accounts via `quarto publish accounts` so only env var token is used.

**Run:**

```bash
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd --no-prompt
```

**Check:**

- [ ] No browser opens
- [ ] No interactive prompts
- [ ] Token from environment variable used
- [ ] Account resolved from ACCOUNT_ID
- [ ] Publish succeeds
- [ ] No ghost token persisted (run `quarto publish accounts` after — should show no posit-connect-cloud accounts)

### 7b: Multiple Accounts Without ACCOUNT_ID (--no-prompt)

If the access token has access to multiple publishable accounts:

```bash
export POSIT_CONNECT_CLOUD_ACCESS_TOKEN="<accessToken>"
# Intentionally do NOT set POSIT_CONNECT_CLOUD_ACCOUNT_ID
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd --no-prompt
```

- [ ] Publish fails with error: "Multiple publishable accounts found. Set POSIT_CONNECT_CLOUD_ACCOUNT_ID to select a specific account."
- [ ] No content published

This matches other providers' behavior: ambiguous account selection fails in non-interactive mode.

**Cleanup:**

```bash
unset POSIT_CONNECT_CLOUD_ACCESS_TOKEN
unset POSIT_CONNECT_CLOUD_ACCOUNT_ID
```

Re-authorize interactively for remaining tests.

---

## Test 8: Provider Selection (Interactive)

Verify posit-connect-cloud appears in the interactive provider menu.

**Run:**

```bash
./package/dist/bin/quarto.cmd publish tests/docs/manual/publish-connect-cloud/single-doc/document.qmd
```

(No provider argument — triggers interactive selection.)

**Check:**

- [ ] "Provider:" prompt shown
- [ ] "Posit Connect Cloud" listed as an option
- [ ] Selecting it proceeds to publish flow
- [ ] Ctrl+C to cancel (or complete the publish)

---

## Test 9: posit-cloud Deprecation Warning

Verify the old provider name shows a helpful warning.

**Run:**

```bash
./package/dist/bin/quarto.cmd publish posit-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd
```

**Check:**

- [ ] Warning message displayed
- [ ] Message mentions "no longer supported"
- [ ] Message suggests `posit-connect-cloud`
- [ ] Message includes link to docs.posit.co
- [ ] Command fails (provider not found after warning)

---

## Test 10: Error Cases

### 10a: Invalid Token

```bash
export POSIT_CONNECT_CLOUD_ACCESS_TOKEN="invalid_garbage_token"
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd
```

- [ ] Clear authentication error
- [ ] No crash/stacktrace

```bash
unset POSIT_CONNECT_CLOUD_ACCESS_TOKEN
```

### 10b: Deleted Content

1. Publish the single doc (or use existing from Test 1)
2. Delete the content from the Connect Cloud dashboard
3. Re-publish:

```bash
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd
```

- [ ] Silently detects content is gone via `resolveTarget` (debug log: "not found" or "deleted")
- [ ] No "Publish update to:" prompt for the deleted target
- [ ] Falls through to new publish flow ("Publish with account:" prompt)
- [ ] Publishes successfully with new content ID
- [ ] Old stale entry remains in `_publish.yml` (not cleaned up — known limitation)

### 10c: --no-render Flag

```bash
./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd --no-render
```

- [ ] Skips rendering
- [ ] Still uploads and publishes (uses last rendered output)

**Known issue:** `--no-render` may produce incomplete bundles for single documents (missing supporting resources). This affects all bundle-based providers, not just Connect Cloud.

---

## Test 11: Token Refresh

Access tokens expire after some time. To test proactive refresh:

**Pre-condition:** The stored token file must contain a valid `refreshToken` (present by default after interactive auth in Test 1).

1. Open the stored token file (see token storage path in Setup)
2. Change `expiresAt` to a past timestamp (e.g., `1000`)
3. Publish:
   ```bash
   ./package/dist/bin/quarto.cmd publish posit-connect-cloud tests/docs/manual/publish-connect-cloud/single-doc/document.qmd
   ```

- [ ] Publish succeeds (token refreshed automatically)
- [ ] Check stored token file: `expiresAt` updated to future value
- [ ] No re-authorization prompt

---

## Results Template

| # | Test | Result | Notes |
|---|------|--------|-------|
| 1 | First-time authorization | | |
| 2 | Token persistence | | |
| 3 | Publish revealjs slides | | |
| 4 | Publish website | | |
| 5 | Update existing content | | |
| 6 | Account management | | |
| 7 | CI/CD mode | | |
| 7b | Multiple accounts without ACCOUNT_ID | | |
| 8 | Provider selection | | |
| 9 | posit-cloud deprecation | | |
| 10a | Invalid token error | | |
| 10b | Deleted content | | |
| 10c | --no-render flag | | |
| 11 | Token refresh | | |

## Post-Testing Cleanup

1. Delete test content from the Connect Cloud dashboard
2. Remove stored tokens:
   ```bash
   ./package/dist/bin/quarto.cmd publish accounts
   ```
3. Unset env vars:
   ```bash
   unset POSIT_CONNECT_CLOUD_ENVIRONMENT
   unset POSIT_CONNECT_CLOUD_ACCESS_TOKEN
   unset POSIT_CONNECT_CLOUD_ACCOUNT_ID
   ```
4. Revert any test fixture edits:
   ```bash
   git checkout tests/docs/manual/publish-connect-cloud/
   ```
5. Test artifacts (`_publish.yml`, `_site/`, rendered HTML) are covered by `.gitignore` in each fixture directory.

---

### Tests Docs Manuscript Ipynb Full README (`tests/docs/manuscript/ipynb-full/README.md`)

# Notebooks Now! Submission Template

[](https://mybinder.org/v2/gh/Notebooks-Now/submission-myst-full/HEAD?labpath=notebooks/data-screening.ipynb)

This submission template is for a markdown-based publication with additional supporting notebook and markdown files, as well as supporting data, bibliography, and MyST build configuration.

## Source files

The primary source file for this template is a MyST markdown article. This file may reference notebook cells from other sub-articles to use the output of these cells as figures. It may also reference content from other markdown sub-articles. All of these supplementary source notebooks/articles should be saved in the `notebooks/` folder. It also contains MyST blocks tagged in their metadata as `"part": "abstract"`, or `"part": "availability"` - these cells will be extracted from the document and included as the specified part in the built output.

## Supporting material

### Supplementary data

By convention, all data should be saved in `data/` directory. There is nothing magic about this directory; references to your data from your notebook must still specify the correct relative path.

### Supplementary images

Similar to the `data/` directory, images for figures should be specified in `images/` directory.

### Bibliography

Bibliography entries may be specified two ways, both described in the [MyST docs](https://myst-tools.org/docs/mystjs/citations). They may be listed explicitly in BibTeX format, by convention in the file `references.bib`, and referenced by key using a `cite` MyST role. They may also be specified as inline DOI links. These do not require full bibliographic information; the data is fetched implicitly on build from the DOI.

## MyST configuration

A `myst.yml` file must be provided to configure notebook metadata and exports. This includes authors, affiliations, licenses, keywords, and [much more](https://myst-tools.org/docs/mystjs/frontmatter).

## Building output artifacts

To build PDF/JATS output from your source data, you must have the MyST CLI installed

```bash
npm install myst-cli
```

Then build all exports defined in the `myst.yml` file:

```bash
myst build --all
```

---

### Tests Docs Manuscript Ipynb Single README (`tests/docs/manuscript/ipynb-single/README.md`)

# Notebooks Now! Submission Template

[](https://mybinder.org/v2/gh/Notebooks-Now/submission-quarto-lite/HEAD?labpath=article.ipynb)

This submission template is for a simple notebook-based publication with one source file, supporting data, bibliography, and Quarto build configuration.

## Source file

The source file for this template is a Jupyter notebook. There is not necessarily anything special about this notebook. It may contain markdown cells, code cells, and outputs from common Python packages, including pandas, matplotlib, plotly, seaborn, and altair. 

The notebook has an additional markdown cell at the top of the document which configures Quarto to render the notebook.

## Supporting material

### Supplementary data

By convention, all data should be saved in `data/` directory. There is nothing magic about this directory; references to your data from your notebook must still specify the correct relative path.

### Supplementary images

Similar to the `data/` directory, images for figures should be specified in `images/` directory.

### Bibliography

Bibliography entries may be specific in the document as described in the [Quarto documentation](https://quarto.org/docs/authoring/footnotes-and-citations.html#bibliography-files). 

## Quarto configuration

Configuration for the example is provided by a YAML block that appears as the first cell of the Notebook. 

## Building output artfiacts

To build PDF/JATS output from your source data, you must have the Quarto CLI installed - you can download or learn about installation at <https://quarto.org/docs/download/>

Then render the article using

```
quarto render article.ipynb
```

---

### Tests Docs Manuscript Qmd Full README (`tests/docs/manuscript/qmd-full/README.md`)

# Notebooks Now! Submission Template

[](https://mybinder.org/v2/gh/Notebooks-Now/submission-quarto-lite/HEAD?labpath=article.ipynb)

This submission template is for a simple notebook-based publication with one source file, supporting data, bibliography, and Quarto build configuration.

## Source file

The source file for this template is a Jupyter notebook. There is not necessarily anything special about this notebook. It may contain markdown cells, code cells, and outputs from common Python packages, including pandas, matplotlib, plotly, seaborn, and altair. 

The notebook has an additional markdown cell at the top of the document which configures Quarto to render the notebook.

## Supporting material

### Supplementary data

By convention, all data should be saved in `data/` directory. There is nothing magic about this directory; references to your data from your notebook must still specify the correct relative path.

### Supplementary images

Similar to the `data/` directory, images for figures should be specified in `images/` directory.

### Bibliography

Bibliography entries may be specific in the document as described in the [Quarto documentation](https://quarto.org/docs/authoring/footnotes-and-citations.html#bibliography-files). 

## Quarto configuration

Configuration for the example is provided by a YAML block that appears as the first cell of the Notebook. 

## Building output artfiacts

To build PDF/JATS output from your source data, you must have the Quarto CLI installed - you can download or learn about installation at <https://quarto.org/docs/download/>

Then render the article using

```
quarto render article.ipynb
```

---

### Tests Docs Manuscript Qmd Single README (`tests/docs/manuscript/qmd-single/README.md`)

# Notebooks Now! Submission Template

[](https://mybinder.org/v2/gh/Notebooks-Now/submission-quarto-lite/HEAD?labpath=article.ipynb)

This submission template is for a simple notebook-based publication with one source file, supporting data, bibliography, and Quarto build configuration.

## Source file

The source file for this template is a Jupyter notebook. There is not necessarily anything special about this notebook. It may contain markdown cells, code cells, and outputs from common Python packages, including pandas, matplotlib, plotly, seaborn, and altair. 

The notebook has an additional markdown cell at the top of the document which configures Quarto to render the notebook.

## Supporting material

### Supplementary data

By convention, all data should be saved in `data/` directory. There is nothing magic about this directory; references to your data from your notebook must still specify the correct relative path.

### Supplementary images

Similar to the `data/` directory, images for figures should be specified in `images/` directory.

### Bibliography

Bibliography entries may be specific in the document as described in the [Quarto documentation](https://quarto.org/docs/authoring/footnotes-and-citations.html#bibliography-files). 

## Quarto configuration

Configuration for the example is provided by a YAML block that appears as the first cell of the Notebook. 

## Building output artfiacts

To build PDF/JATS output from your source data, you must have the Quarto CLI installed - you can download or learn about installation at <https://quarto.org/docs/download/>

Then render the article using

```
quarto render article.ipynb
```

---

### Tests Docs Reveal Mini README (`tests/docs/reveal/mini/README.md`)

## Revealjs Mini Demos

This folder contains the source code for the embedded mini demos used in the Quarto [Revealjs documentation](https://quarto.org/docs/presentation/revealjs/).

---

### Tests Docs Axe Scan Sites Brand Light Only README (`tests/docs/axe-scan/sites/brand-light-only/README.md`)

# Fixture site: brand-light-only

A regression fixture for one decision: when does a page have a dark mode to
scan?

`_brand.yml` sets light values only. Quarto answers such a brand with the
colour-scheme link machinery but no dark mode behind it:

```html
<link rel="stylesheet" class="quarto-color-scheme" data-mode="light" ...>
<link rel="stylesheet" class="quarto-color-scheme quarto-color-alternate"
      data-mode="light" href=".../bootstrap-dark-....min.css">
<link rel="stylesheet" class="quarto-color-scheme-extra" data-mode="light" ...>
```

Three traps sit in that markup. Every link is tagged `data-mode="light"`, one
of them carries `quarto-color-alternate`, and one points at a *dark* Bootstrap
build. A probe that asks for `[data-mode]`, or for the alternate class, or for
a `-dark` href, reads all this as "there is a dark theme". It then scans both
themes for nothing and reports duplicate coverage as if it were real. Found on
a live brand.yml site, 2026-08-20.

What the page does **not** carry is the inline before-body script (the one
that defines `window.quartoToggleColorScheme`): Quarto emits it iff the page
really has two modes. Mode discovery reads that marker, so each page here gets
exactly one cell, labelled `default`.

**Do not add dark values to `_brand.yml`.** That would make this an ordinary
two-mode site and delete the regression it guards.

## Why a real project and not a hand-written page

The markup above belongs to Quarto. A hand-written page that reproduces it is
a mock of a contract we do not own, so it would keep passing after Quarto
changed what it emits. This site renders in the smoke test's setup, so the
fixture tracks Quarto. (The sniffer's reading of a distilled copy of this
markup is pinned browser-free in `tests/unit/axe-mode-discovery.test.ts`.)

The mock came first and was correct on the essential point. The real render is
richer: the mock had no `quarto-color-alternate` link and no dark Bootstrap
href, so it never covered two of the three traps.

## Planted violation

One bare `<button></button>` in `index.qmd`, so `button-name` fires and the
single `default` cell demonstrably scanned the page — a clean result would
look exactly like a page that was never scanned at all.

Keep this page minimal. A second, conditional finding would blur the one
comparison the site exists to make.

## Intent against observed


Recorded from a full scan: 2/2 cells ok (one page, two viewports, one mode),
1 finding (1 new, 0 known), axe-core 4.10.3, signature scheme 1.

| signature | impact | scope | n | pages | viewports | themes |
|---|---|---|---|---|---|---|
| `button-name :: button` | critical | localized | 1 | index.html | both | default |

The cells are `index__1440x900__default` and `index__320x568__default` —
no light/dark pair, and no `darkColoured` annotation (the links measure
light).


One mode discovered means one presentation scanned. A page with hand-written
`@media (prefers-color-scheme: dark)` CSS and no Quarto dark mode still scans
once, and its dark-only findings are missed — the documented v1 blind spot
(see the design note's "The theme axis, settled").

### Regenerating

```bash
cd tests/docs/axe-scan/sites/brand-light-only
quarto render
quarto call axe _site          # writes _axe-checks/ next to _quarto.yml
```

Both `_site/` and `_axe-checks/` are throwaway. The smoke test removes them.

---

### Tests Docs Axe Scan Sites Darkly README (`tests/docs/axe-scan/sites/darkly/README.md`)

# Fixture site: darkly

A regression fixture for one decision: a one-mode page is one cell, whatever
colour that mode is.

`theme: darkly` ships a single dark-coloured presentation. Quarto's own
machinery still calls it the light slot: the rendered page says
`body.quarto-light`, and its sole `id="quarto-bootstrap"` link is tagged
`data-mode="dark"` only because the compiled CSS measures as dark (a blackness
sentinel, threshold 40%). So every declaration-level signal is misleading in
one direction or the other.

The scanner must:

- discover **one** mode and label the cell `default` — never `dark`, and never
  a light/dark pair;
- record the measured colour as an annotation — `darkColoured: true` in the
  cell payload, and a note on the console — never in the cell's name.

**Do not add a `light:`/`dark:` theme pair here.** That would make this an
ordinary two-mode site and delete the case it guards.

## Planted violation

One bare `<button></button>` in `index.qmd`, so `button-name` fires and the
`default` cell demonstrably scanned the page.

## Intent against observed


Recorded from a full scan: 2/2 cells ok (one page, two viewports, one mode),
1 finding (1 new, 0 known), axe-core 4.10.3, signature scheme 1.

| signature | impact | scope | n | pages | viewports | themes |
|---|---|---|---|---|---|---|
| `button-name :: button` | critical | localized | 1 | index.html | both | default |

Both cells carry `darkColoured: true`, and the console prints the
dark-coloured note for `index.html`.


### Regenerating

```bash
cd tests/docs/axe-scan/sites/darkly
quarto render
quarto call axe _site          # writes _axe-checks/ next to _quarto.yml
```

Both `_site/` and `_axe-checks/` are throwaway. The smoke test removes them.

---

### Tests Docs Axe Scan Sites Findings README (`tests/docs/axe-scan/sites/findings/README.md`)

# Fixture site: findings

Grouping, the systemic against localized call, the impact spread, baseline
reconciliation, and the report. Also one page Quarto did not render.

The conditional findings that prove the scan matrix live in `../matrix/`, and
light-only brand markup lives in `../brand-light-only/`. Keep them there. This
site's assertions read page lists, and a conditional finding added here would
appear in some cells and not others for reasons unrelated to grouping.

**Do not fix the accessibility problems in this directory.** They are the test
data. `_axe-baseline.json` is committed for the same reason.

## Planted violations

`scope` is what the scanner must conclude, not where the markup lives.
`systemic` means repeated from a shared source, which is three or more elements
or more than one page. `localized` means a one-off.

| rule | impact | placement | scope | why it is here |
|---|---|---|---|---|
| `image-alt` | critical | `_systemic.qmd`, included by every content page | systemic | the critical end of the impact spread, and the systemic case |
| `link-name` | serious | `index.qmd` only | localized | the serious middle, and the localized case |
| `heading-order` | moderate | `index.qmd` only, h2 to h4 | localized | the moderate end |
| `image-alt` | critical | `examples.qmd`, marked `.a11y-accepted-example` | localized | a deliberate teaching example. v1 has no exclude-in-source, so the baseline absorbs it |
| `button-name` x2, `image-alt`, `link-name` | critical and serious | `static/legacy.html` | systemic and localized | signature edge cases in exact markup, and a page with no Quarto DOM |

## The page Quarto did not render

`static/legacy.html` is hand-written and copied through as a resource. A real
Quarto site does this whenever it ships a page Pandoc never touched.

The page earns its place for one reason: nothing in the scanner may require
Quarto's DOM. It has no colour-scheme machinery at all, so mode discovery must
give it one `default` cell per viewport while the Quarto pages around it get
the light/dark pair. The smoke test asserts both on the same scan, so a single
cell is provably a discovery about this page and not the scanner giving up
everywhere.

The page also carries exact markup for the signature-normalization edge cases,
which is convenient but not the reason it is here. Those cases are pinned
browser-free in `tests/unit/axe-signature.test.ts` and
`tests/unit/axe-aggregate.test.ts`.

## Committed baseline

`_axe-baseline.json` exercises all four reconcile outcomes in one file.

| entry | outcome | why |
|---|---|---|
| `image-alt` on the systemic include, `pages: []` | **known** | site-wide acceptance, the right shape for shared chrome |
| `link-name` on `index.html`, `pages: ["index.html"]` | **known** | page-scoped acceptance that covers every page the finding occurs on |
| the `.a11y-accepted-example` image, page-scoped | **known** | stands in for exclude-in-source. When that ships, this entry must turn **stale**, which is a designed test of stale reporting |
| `heading-order`, accepted at `impact: minor` | **new** | planted as moderate, so escalation past the accepted impact re-alerts |
| a signature matching nothing | **stale** | reported for hand-pruning, never removed automatically |

## Intent against observed

axe chooses a minimal unique selector from the DOM as it finds it, so the
signature a planted violation produces is discovered, not decreed. The table
below records what this site actually produces. It is the contract the smoke
test asserts against.


Recorded from a full scan: 14/14 cells ok (three light+dark pages, one
`default` page), 9 findings (6 new, 3 known), axe-core 4.10.3, signature
scheme 1.

| signature | impact | scope | n | pages | viewports | themes | status |
|---|---|---|---|---|---|---|---|
| `button-name :: button[data-widget-target="#panel-*"]` | critical | systemic | 3 | static/legacy.html | both | default | new |
| `image-alt :: img` | critical | systemic | 2 | about.html, index.html | both | both | known |
| `image-alt :: img[width="*"]` | critical | localized | 2 | examples.html | both | both | known |
| `image-alt :: #hero` | critical | localized | 2 | static/legacy.html | both | default | new |
| `button-name :: button[data-widget-target="#settings-dialog"]` | critical | localized | 1 | static/legacy.html | both | default | new |
| `link-name :: a` | serious | localized | 2 | static/legacy.html | both | default | new |
| `link-name :: p > a` | serious | localized | 1 | index.html | both | both | known |
| `heading-order :: h4` | moderate | localized | 1 | index.html | both | both | new |
| `aria-allowed-role :: .navbar-toggler` | minor | systemic | 3 | about.html, examples.html, index.html | 320x568 | both | new |

Stale baseline entries: `region-fixture0`.


### Notes on the table

Three rows deserve a note.

- **`aria-allowed-role :: .navbar-toggler`** comes from Quarto's own navbar, not
  from this fixture. It is a genuine finding and a useful one, systemic across
  three pages and mobile-only. It belongs to Quarto, so it can change or
  disappear on a Quarto upgrade. No test depends on it, and no test depends on
  the total finding count either.
- **`image-alt :: #hero`** collapses two images whose `id` and `style` both
  differ. axe selected them by id (`#hero-14`, `#hero-15`), so the collapse came
  from the instance-id rule and not from the volatile-attribute rule. Either way
  the two become one finding, which is the intent.
- **`link-name :: a`** is the widest collapse the scheme performs. axe keyed
  those two links on `href`, which is dropped as volatile, leaving the bare tag.
  Anything accepted at that signature is accepted for every unnamed `a` on the
  listed pages. That is exactly why the baseline's `pages` scope, and not the
  signature, is what keeps such an acceptance honest.

### Regenerating

```bash
cd tests/docs/axe-scan/sites/findings
quarto render
quarto call axe _site          # writes _axe-checks/ next to _quarto.yml
```

Both `_site/` and `_axe-checks/` are throwaway. The smoke test removes them.

---

### Tests Docs Axe Scan Sites Matrix README (`tests/docs/axe-scan/sites/matrix/README.md`)

# Fixture site: matrix

The two axes that justify scanning every page four times.

Every planted failure here is conditional, and each is reachable by exactly one
route. A cell that took the wrong route therefore fails an assertion, rather
than quietly reporting a duplicate.

**Do not fix the accessibility problems in this directory.** They are the test
data.

Keep unconditional violations out of this site. One would appear in all four
cells of every comparison the site exists to make. There is no
`_axe-baseline.json` here either, which also exercises a first run with no
ledger.

## Planted violations

| rule | impact | placement | reachable by |
|---|---|---|---|
| `color-contrast` | serious | `media.qmd`, inside `@media (prefers-color-scheme: dark)` | emulating a dark colour-scheme preference |
| `color-contrast` | serious | `media.qmd`, inside `@media (max-width: 500px)` | the narrow viewport |
| `color-contrast` | serious | `theme.qmd`, from darkly's own palette | seeding Quarto's colour-scheme localStorage key |

Absence matters as much as presence. The dark-only findings must **not** appear
in the light cells, and the mobile-only finding must **not** appear at
1440x900.

## Two routes into a dark cell

The theme axis tests the two presentations the author ships, not the two
states a user's OS can be in. A dark presentation can live in two places, and a
dark cell must select both:

- `media.qmd`'s failure is in author CSS under `@media (prefers-color-scheme:
  dark)`, so **emulation** reaches it.
- `theme.qmd`'s failure is in darkly itself, so only **seeding
  `localStorage["quarto-color-scheme"] = "alternate"` before navigation**
  reaches it. Emulation cannot: Quarto's themes ignore `prefers-color-scheme`
  unless `respect-user-color-scheme` is set, and this site leaves it at its
  default of false. Without the seed, this failure is invisible and the dark
  cells render the light theme — which is why its assertion is the one that
  fails if mode selection regresses.

Every page here carries the before-body script marker, so discovery must give
every page the light/dark pair. A `default` cell on this site would mean a
dark cell silently vanished from the matrix; the smoke test asserts the pair
on every page. One-mode discovery is covered where it is correct, in
`../brand-light-only/`, `../darkly/` and on `../findings/static/legacy.html`.

## Intent against observed


Recorded from a full scan: 12/12 cells ok, 4 findings (4 new, 0 known),
axe-core 4.10.3, signature scheme 1.

| signature | impact | scope | n | pages | viewports | themes |
|---|---|---|---|---|---|---|
| `color-contrast :: #555555 on #333333` | serious | localized | 1 | media.html | both | dark |
| `color-contrast :: #f8f8f8 on #f0f0f0` | serious | localized | 1 | media.html | 320x568 | both |
| `color-contrast :: #6c757d on #222222` | serious | localized | 1 | theme.html | both | dark |
| `aria-allowed-role :: .navbar-toggler` | minor | systemic | 3 | index.html, media.html, theme.html | 320x568 | both |


`aria-allowed-role :: .navbar-toggler` comes from Quarto's own navbar, not from
this fixture. It can change or disappear on a Quarto upgrade, so no test depends
on it.

### Regenerating

```bash
cd tests/docs/axe-scan/sites/matrix
quarto render
quarto call axe _site          # writes _axe-checks/ next to _quarto.yml
```

Both `_site/` and `_axe-checks/` are throwaway. The smoke test removes them.

---

### Tests Docs Manual Preview Brand Detection README (`tests/docs/manual/preview/brand-detection/README.md`)

# Brand detection during preview (#14593)

Manual fixture for the bug where `quarto preview` ignored a sibling `_brand.yml`
added or removed while the preview was running: `project.brandCache` was resolved
once and never invalidated, so the brand only took effect after restarting the
preview process. RStudio's "Render" button hits the same persistent preview
server, so it observed the same stale brand.

Drive this with the `/quarto-preview-test` workflow (start preview, mutate
`_brand.yml`, force a re-render, inspect output). See the parent
`../README.md` "Test Matrix: Brand Detection (#14593)" for the T28–T32 cases.

## Files

| File | Role |
|------|------|
| `report.qmd` | Minimal Typst doc with a link; `keep-typ: true` so the `.typ` is inspectable |
| `brand-imperial.yml` | A brand setting `primary: imperial-red` (`#BC1E22`) |

`report.typ`, `report.pdf`, `_brand.yml`, and `.quarto/` are scratch produced by a
run — only `report.qmd` and `brand-imperial.yml` are tracked.

## Brand signal

The brand is applied when the kept `report.typ` contains:

```
#show link: set text(fill: rgb("#bc1e22")
```

Present ⇒ brand ON (link colored). Absent ⇒ brand OFF.

## Manual on/off check

1. Start with no `_brand.yml` and preview the doc:
   `quarto preview report.qmd --to typst --no-browser`.
2. **ON** — copy `brand-imperial.yml` to `_brand.yml`, force a re-render (touch
   `report.qmd`), and confirm `report.typ` gains the brand link-color line.
3. **OFF** — remove `_brand.yml`, force another re-render, and confirm the
   line disappears.

Before the fix the brand stayed at its first-resolved state (OFF in the add case,
ON in the remove case) until the preview process was stopped. After the fix, the
resolve-time source-state guard on `project.brandCache` notices the `_brand.yml`
appear/disappear and re-resolves on the next render. A separate `quarto render`
(fresh context) always resolved the brand correctly, which is the disambiguator
that isolates the bug to the persistent preview context.

---

### Tests Docs Manual Preview Sass Cache Crash 14594 README (`tests/docs/manual/preview/sass-cache-crash-14594/README.md`)

# Sass cache reuse during preview (#14594)

Manual fixture for the bug where `quarto preview` of a book (or website) project
crashed with `BadResource: Bad resource ID` while recompiling the theme Sass on a
re-render. `SassCache.cleanup()` closed the session `Deno.Kv` handle but left the
stale instance in the module-level `_sassCache` registry, so the next
`sassCache(path, temp)` resolve reused the now-closed handle.

On each re-render the serve/watch reload path runs `refreshProjectConfig`
(`src/project/serve/watch.ts`, ~line 277 for HTML; also on any config change,
~line 305), which calls `project.cleanup()` and closes that session Sass KV
handle. The next render must open a fresh handle. RStudio's and Positron's live
preview hit the same persistent server, so they saw the same crash.

Drive this with the `/quarto-preview-test` workflow. See the parent `../README.md`
"Test Matrix: Sass Cache Reuse (#14594)" for the T34-T36 cases.

## Files

| File | Role |
|------|------|
| `_quarto.yml` | Flat multi-chapter book with `theme: cosmo` + `css: styles.css` (routes the theme through the session Sass cache) |
| `index.qmd`, `chapter1.qmd`, `chapter2.qmd` | Minimal chapters; a project preview re-renders these |
| `styles.css` | Custom CSS so a real Sass compile runs alongside the cosmo theme |

`_book/` and `.quarto/` are scratch produced by a run and are git-ignored.

## Crash signal

Before the fix, after the first re-render the preview log shows:

```
BadResource: Bad resource ID
```

and pages stop reloading. After the fix, every re-render completes and reloads.

Deterministic unit coverage lives in `tests/unit/sass-cache.test.ts`; this fixture
is the interactive regression guide the unit test can't cover (live serve/watch
path).

---

### Tests Docs Smoke All 2024 02 12 Issue 8712 README (`tests/docs/smoke-all/2024/02/12/issue-8712/README.md`)

## A Quarto Manuscript Template

This is a template repo for generating a manuscript from Quarto that accompanies the tutorial at: [Quarto Manuscripts: RStudio](https://quarto.org/docs/manuscripts/authoring/rstudio.html)

---

### Tests Docs Test Jupyter (`tests/docs/test-jupyter.md`)

---
title: Juptyer Test
jupyter:
  kernelspec:
    display_name: Python 3
    language: python
    name: python3
---

---

### Tests Docs Unpaired Md (`tests/docs/unpaired-md.md`)

---
jupyter:
  kernelspec:
    display_name: Python 3
    language: python
    name: python3
---

```python
print("Hello World")
```

```python

```

---

### Tests Docs Books Visualization Curriculum Requirements (`tests/docs/books/visualization-curriculum/requirements.txt`)

altair==4.1.0
altair-data-server==0.4.1
altair-saver==0.5.0
altair-viewer==0.3.0
anyio==3.0.1
appnope==0.1.2
argon2-cffi==20.1.0
async-generator==1.10
attrs==21.2.0
Babel==2.9.1
backcall==0.2.0
bleach==3.3.0
certifi==2020.12.5
cffi==1.14.5
chardet==4.0.0
decorator==5.0.9
defusedxml==0.7.1
entrypoints==0.3
idna==3.1
ipykernel==5.5.5
ipython==7.23.1
ipython-genutils==0.2.0
jedi==0.18.0
Jinja2==3.0.0
json5==0.9.5
jsonschema==3.2.0
jupyter-client==6.1.12
jupyter-core==4.7.1
jupyter-server==1.7.0
jupyterlab==3.0.15
jupyterlab-pygments==0.1.2
jupyterlab-server==2.5.1
MarkupSafe==2.0.0
matplotlib-inline==0.1.2
mistune==0.8.4
nbclassic==0.2.8
nbclient==0.5.3
nbconvert==6.0.7
nbformat==5.1.3
nest-asyncio==1.5.1
notebook==6.3.0
numpy==1.20.3
packaging==20.9
pandas==1.2.4
pandocfilters==1.4.3
parso==0.8.2
pexpect==4.8.0
pickleshare==0.7.5
portpicker==1.3.9
prometheus-client==0.10.1
prompt-toolkit==3.0.18
ptyprocess==0.7.0
pycparser==2.20
Pygments==2.9.0
pyparsing==2.4.7
pyrsistent==0.17.3
python-dateutil==2.8.1
pytz==2021.1
PyYAML==5.4.1
pyzmq==22.0.3
requests==2.25.1
selenium==3.141.0
Send2Trash==1.5.0
six==1.16.0
sniffio==1.2.0
terminado==0.9.5
testpath==0.4.4
toolz==0.11.1
tornado==6.1
traitlets==5.0.5
urllib3==1.26.4
vega-datasets==0.9.0
wcwidth==0.2.5
webencodings==0.5.1
websocket-client==0.59.0

---

### Tests Docs Manual Preview 10392 Project Preview Stale Cache After Edit (`tests/docs/manual/preview/10392-project-preview-stale-cache-after-edit.md`)

# Project preview serves stale rendered output after source edit (#10392)

Manual preview test for the `fileInformationCache.fullMarkdown` staleness bug on the project preview path (website / book). Reproduces deterministically.

## What this catches

In a project preview, after editing a non-index `.qmd`:

- The HTML file in `_site/` IS regenerated (mtime advances).
- The HTML body content does NOT contain the edit.
- The browser reload signal fires but the served HTML is stale.
- Stopping and restarting `quarto preview` clears the bug for the first edit; the second edit reproduces it.

**Root cause:** the persistent `ProjectContext` returned by `watcher.project()` owns a long-lived `fileInformationCache`. The HTTP-handler render in `src/project/serve/serve.ts` reuses that context. Without invalidation on watcher events, `projectResolveFullMarkdownForFile` (`src/project/project-shared.ts`) returned the pre-edit expanded markdown for the post-watcher HTTP-handler render call. The fix invalidates on watcher events (surgical) and adds an `mtime + size` freshness check to the cache (defense-in-depth).

## Setup

Use the existing website fixture at `tests/docs/manual/preview/project-preview/`. It contains:

```
_quarto.yml          # type: website
index.qmd            # home page
about.qmd            # non-index page used by T1, T2, T6, T7
styles.css
```

The bug does not require code execution; pure prose edits to `about.qmd` trigger it. T7 needs a Jupyter-flavored input — use `tests/docs/manual/preview/keep-ipynb.qmd` or any `.qmd` with a Python code cell.

## P1: Critical

### T1: Edit a non-index `.qmd`, observe stale HTML body

- **Setup:** Use the fixture `tests/docs/manual/preview/project-preview/`. The default body of `about.qmd` is `About this site`.
- **Steps:**
  1. `cd tests/docs/manual/preview/project-preview && quarto preview`.
  2. Wait for preview to load and the default browser tab to open.
  3. Navigate the browser to `http://localhost:<port>/about.html`.
  4. In `about.qmd`, replace `About this site` with `MARKER-UNIQUE-STRING`. Save.
  5. Wait 10 seconds (let watcher + re-render settle).
  6. `Select-String -Path '_site/about.html' -Pattern MARKER-UNIQUE-STRING`
  7. `curl http://127.0.0.1:<port>/about.html | Select-String MARKER-UNIQUE-STRING`
  8. Reload the browser tab.
- **Expected (after fix):** Step 6 and step 7 both return a match. Step 8 shows the new paragraph.
- **Catches:** Persistent `watcher.project().fileInformationCache.fullMarkdown` returning stale expanded markdown to the HTTP-handler `renderProject` call site in `src/project/serve/serve.ts`.

### T2: Repeat the edit a second time

- **Setup:** Same as T1. Preview already running.
- **Steps:**
  1. After T1 (do not restart preview).
  2. Edit `about.qmd` again, change `MARKER-UNIQUE-STRING` to `MARKER-SECOND-STRING`. Save.
  3. Wait 10 seconds.
  4. Repeat steps 6-8 of T1 with the new string.
- **Expected (after fix):** Both checks find the new string.
- **Catches:** Second-edit regression — verifies the fix invalidates the cache on every edit, not only on the first.

### T3: Edit `index.qmd`

- **Setup:** Same project.
- **Steps:**
  1. Edit `index.qmd`, add `INDEX-MARKER.` paragraph. Save.
  2. Wait 10 seconds.
  3. Check `_site/index.html` and the served `/index.html` for `INDEX-MARKER`.
- **Expected:** Both find the marker. This case worked before the fix too — keep it in the suite to confirm no regression on the index path.

## P2: Important

### T4: Edit affects sibling pages via cross-reference

- **Setup:** Extend the fixture: add a `secondary.qmd` next to `about.qmd` containing a figure with `#fig-x`, then in `about.qmd` reference it via `@fig-x`.
- **Steps:**
  1. Preview running.
  2. Edit the `#fig-x` label in `secondary.qmd`.
  3. Verify `_site/about.html` cross-reference text updates.
- **Catches:** Cache invalidation does not propagate to other files in the project that reference the changed file. Lower priority because the immediate symptom (stale body on the directly-edited file) is the main bug.

### T7: Concurrent saves during in-flight HTTP-handler render (Jupyter input)

This case proves the surgical-fix invalidation is correctly serialized with the render queue. Before the fix at the queue level, `invalidateForFile` ran **before** `submitRender`, so a concurrent in-flight render (typically the HTTP-handler's `renderProject` call) could lose its transient `.quarto_ipynb` mid-read and fail intermittently. After the fix, invalidation runs inside the `submitRender` callback so the queue serializes it with prior renders.

- **Setup:** Use a Jupyter `.qmd` (e.g., `tests/docs/manual/preview/keep-ipynb.qmd` rendered into a website project, or copy a Python-cell `.qmd` into `project-preview/`). The input must produce a transient `.quarto_ipynb` for the race to be reachable.
- **Steps:**
  1. `quarto preview` against the project containing the Jupyter `.qmd`.
  2. Open the page in the browser to trigger an HTTP-handler render that reads the transient `.quarto_ipynb`.
  3. While the HTTP-handler render is still in flight (best done by editing during a long-running Python cell), save the `.qmd` 5 times in quick succession (≤ 1s between saves). Aim to fire the watcher while step 2's render is still executing.
  4. Watch the `quarto preview` console for errors of the form "file not found" / "cannot read `.quarto_ipynb`" / `ENOENT`.
- **Expected (after fix):** No file-not-found errors in the console (`ENOENT` / `BadResource` / "cannot read `.quarto_ipynb`"). All renders complete cleanly. The HTML reflects the last *coalesced* render — under a sub-second burst the watcher collapses the saves into a single execution, so the final body may correspond to the first read of the burst rather than the very last keystroke. That coalescing is render-scheduling behavior, not a cache-staleness failure; a single edit or edits seconds apart always render fresh. The trailing-render-after-coalesce gap is tracked separately as a follow-up.
- **Catches:** Race between watcher-triggered `invalidateForFile` and an in-flight render holding the transient notebook open. Window is narrow but reproducible on slow Python cells (use a `time.sleep(5)` cell to widen it). This case asserts transient-notebook safety under concurrency, not last-keystroke freshness.

## P3: Polish

### T5: `freeze: auto` + pure markdown edit

- **Setup:** Add `execute: freeze: auto` to `_quarto.yml`.
- **Steps:**
  1. T1 with `freeze: auto`.
- **Expected:** Same outcome as T1 (freeze should not affect plain markdown).

### T6: WebSocket reload target points at the right page

- **Setup:** Browser open on `/about.html`.
- **Steps:** T1, watch the browser console.
- **Expected:** WebSocket reload signal fires; the page reloads with the new content.

## Blast-radius regression protocol

The fix touches two surfaces with different reach:

- **A — `invalidateForFile(input)` loop in `src/project/serve/watch.ts`.** Runs in **project preview only**. Calls a function already used elsewhere (#14281, `preview.ts`); only the call site is new.
- **B — `mtime + size` guard in `projectResolveFullMarkdownForFile` (`src/project/project-shared.ts`).** Runs **everywhere** the function is called: every render (single doc + project), the single-file project type, `quarto inspect`, and the knitr (`rmd.ts`) and jupyter (`jupyter.ts`) execute paths.

Outside preview, B is functionally a no-op (a one-shot context never sees the source change mid-life, so the guard always agrees with the old cache), but it adds one `Deno.statSync` per cache hit. The cases below confirm both that the fix works on its target surfaces and that the wide change B introduces no regression or perceptible cost elsewhere.

These are interactive preview tests (require real file-save events) and cannot run as automated smoke tests. Drive them with `/quarto-preview-test`. The `#14281` accumulation matrix in `README.md` covers transient-notebook *accumulation*; the cases here cover *content freshness after an edit*, a different axis.

### P1: Critical

#### T8: Single-document preview — freshness after body edit (guard B, single-file path)

- **Setup:** `tests/docs/manual/preview/plain.qmd` (pure markdown, no project).
- **Steps:**
  1. `quarto preview tests/docs/manual/preview/plain.qmd`.
  2. Edit a body line to `MARKER-T8`. Save. Wait 5s.
  3. `curl http://127.0.0.1:<port>/ | Select-String MARKER-T8` and reload the browser.
- **Expected:** Both find `MARKER-T8`.
- **Catches:** Guard B regressing the single-file project type's resolve path — the one surface where single-doc preview leans on B alone (no watcher invalidation A).

#### T9: Project + Jupyter `.qmd` — freshness after edit, no accumulation

- **Setup:** In `project-preview/`, add `pycell.qmd` with a Python cell (`print("v1")`) and link it from `index.qmd`. Requires the Python test env.
- **Steps:**
  1. `cd tests/docs/manual/preview/project-preview && quarto preview`.
  2. Open `/pycell.html`. Change the cell to `print("v2")`. Save. Wait 10s.
  3. Check `_site/pycell.html` and the served page for `v2`.
  4. `ls *.quarto_ipynb*` — confirm at most one `pycell.quarto_ipynb`, no `_1`/`_2` variants.
- **Expected:** Fresh `v2` output; at most one transient notebook; zero after Ctrl+C.
- **Catches:** A interacting badly with the #14281 transient cleanup, or stale jupyter output surviving the edit.

#### T10: Project + raw `.ipynb` input — source preserved, content fresh

- **Setup:** Copy a native `.ipynb` (the `T11` notebook from the README #14281 matrix, or any executed notebook) into `project-preview/` as `notebook.ipynb`; link from `index.qmd`.
- **Steps:**
  1. `quarto preview` the project. Open `/notebook.html`.
  2. Edit a markdown cell in `notebook.ipynb` to add `MARKER-T10`. Save. Wait 10s.
  3. After Ctrl+C, confirm `notebook.ipynb` still exists on disk.
- **Expected:** `MARKER-T10` in served HTML; `notebook.ipynb` **not deleted** (its cache entry has `transient = false`, so `invalidateForFile` must not `safeRemoveSync` it).
- **Catches:** The new A call site firing `invalidateForFile` on a user `.ipynb` and wrongly deleting the source.

### P2: Important

#### T11: Project + knitr `.qmd` — freshness after edit (regression for guard B on `rmd.ts`)

- **Setup:** In `project-preview/`, add `rcell.qmd` with `engine: knitr` and an R cell (`print("r-v1")`); link from `index.qmd`. Requires the R test env.
- **Steps:** Preview the project; change the cell to `print("r-v2")`; save; wait 10s; check served `/rcell.html`.
- **Expected:** Fresh `r-v2`. No `.quarto_ipynb` files (knitr has no jupyter intermediate).
- **Catches:** Guard B regressing the knitr resolve path (`src/execute/rmd.ts:245`).

#### T12: Format-change + stale-body invalidation coexist in one project preview

- **Setup:** `project-preview/about.qmd`, default `format: html`.
- **Steps:**
  1. Preview the project. Edit `about.qmd` body to `MARKER-T12a`. Save. Confirm fresh (the #10392 path, A).
  2. Without restarting, edit `about.qmd` frontmatter to `format: typst`. Save. Trigger one render.
- **Expected:** Step 1 serves fresh body; step 2 detects the format change (#14533) without the two invalidation sites — `watch.ts` (A) and `preview.ts` compatibility check — conflicting.
- **Catches:** Double-invalidation interaction (over-eager eviction → spurious 404, or under-eager → stale format). Cross-ref `README.md` #14533 T20–T25.

### P3: Polish

#### T13: `quarto inspect` after an edit (guard B + added `statSync`)

- **Setup:** Any project file.
- **Steps:** `quarto inspect <file>`, edit the file, `quarto inspect <file>` again in the same shell.
- **Expected:** Both succeed; second reflects the edit. No error from the added `Deno.statSync`.
- **Catches:** Guard B or the stat breaking the inspect resolve calls (`src/inspect/inspect.ts`).

#### T14: Book / many-file project — perf sanity for per-hit `statSync`

- **Setup:** A multi-chapter book (10+ inputs), e.g. `quarto create project book`.
- **Steps:** `quarto render` the book cold, then `quarto preview` and edit one chapter.
- **Expected:** No perceptible slowdown vs `upstream/main`; edited chapter renders fresh.
- **Catches:** The extra `Deno.statSync` per cache hit (guard B) becoming a noticeable cost at scale. Expected negligible; this is the explicit check.

## Cleanup

`quarto preview` exit (Ctrl+C). No persistent state to clean up beyond `_site/` which the next preview run rebuilds. Remove any ad-hoc fixtures added for T9–T14 (`pycell.qmd`, `notebook.ipynb`, `rcell.qmd`) — they are not committed.

## Related

- `tests/docs/manual/preview/README.md` — manual preview test matrix
- `tests/docs/manual/preview/14281-quarto-ipynb-accumulation.qmd` — neighboring manual test (different bug, similar structure)

---

### Tests Docs Manual Preview 14359 Positron Kill Quarto Ipynb (`tests/docs/manual/preview/14359-positron-kill-quarto-ipynb.md`)

# Manual Test: .quarto_ipynb cleanup on ungraceful termination (#14359)

## Purpose

Verify that `.quarto_ipynb` files are cleaned up after render even when preview
is killed ungracefully (e.g., Positron terminal bin icon). This tests the fix
that restores immediate deletion in `cleanupNotebook()`.

**Why manual?** Ungraceful termination bypasses normal cleanup handlers.
The fix ensures deletion happens right after execution, before any signal
could interrupt the process.

## Automated coverage

The smoke-all test `tests/docs/smoke-all/2025/05/21/keep_ipynb_single-file/14359.qmd`
verifies the core behavior (file deleted after render). This manual test
covers the interactive preview scenario.

## Steps

1. Open Positron (or VS Code with Quarto extension)
2. Open `tests/docs/manual/preview/14281-quarto-ipynb-accumulation.qmd`
3. Start Quarto Preview (terminal or Quarto extension)
4. Wait for first render to complete
5. Check directory: `ls *.quarto_ipynb*` — should be zero files (file was deleted after render)
6. Edit and save the file to trigger a re-render
7. After re-render completes, check again — still zero `.quarto_ipynb` files
8. Kill preview ungracefully (Positron terminal bin icon / close terminal)
9. Check directory — still zero `.quarto_ipynb` files

## Expected

- Zero `.quarto_ipynb` files at all times (the file is deleted immediately
  after each execution, not deferred to cleanup handlers)
- Ungraceful termination does not leave stale files because deletion already
  happened

## Note on Positron fix

Quarto extension v1.131.0 (posit-dev/positron#13006) now sends
`previewTerminateRequest()` when the terminal is closed, giving Quarto
a clean shutdown. Our fix (immediate deletion after execution) and their
fix (clean shutdown signal) are complementary — either alone prevents
stale files, both together provide defense in depth.

## Related

- #14281 — within-session accumulation (fixed by PR #14287)
- #12780 — `keep-ipynb` support (PR #12793 introduced this regression)
- posit-dev/positron#13006 — Killing Quarto Preview should exit process (fixed in extension v1.131.0)

---

### Tests Docs Project Ai Config Files AGENTS (`tests/docs/project/ai-config-files/AGENTS.md`)

---
title: "Agents Configuration"
---

This is an agents.md configuration file that should be ignored during project scanning.

---

### Tests Docs Project Ai Config Files CLAUDE (`tests/docs/project/ai-config-files/CLAUDE.md`)

---
title: "Claude Configuration"
---

This is an AI assistant configuration file that should be ignored during project scanning.

---

### Tests Docs Project Output Dir Parent Ref Marker (`tests/docs/project/output-dir-parent-ref/marker.txt`)

This file should NOT be deleted when rendering with output-dir: ../output-dir-parent-ref
This pattern references the project directory via parent traversal.

---

### Tests Docs Project Output Dir Safety Marker (`tests/docs/project/output-dir-safety/marker.txt`)

This file should NOT be deleted when rendering with output-dir: ./
If this file is missing after render, the bug is present.

---

### Tests Docs Playwright Html Code Highlight  Body (`tests/docs/playwright/html/code-highlight/_body.md`)

## Light and Dark Mode Testing

This document can be viewed in both light mode and dark mode to test code highlighting in both color schemes.

```{.julia}
function divide_floats(x::Float64, y::Float64)
    return x / y
end
```

And here the inline version `function divide_floats(x::Float64, y::Float64)`{.julia}.

::: {.callout-note}
The tests will automatically check the highlighting in both light and dark modes using Playwright's color scheme testing capabilities.
:::

---

### Tests Docs Directives Include Test1 Sub Include 1 (`tests/docs/directives/include/test1/sub/include-1.md`)

# Inner-1

Here is a [relative link](../link.qmd).

## inherited fixup

{{< include sub/include-2.md >}}

## forced fixup

{{< include sub/include-2.md fixup=true >}}

## forced no fixup

{{< include sub/include-2.md fixup=false >}}

---

### Tests Docs Smoke All 2023 09 13 Knitr Warning Panel Layout (`tests/docs/smoke-all/2023/09/13/knitr-warning-panel-layout.md`)

---
title: Knitr Table Test
keep-md: true
---

::: {#tbl-tables .cell layout-ncol="2" tbl-cap='Tables'}

```{.r .cell-code}
library(knitr)
```

::: {.cell-output .cell-output-stderr}

```
Warning: le package 'knitr' a été compilé avec la version R 4.2.3
```

:::

```{.r .cell-code}
kable(head(cars), caption = "Cars {#tbl-cars}")
```

::: {.cell-output-display}

Table: Cars {#tbl-cars}

| speed | dist |
| ----: | ---: |
|     4 |    2 |
|     4 |   10 |
|     7 |    4 |
|     7 |   22 |
|     8 |   16 |
|     9 |   10 |

:::

```{.r .cell-code}
kable(head(pressure), caption = "Pressure {#tbl-pressure}")
```

::: {.cell-output-display}

Table: Pressure {#tbl-pressure}

| temperature | pressure |
| ----------: | -------: |
|           0 |   0.0002 |
|          20 |   0.0012 |
|          40 |   0.0060 |
|          60 |   0.0300 |
|          80 |   0.0900 |
|         100 |   0.2700 |

:::
:::

See @tbl-cars for more information.

---

### Tests Docs Smoke All 2023 09 18 6871 Adoc (`tests/docs/smoke-all/2023/09/18/6871-adoc.md`)

---
title: Knitr Table Test
format: asciidoc
keep-md: true
\_quarto:
tests:
asciidoc:
ensureFileRegexMatches: # The checks here are looser than I'd like, because checking for asciidoc # nested structures in line-by-line regexes seems impossible, but this # is the only smoke-all test we can run in asciidoc.. # # TODO this is a problem. - - "\\[\\[tbl-cars\\]\\]" - "\\[\\[tbl-pressure\\]\\]" - "\\[#tbl-tables\\]" # TODO understand the difference between [[foo]] and [#foo]. From my reading of the asciidoc docs it seems ok but we should double-check. - "\\. Tables" - "\\. Cars" - "\\. Pressure" - []
---

::: {#tbl-tables .cell layout-ncol="2" tbl-cap='Tables'}

```{.r .cell-code}
library(knitr)
```

::: {.cell-output .cell-output-stderr}

```
Warning: le package 'knitr' a été compilé avec la version R 4.2.3
```

:::

```{.r .cell-code}
kable(head(cars), caption = "Cars {#tbl-cars}")
```

::: {.cell-output-display}

Table: Cars {#tbl-cars}

| speed | dist |
| ----: | ---: |
|     4 |    2 |
|     4 |   10 |
|     7 |    4 |
|     7 |   22 |
|     8 |   16 |
|     9 |   10 |

:::

```{.r .cell-code}
kable(head(pressure), caption = "Pressure {#tbl-pressure}")
```

::: {.cell-output-display}

Table: Pressure {#tbl-pressure}

| temperature | pressure |
| ----------: | -------: |
|           0 |   0.0002 |
|          20 |   0.0012 |
|          40 |   0.0060 |
|          60 |   0.0300 |
|          80 |   0.0900 |
|         100 |   0.2700 |

:::
:::

See @tbl-cars for more information.

---

### Tests Docs Smoke All 2023 09 18 6871 (`tests/docs/smoke-all/2023/09/18/6871.md`)

---
title: Knitr Table Test
format: jats
keep-md: true
_quarto:
  tests:
    jats:
      ensureJatsXpath:
        - - '//fig//fig[@id="tbl-cars"]//table'
          - '//fig//fig[@id="tbl-cars"]//caption'
          - '//fig//fig[@id="tbl-pressure"]//table'
          - '//fig//fig[@id="tbl-pressure"]//caption'
          - '//fig[@id="tbl-tables"]/caption'
        - []
---

::: {#tbl-tables .cell layout-ncol="2" tbl-cap='Tables'}

```{.r .cell-code}
library(knitr)
```

::: {.cell-output .cell-output-stderr}

```
Warning: le package 'knitr' a été compilé avec la version R 4.2.3
```

:::

```{.r .cell-code}
kable(head(cars), caption = "Cars {#tbl-cars}")
```

::: {.cell-output-display}

Table: Cars {#tbl-cars}

| speed | dist |
| ----: | ---: |
|     4 |    2 |
|     4 |   10 |
|     7 |    4 |
|     7 |   22 |
|     8 |   16 |
|     9 |   10 |

:::

```{.r .cell-code}
kable(head(pressure), caption = "Pressure {#tbl-pressure}")
```

::: {.cell-output-display}

Table: Pressure {#tbl-pressure}

| temperature | pressure |
| ----------: | -------: |
|           0 |   0.0002 |
|          20 |   0.0012 |
|          40 |   0.0060 |
|          60 |   0.0300 |
|          80 |   0.0900 |
|         100 |   0.2700 |

:::
:::

See @tbl-cars for more information.

</details>

---

### Tests Docs Smoke All 2023 09 18 6873 (`tests/docs/smoke-all/2023/09/18/6873.md`)

---
title: "table"
format: html
keep-md: true
---


::: {#tbl-unetable .cell}

```{.r .cell-code}
library(gt)
start_date <- "2010-06-07"
end_date <- "2010-06-14"
sp500 |>
  dplyr::filter(date >= start_date & date <= end_date) |>
  dplyr::select(-adj_close) |>
  gt() |>
  tab_header(
    title = "S&P 500",
    subtitle = glue::glue("{start_date} to {end_date}")
  ) |>
  fmt_currency() |>
  fmt_date(columns = date, date_style = "wd_m_day_year") |>
  fmt_number(columns = volume, suffixing = TRUE)
```

::: {.cell-output-display}


```
/* Detailed source-code truncated for AI context efficiency. */
```


:::
:::

---

### Tests Docs Smoke All 2024 02 12 8711.Pdf (`tests/docs/smoke-all/2024/02/12/8711.pdf.md`)

---
title: "Untitled"
format:
  pdf:
    keep-md: true
crossref:
  custom:
    - kind: float
      key: out
      latex-env: notoutput
      reference-prefix: Output
---


::: {.cell}

```{.r .cell-code}
knitr::kable(mtcars)
```

::: {.cell-output-display}


|                    |  mpg| cyl|  disp|  hp| drat|    wt|  qsec| vs| am| gear| carb|
|:-------------------|----:|---:|-----:|---:|----:|-----:|-----:|--:|--:|----:|----:|
|Mazda RX4           | 21.0|   6| 160.0| 110| 3.90| 2.620| 16.46|  0|  1|    4|    4|
|Mazda RX4 Wag       | 21.0|   6| 160.0| 110| 3.90| 2.875| 17.02|  0|  1|    4|    4|
|Datsun 710          | 22.8|   4| 108.0|  93| 3.85| 2.320| 18.61|  1|  1|    4|    1|
|Hornet 4 Drive      | 21.4|   6| 258.0| 110| 3.08| 3.215| 19.44|  1|  0|    3|    1|
|Hornet Sportabout   | 18.7|   8| 360.0| 175| 3.15| 3.440| 17.02|  0|  0|    3|    2|
|Valiant             | 18.1|   6| 225.0| 105| 2.76| 3.460| 20.22|  1|  0|    3|    1|
|Duster 360          | 14.3|   8| 360.0| 245| 3.21| 3.570| 15.84|  0|  0|    3|    4|
|Merc 240D           | 24.4|   4| 146.7|  62| 3.69| 3.190| 20.00|  1|  0|    4|    2|
|Merc 230            | 22.8|   4| 140.8|  95| 3.92| 3.150| 22.90|  1|  0|    4|    2|
|Merc 280            | 19.2|   6| 167.6| 123| 3.92| 3.440| 18.30|  1|  0|    4|    4|
|Merc 280C           | 17.8|   6| 167.6| 123| 3.92| 3.440| 18.90|  1|  0|    4|    4|
|Merc 450SE          | 16.4|   8| 275.8| 180| 3.07| 4.070| 17.40|  0|  0|    3|    3|
|Merc 450SL          | 17.3|   8| 275.8| 180| 3.07| 3.730| 17.60|  0|  0|    3|    3|
|Merc 450SLC         | 15.2|   8| 275.8| 180| 3.07| 3.780| 18.00|  0|  0|    3|    3|
|Cadillac Fleetwood  | 10.4|   8| 472.0| 205| 2.93| 5.250| 17.98|  0|  0|    3|    4|
|Lincoln Continental | 10.4|   8| 460.0| 215| 3.00| 5.424| 17.82|  0|  0|    3|    4|
|Chrysler Imperial   | 14.7|   8| 440.0| 230| 3.23| 5.345| 17.42|  0|  0|    3|    4|
|Fiat 128            | 32.4|   4|  78.7|  66| 4.08| 2.200| 19.47|  1|  1|    4|    1|
|Honda Civic         | 30.4|   4|  75.7|  52| 4.93| 1.615| 18.52|  1|  1|    4|    2|
|Toyota Corolla      | 33.9|   4|  71.1|  65| 4.22| 1.835| 19.90|  1|  1|    4|    1|
|Toyota Corona       | 21.5|   4| 120.1|  97| 3.70| 2.465| 20.01|  1|  0|    3|    1|
|Dodge Challenger    | 15.5|   8| 318.0| 150| 2.76| 3.520| 16.87|  0|  0|    3|    2|
|AMC Javelin         | 15.2|   8| 304.0| 150| 3.15| 3.435| 17.30|  0|  0|    3|    2|
|Camaro Z28          | 13.3|   8| 350.0| 245| 3.73| 3.840| 15.41|  0|  0|    3|    4|
|Pontiac Firebird    | 19.2|   8| 400.0| 175| 3.08| 3.845| 17.05|  0|  0|    3|    2|
|Fiat X1-9           | 27.3|   4|  79.0|  66| 4.08| 1.935| 18.90|  1|  1|    4|    1|
|Porsche 914-2       | 26.0|   4| 120.3|  91| 4.43| 2.140| 16.70|  0|  1|    5|    2|
|Lotus Europa        | 30.4|   4|  95.1| 113| 3.77| 1.513| 16.90|  1|  1|    5|    2|
|Ford Pantera L      | 15.8|   8| 351.0| 264| 4.22| 3.170| 14.50|  0|  1|    5|    4|
|Ferrari Dino        | 19.7|   6| 145.0| 175| 3.62| 2.770| 15.50|  0|  1|    5|    6|
|Maserati Bora       | 15.0|   8| 301.0| 335| 3.54| 3.570| 14.60|  0|  1|    5|    8|
|Volvo 142E          | 21.4|   4| 121.0| 109| 4.11| 2.780| 18.60|  1|  1|    4|    2|


:::
:::


::: {#out-2}


::: {.cell}
::: {.cell-output .cell-output-stdout}

```
Call:
   aov(formula = yield ~ block + N * P + K, data = npk)

Terms:
                   block        N        P        K      N:P Residuals
Sum of Squares  343.2950 189.2817   8.4017  95.2017  21.2817  218.9033
Deg. of Freedom        5        1        1        1        1        14

Residual standard error: 3.954232
Estimated effects may be unbalanced
```


:::
:::


Sample ANOVA output

:::

See @out-2

---

### Tests Docs Smoke All 2024 02 13 8711.Pdf (`tests/docs/smoke-all/2024/02/13/8711.pdf.md`)

---
title: "Untitled"
format: latex
crossref:
  custom:
    - kind: float
      key: out
      latex-env: notoutput
      reference-prefix: Output
      caption-location: top
_quarto:
  tests:
    latex:
      ensureFileRegexMatches:
        - ["\\\\begin\\{longtable"]
        - []
---

:::{#tbl-1}

::: {.cell}
::: {.cell-output-display}

|                     |  mpg | cyl |  disp |  hp | drat |    wt |  qsec |  vs |  am | gear | carb |
| :------------------ | ---: | --: | ----: | --: | ---: | ----: | ----: | --: | --: | ---: | ---: |
| Mazda RX4           | 21.0 |   6 | 160.0 | 110 | 3.90 | 2.620 | 16.46 |   0 |   1 |    4 |    4 |
| Mazda RX4 Wag       | 21.0 |   6 | 160.0 | 110 | 3.90 | 2.875 | 17.02 |   0 |   1 |    4 |    4 |
| Datsun 710          | 22.8 |   4 | 108.0 |  93 | 3.85 | 2.320 | 18.61 |   1 |   1 |    4 |    1 |
| Hornet 4 Drive      | 21.4 |   6 | 258.0 | 110 | 3.08 | 3.215 | 19.44 |   1 |   0 |    3 |    1 |
| Hornet Sportabout   | 18.7 |   8 | 360.0 | 175 | 3.15 | 3.440 | 17.02 |   0 |   0 |    3 |    2 |
| Valiant             | 18.1 |   6 | 225.0 | 105 | 2.76 | 3.460 | 20.22 |   1 |   0 |    3 |    1 |
| Duster 360          | 14.3 |   8 | 360.0 | 245 | 3.21 | 3.570 | 15.84 |   0 |   0 |    3 |    4 |
| Merc 240D           | 24.4 |   4 | 146.7 |  62 | 3.69 | 3.190 | 20.00 |   1 |   0 |    4 |    2 |
| Merc 230            | 22.8 |   4 | 140.8 |  95 | 3.92 | 3.150 | 22.90 |   1 |   0 |    4 |    2 |
| Merc 280            | 19.2 |   6 | 167.6 | 123 | 3.92 | 3.440 | 18.30 |   1 |   0 |    4 |    4 |
| Merc 280C           | 17.8 |   6 | 167.6 | 123 | 3.92 | 3.440 | 18.90 |   1 |   0 |    4 |    4 |
| Merc 450SE          | 16.4 |   8 | 275.8 | 180 | 3.07 | 4.070 | 17.40 |   0 |   0 |    3 |    3 |
| Merc 450SL          | 17.3 |   8 | 275.8 | 180 | 3.07 | 3.730 | 17.60 |   0 |   0 |    3 |    3 |
| Merc 450SLC         | 15.2 |   8 | 275.8 | 180 | 3.07 | 3.780 | 18.00 |   0 |   0 |    3 |    3 |
| Cadillac Fleetwood  | 10.4 |   8 | 472.0 | 205 | 2.93 | 5.250 | 17.98 |   0 |   0 |    3 |    4 |
| Lincoln Continental | 10.4 |   8 | 460.0 | 215 | 3.00 | 5.424 | 17.82 |   0 |   0 |    3 |    4 |
| Chrysler Imperial   | 14.7 |   8 | 440.0 | 230 | 3.23 | 5.345 | 17.42 |   0 |   0 |    3 |    4 |
| Fiat 128            | 32.4 |   4 |  78.7 |  66 | 4.08 | 2.200 | 19.47 |   1 |   1 |    4 |    1 |
| Honda Civic         | 30.4 |   4 |  75.7 |  52 | 4.93 | 1.615 | 18.52 |   1 |   1 |    4 |    2 |
| Toyota Corolla      | 33.9 |   4 |  71.1 |  65 | 4.22 | 1.835 | 19.90 |   1 |   1 |    4 |    1 |
| Toyota Corona       | 21.5 |   4 | 120.1 |  97 | 3.70 | 2.465 | 20.01 |   1 |   0 |    3 |    1 |
| Dodge Challenger    | 15.5 |   8 | 318.0 | 150 | 2.76 | 3.520 | 16.87 |   0 |   0 |    3 |    2 |
| AMC Javelin         | 15.2 |   8 | 304.0 | 150 | 3.15 | 3.435 | 17.30 |   0 |   0 |    3 |    2 |
| Camaro Z28          | 13.3 |   8 | 350.0 | 245 | 3.73 | 3.840 | 15.41 |   0 |   0 |    3 |    4 |
| Pontiac Firebird    | 19.2 |   8 | 400.0 | 175 | 3.08 | 3.845 | 17.05 |   0 |   0 |    3 |    2 |
| Fiat X1-9           | 27.3 |   4 |  79.0 |  66 | 4.08 | 1.935 | 18.90 |   1 |   1 |    4 |    1 |
| Porsche 914-2       | 26.0 |   4 | 120.3 |  91 | 4.43 | 2.140 | 16.70 |   0 |   1 |    5 |    2 |
| Lotus Europa        | 30.4 |   4 |  95.1 | 113 | 3.77 | 1.513 | 16.90 |   1 |   1 |    5 |    2 |
| Ford Pantera L      | 15.8 |   8 | 351.0 | 264 | 4.22 | 3.170 | 14.50 |   0 |   1 |    5 |    4 |
| Ferrari Dino        | 19.7 |   6 | 145.0 | 175 | 3.62 | 2.770 | 15.50 |   0 |   1 |    5 |    6 |
| Maserati Bora       | 15.0 |   8 | 301.0 | 335 | 3.54 | 3.570 | 14.60 |   0 |   1 |    5 |    8 |
| Volvo 142E          | 21.4 |   4 | 121.0 | 109 | 4.11 | 2.780 | 18.60 |   1 |   1 |    4 |    2 |

:::
:::

Table caption.

:::

See @tbl-1.

::: {#out-2}

::: {.cell}
::: {.cell-output .cell-output-stdout}

```
Call:
   aov(formula = yield ~ block + N * P + K, data = npk)

Terms:
                   block        N        P        K      N:P Residuals
Sum of Squares  343.2950 189.2817   8.4017  95.2017  21.2817  218.9033
Deg. of Freedom        5        1        1        1        1        14

Residual standard error: 3.954232
Estimated effects may be unbalanced
```

:::
:::

Sample ANOVA output

:::

See @out-2

---

### Tests Docs Smoke All 2024 02 16 8711.Pdf (`tests/docs/smoke-all/2024/02/16/8711.pdf.md`)

---
title: "Untitled"
format:
  latex:
    keep-md: true
crossref:
  custom:
    - kind: float
      key: out
      latex-env: notoutput
      reference-prefix: Output
_quarto:
  tests:
    latex:
      ensureFileRegexMatches:
        - ["\\\\begin\\{longtable"]
        - ["\\\\begin\\{tabular"]
---


::: {#tbl-1 .cell tbl-cap='Table caption'}

```{.r .cell-code}
knitr::kable(mtcars)
```

::: {.cell-output-display}


|                    |  mpg| cyl|  disp|  hp| drat|    wt|  qsec| vs| am| gear| carb|
|:-------------------|----:|---:|-----:|---:|----:|-----:|-----:|--:|--:|----:|----:|
|Mazda RX4           | 21.0|   6| 160.0| 110| 3.90| 2.620| 16.46|  0|  1|    4|    4|
|Mazda RX4 Wag       | 21.0|   6| 160.0| 110| 3.90| 2.875| 17.02|  0|  1|    4|    4|
|Datsun 710          | 22.8|   4| 108.0|  93| 3.85| 2.320| 18.61|  1|  1|    4|    1|
|Hornet 4 Drive      | 21.4|   6| 258.0| 110| 3.08| 3.215| 19.44|  1|  0|    3|    1|
|Hornet Sportabout   | 18.7|   8| 360.0| 175| 3.15| 3.440| 17.02|  0|  0|    3|    2|
|Valiant             | 18.1|   6| 225.0| 105| 2.76| 3.460| 20.22|  1|  0|    3|    1|
|Duster 360          | 14.3|   8| 360.0| 245| 3.21| 3.570| 15.84|  0|  0|    3|    4|
|Merc 240D           | 24.4|   4| 146.7|  62| 3.69| 3.190| 20.00|  1|  0|    4|    2|
|Merc 230            | 22.8|   4| 140.8|  95| 3.92| 3.150| 22.90|  1|  0|    4|    2|
|Merc 280            | 19.2|   6| 167.6| 123| 3.92| 3.440| 18.30|  1|  0|    4|    4|
|Merc 280C           | 17.8|   6| 167.6| 123| 3.92| 3.440| 18.90|  1|  0|    4|    4|
|Merc 450SE          | 16.4|   8| 275.8| 180| 3.07| 4.070| 17.40|  0|  0|    3|    3|
|Merc 450SL          | 17.3|   8| 275.8| 180| 3.07| 3.730| 17.60|  0|  0|    3|    3|
|Merc 450SLC         | 15.2|   8| 275.8| 180| 3.07| 3.780| 18.00|  0|  0|    3|    3|
|Cadillac Fleetwood  | 10.4|   8| 472.0| 205| 2.93| 5.250| 17.98|  0|  0|    3|    4|
|Lincoln Continental | 10.4|   8| 460.0| 215| 3.00| 5.424| 17.82|  0|  0|    3|    4|
|Chrysler Imperial   | 14.7|   8| 440.0| 230| 3.23| 5.345| 17.42|  0|  0|    3|    4|
|Fiat 128            | 32.4|   4|  78.7|  66| 4.08| 2.200| 19.47|  1|  1|    4|    1|
|Honda Civic         | 30.4|   4|  75.7|  52| 4.93| 1.615| 18.52|  1|  1|    4|    2|
|Toyota Corolla      | 33.9|   4|  71.1|  65| 4.22| 1.835| 19.90|  1|  1|    4|    1|
|Toyota Corona       | 21.5|   4| 120.1|  97| 3.70| 2.465| 20.01|  1|  0|    3|    1|
|Dodge Challenger    | 15.5|   8| 318.0| 150| 2.76| 3.520| 16.87|  0|  0|    3|    2|
|AMC Javelin         | 15.2|   8| 304.0| 150| 3.15| 3.435| 17.30|  0|  0|    3|    2|
|Camaro Z28          | 13.3|   8| 350.0| 245| 3.73| 3.840| 15.41|  0|  0|    3|    4|
|Pontiac Firebird    | 19.2|   8| 400.0| 175| 3.08| 3.845| 17.05|  0|  0|    3|    2|
|Fiat X1-9           | 27.3|   4|  79.0|  66| 4.08| 1.935| 18.90|  1|  1|    4|    1|
|Porsche 914-2       | 26.0|   4| 120.3|  91| 4.43| 2.140| 16.70|  0|  1|    5|    2|
|Lotus Europa        | 30.4|   4|  95.1| 113| 3.77| 1.513| 16.90|  1|  1|    5|    2|
|Ford Pantera L      | 15.8|   8| 351.0| 264| 4.22| 3.170| 14.50|  0|  1|    5|    4|
|Ferrari Dino        | 19.7|   6| 145.0| 175| 3.62| 2.770| 15.50|  0|  1|    5|    6|
|Maserati Bora       | 15.0|   8| 301.0| 335| 3.54| 3.570| 14.60|  0|  1|    5|    8|
|Volvo 142E          | 21.4|   4| 121.0| 109| 4.11| 2.780| 18.60|  1|  1|    4|    2|


:::
:::

::: {.cell}

```{.r .cell-code}
knitr::kable(mtcars)
```

::: {.cell-output-display}


|                    |  mpg| cyl|  disp|  hp| drat|    wt|  qsec| vs| am| gear| carb|
|:-------------------|----:|---:|-----:|---:|----:|-----:|-----:|--:|--:|----:|----:|
|Mazda RX4           | 21.0|   6| 160.0| 110| 3.90| 2.620| 16.46|  0|  1|    4|    4|
|Mazda RX4 Wag       | 21.0|   6| 160.0| 110| 3.90| 2.875| 17.02|  0|  1|    4|    4|
|Datsun 710          | 22.8|   4| 108.0|  93| 3.85| 2.320| 18.61|  1|  1|    4|    1|
|Hornet 4 Drive      | 21.4|   6| 258.0| 110| 3.08| 3.215| 19.44|  1|  0|    3|    1|
|Hornet Sportabout   | 18.7|   8| 360.0| 175| 3.15| 3.440| 17.02|  0|  0|    3|    2|
|Valiant             | 18.1|   6| 225.0| 105| 2.76| 3.460| 20.22|  1|  0|    3|    1|
|Duster 360          | 14.3|   8| 360.0| 245| 3.21| 3.570| 15.84|  0|  0|    3|    4|
|Merc 240D           | 24.4|   4| 146.7|  62| 3.69| 3.190| 20.00|  1|  0|    4|    2|
|Merc 230            | 22.8|   4| 140.8|  95| 3.92| 3.150| 22.90|  1|  0|    4|    2|
|Merc 280            | 19.2|   6| 167.6| 123| 3.92| 3.440| 18.30|  1|  0|    4|    4|
|Merc 280C           | 17.8|   6| 167.6| 123| 3.92| 3.440| 18.90|  1|  0|    4|    4|
|Merc 450SE          | 16.4|   8| 275.8| 180| 3.07| 4.070| 17.40|  0|  0|    3|    3|
|Merc 450SL          | 17.3|   8| 275.8| 180| 3.07| 3.730| 17.60|  0|  0|    3|    3|
|Merc 450SLC         | 15.2|   8| 275.8| 180| 3.07| 3.780| 18.00|  0|  0|    3|    3|
|Cadillac Fleetwood  | 10.4|   8| 472.0| 205| 2.93| 5.250| 17.98|  0|  0|    3|    4|
|Lincoln Continental | 10.4|   8| 460.0| 215| 3.00| 5.424| 17.82|  0|  0|    3|    4|
|Chrysler Imperial   | 14.7|   8| 440.0| 230| 3.23| 5.345| 17.42|  0|  0|    3|    4|
|Fiat 128            | 32.4|   4|  78.7|  66| 4.08| 2.200| 19.47|  1|  1|    4|    1|
|Honda Civic         | 30.4|   4|  75.7|  52| 4.93| 1.615| 18.52|  1|  1|    4|    2|
|Toyota Corolla      | 33.9|   4|  71.1|  65| 4.22| 1.835| 19.90|  1|  1|    4|    1|
|Toyota Corona       | 21.5|   4| 120.1|  97| 3.70| 2.465| 20.01|  1|  0|    3|    1|
|Dodge Challenger    | 15.5|   8| 318.0| 150| 2.76| 3.520| 16.87|  0|  0|    3|    2|
|AMC Javelin         | 15.2|   8| 304.0| 150| 3.15| 3.435| 17.30|  0|  0|    3|    2|
|Camaro Z28          | 13.3|   8| 350.0| 245| 3.73| 3.840| 15.41|  0|  0|    3|    4|
|Pontiac Firebird    | 19.2|   8| 400.0| 175| 3.08| 3.845| 17.05|  0|  0|    3|    2|
|Fiat X1-9           | 27.3|   4|  79.0|  66| 4.08| 1.935| 18.90|  1|  1|    4|    1|
|Porsche 914-2       | 26.0|   4| 120.3|  91| 4.43| 2.140| 16.70|  0|  1|    5|    2|
|Lotus Europa        | 30.4|   4|  95.1| 113| 3.77| 1.513| 16.90|  1|  1|    5|    2|
|Ford Pantera L      | 15.8|   8| 351.0| 264| 4.22| 3.170| 14.50|  0|  1|    5|    4|
|Ferrari Dino        | 19.7|   6| 145.0| 175| 3.62| 2.770| 15.50|  0|  1|    5|    6|
|Maserati Bora       | 15.0|   8| 301.0| 335| 3.54| 3.570| 14.60|  0|  1|    5|    8|
|Volvo 142E          | 21.4|   4| 121.0| 109| 4.11| 2.780| 18.60|  1|  1|    4|    2|


:::
:::


| Default | Left | Right | Center |
|---------|:-----|------:|:------:|
| 12      | 12   |    12 |   12   |
| 123     | 123  |   123 |  123   |
| 1       | 1    |     1 |   1    |

: Demonstration of pipe table syntax

::: {#out-2}


::: {.cell}
::: {.cell-output .cell-output-stdout}

```
Call:
   aov(formula = yield ~ block + N * P + K, data = npk)

Terms:
                   block        N        P        K      N:P Residuals
Sum of Squares  343.2950 189.2817   8.4017  95.2017  21.2817  218.9033
Deg. of Freedom        5        1        1        1        1        14

Residual standard error: 3.954232
Estimated effects may be unbalanced
```


:::
:::


Sample ANOVA output

:::

See @out-2

---

### Tests Docs Smoke All 2024 06 05 9696 (`tests/docs/smoke-all/2024/06/05/9696.md`)

---
title: "dash-test"
format: dashboard
keep-md: true
editor: visual
editor_options:
  chunk_output_type: console
_quarto:
  tests:
    dashboard:
      ensureHtmlElements:
        - []
        - ["div.cell.hidden"]
---

## Heading

::: {.cell title='a title0'}
::: {.cell-output-display}

```
/* Detailed source-code truncated for AI context efficiency. */
```

:::
:::

---

### Tests Docs Extensions Format Resources 9918  Extensions Test Dummy 4 (`tests/docs/extensions/format-resources/9918/_extensions/test/dummy-4.txt`)

This file should be copied to root project for rendering as it is included as a format-resources

---

### Tests Docs Smoke All Typst Relative Font Path Artifacts Fonts OFL (`tests/docs/smoke-all/typst/relative-font-path/artifacts/fonts/OFL.txt`)

Copyright 2016 The Amaranth Project Authors (https://github.com/googlefonts/amaranth)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, 
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

---

### Tests Docs Extensions Format Resources 9918  Extensions Test Files To Root Dummy 2 (`tests/docs/extensions/format-resources/9918/_extensions/test/files-to-root/dummy-2.txt`)

This file should be copied to root project for rendering as it is included as a format-resources

---

### Tests Docs Extensions Format Resources 9918  Extensions Test Files To Root Dummy 3 (`tests/docs/extensions/format-resources/9918/_extensions/test/files-to-root/dummy-3.txt`)

This file should be copied to root project for rendering as it is included as a format-resources

---

### Tests Docs Extensions Format Resources 9918  Extensions Test Folder To Root Dummy (`tests/docs/extensions/format-resources/9918/_extensions/test/folder-to-root/dummy.txt`)

This file should be copied to root project for rendering as it is included as a format-resources

---

--- METRICS ---
- Files Extracted: 43
- Estimated Token Budget: ~28852 tokens
- Canonical Reference: https://codewiki.google/github.com/quarto-dev/quarto-cli
