> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avocadostudio.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# QA gate: avocado qa

> One command, run last, that checks an integration the way the editor will use it — in a browser, in a frame on the editor's origin, with edited content — and exits non-zero until it is right.

An integration can type-check, build, answer `curl` on every editor route, and still break the moment a person opens it in the Studio. On the two sites this command was designed from, every problem found after the coding agent reported "done" lived somewhere those checks never go:

* a CMS live-preview SDK that threw a full-page error as soon as anything but its own app framed the page;
* template security headers that refused the editor frame;
* a blog post that answered 404 in the preview once its draft held a block type the template no longer rendered;
* bullets a CSS reset had removed, invisible until real content arrived with a list;
* a whole blog post mapped to one block, so clicking anywhere selected everything;
* an image component that crashed on any URL not from its own CDN.

`avocado qa` exercises exactly those paths. It is the last step of the `avocado-integrate` skill, and an agent should not report an integration finished until it exits 0.

```bash theme={null}
# from the site's directory, with its dev server running
npx avocado qa
```

`avocado` is a bin of `@avocadostudio-ai/site-sdk`, so `npx avocado qa` finds it wherever the SDK is installed. The unscoped name `avocado` on npm belongs to an unrelated package, so where the SDK is only a transitive dependency (an Astro site under pnpm, for example) use the scoped form, which always resolves to this command:

```bash theme={null}
npx @avocadostudio-ai/site-sdk qa
```

## What it does

It runs against the running site and the orchestrator the site reads drafts from. Everything it renders comes from a **throwaway session** named `qa-<timestamp>`, seeded with the site's own pages. Your session is never read from or written to, and the throwaway sessions are removed at the end (library mode has no delete route; there they stay in the database, harmlessly).

```mermaid theme={null}
flowchart LR
    P["<b>0 · Preflight</b><br/>framework, Node, package manager,<br/>disk, frame headers, known blockers"]
    S["<b>1 · Static</b><br/>foreign peers, editor script<br/>on public pages, --build"]
    C["<b>2 · Content contract</b><br/>manifest, block types, panel,<br/>manifest.lock; seeds qa-&lt;ts&gt;"]
    R["<b>3 · Editor render</b><br/>every page in a frame on the<br/>editor origin, then fixtures"]
    I["<b>Invasiveness</b><br/>what the integration changed"]
    M["<b>5 · Manual pass</b><br/>checklist"]
    P --> S --> C --> R --> I --> M

    style R fill:#7ED957,stroke:#14532D,color:#0a0a0a
```

Every check reports `pass`, `warn`, `fail`, `skip` or `info`. Any `fail` makes the command exit 1. Warnings do not, but each carries a fix, and an agent should read them.

### 0 · Preflight

| Check                 | Fails when                                                                                                                                                                                                                                                              |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Framework and version | Next is below 15, has no `app/` directory, or Astro is below 5                                                                                                                                                                                                          |
| Node                  | Below the framework's minimum (warns against `engines.node`)                                                                                                                                                                                                            |
| Package manager       | *Warns* on two lockfiles, a `packageManager` pin that disagrees with the lockfile, or a parent `package.json` pinning a different manager — corepack then refuses every call                                                                                            |
| Free disk             | Less than the remaining install plus build headroom (`df`)                                                                                                                                                                                                              |
| Known blockers        | *Warns* on `ContentfulLivePreviewProvider` without `targetOrigin`, Sanity visual editing, the Storyblok bridge, Prismic and Payload previews, existing `draftMode()` use, an Astro `ClientRouter`, and a CSS reset that strips list markers with nothing restoring them |
| Draft secret          | *Warns* when no `DRAFT_MODE_SECRET` is found                                                                                                                                                                                                                            |
| Site and orchestrator | Either does not answer, or the orchestrator refuses the token                                                                                                                                                                                                           |
| Frame headers         | The editor render's `frame-ancestors` (or, without one, `X-Frame-Options`) does not admit the editor origin                                                                                                                                                             |

### 1 · Static

| Check               | Fails when                                                                                                                                                                                         |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Foreign peers       | *Warns* when `next`, `@next/*` or (without React islands) `react` are installed in an Astro site, with their size                                                                                  |
| Public-page script  | *Warns* when a public page's scripts carry the preview bridge above the budget (`--js-budget`, 10 KB gzipped). Measured against a dev server the sizes are unminified; the presence is what counts |
| Typecheck and build | Only with `--build`: the site's own `typecheck` and `build` scripts                                                                                                                                |

### 2 · Content contract

| Check           | Fails when                                                                                                                         |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Manifest        | `/api/editor/blocks` does not validate, declares a type twice, or has field metadata that does not resolve                         |
| Pages           | `/api/editor/pages` has no pages, duplicate slugs, or duplicate block ids within a page                                            |
| Block types     | A page holds a type the manifest does not list. Chrome built-ins (a footer) are fine; other built-ins the site omits are a warning |
| Props vs schema | *Warns* when stored props do not match `propsSchema` — the first edit to that block would be refused                               |
| Panel coverage  | *Warns* on every [`panelCoverage`](/integration/coverage) finding                                                                  |
| Draft endpoint  | *Warns* when `/api/editor/draft` answers a bad secret with a 500                                                                   |
| `manifest.lock` | *Warns* when block types or fields were removed or changed kind since the last run (see below)                                     |

### 3 · Editor render

Each page is framed from the editor's own origin — the host page is served by intercepting a path on that origin, so the site's real `frame-ancestors` and postMessage target are what gets tested, and it works while the editor itself is running.

| Check             | Fails when                                                                                                                                                                          |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Load              | A page is not HTTP 200 in the frame, or the browser refuses to show it                                                                                                              |
| Errors            | An uncaught error or rejection in the frame. Known ones (Contentful's "current origin is not supported", a refused frame) come with their fix; console errors are a warning         |
| Bridge            | *Warns* when no page posts a `site-editor/v1` message to the editor origin — the bridge is not mounted, or the site trusts a different editor origin                                |
| Block ids         | A `data-block-id` on the page is not a block in the draft (the editor's "block is not in the page the orchestrator returned"). Draft blocks with no wrapper are a warning           |
| Editable coverage | [`editableCoverage`](/integration/coverage): a field with no marker, or an image marked on a void element                                                                           |
| Granularity       | *Warns* when one block's fields sit in two or more stacked, section-sized regions, or more than four fields of three kinds spread over more than a screen                           |
| Draft source      | A text field set to a unique value in the throwaway session does not appear — the site reads drafts from somewhere else                                                             |
| Rich text         | One rich-text field per block type, rendered with a fixture holding every node type, shows lists without markers or headings that look like body text. Unstyled links are a warning |
| Image sources     | A page breaks when an image field holds a relative path, a stock-photo host, the orchestrator's `/generated-images` URL, or an inline SVG placeholder                               |
| Preview drift     | *Warns* when a split preview route (a middleware rewrite to `/preview-draft`) renders different text, headings or images from the public route, with no draft applied               |

The fixtures — rich text, the draft probe, image sources — each go into a throwaway session of their own (`qa-<ts>-fx<n>`), so the plain pass measures your real content.

Without Playwright the stage still runs on the server HTML: load, block ids, coverage, the draft probe, image sources and drift are all in the markup. Errors, granularity and rich-text styles need a browser and are reported as skipped. Playwright is looked for in the project, next to the SDK and in a pnpm workspace; it is not a dependency of the SDK.

```bash theme={null}
npm i -D playwright && npx playwright install chromium
```

### Invasiveness (informational)

A git diff against `--base`, or the merge-base with the default branch, including uncommitted and untracked files — an agent's integration usually is not committed when this runs. Installed skills (`.claude`, `.agents`, `AGENTS.md`, `CLAUDE.md`) and lockfiles are excluded. It reports new and changed files and lines, files outside the integration's own folders, public route files that changed, and — as a warning — library modules that something imported at the base and nothing imports now, the data layer an integration left behind.

### 5 · Manual pass

Printed at the end: ten minutes in the Studio once the gate is green.

* Clicking each visible section selects a block with a sensible name and only its fields
* Fields that cannot be written (asset alt text, slugs, dates) are not offered as editable
* A chat edit and a panel edit both update the preview within a few seconds
* Rich text in the panel looks like rich text on the page (lists, headings, links)
* Asking for a language the site does not have produces a question, not an overwrite
* Publishing one page changes only that page in the CMS, and the public site shows it after reload

## Not yet automated

Printed on every run, so a green summary is not read as covering them:

* the CMS space holds every content type the site queries (needs CMS credentials);
* lint;
* a round trip that is a no-op for every page × locale, a synthetic edit per field kind, and a publish dry run — all three need the site's lens, which the SDK has no endpoint to run;
* rich text in the Studio's property panel (the panel is the editor's, not the site's);
* **session compatibility**: seeding a session from the previous manifest and loading it against the new one. The snapshot half is done — see `manifest.lock` below.

## Files

| File                      |                                                                                                                                                                                                                                                                                                                                                                   |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.avocado/qa-report.json` | Every check with its evidence. Do not commit it                                                                                                                                                                                                                                                                                                                   |
| `.avocado/manifest.lock`  | The shape of the block manifest at the last run — type → field → kind. **Commit it.** A draft session outlives the code that produced it, so renaming or splitting a block type leaves every draft still holding the old one stale: the preview cannot render it and the editor reports the block as missing. The next run warns, and the diff shows up in review |

## Options

| Flag                                              | Default                                                                                                                     |
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `--site <url>`                                    | The port in `package.json`'s `dev` script, else `:3000` (Next) or `:4321` (Astro)                                           |
| `--orchestrator <url>`                            | `ORCHESTRATOR_URL` from the env files, else `http://localhost:4200`. In library mode, `http://localhost:<port>/api/avocado` |
| `--editor-origin <url>`                           | `NEXT_PUBLIC_EDITOR_ORIGIN` / `AVOCADO_EDITOR_ORIGINS`, else `http://localhost:4100`                                        |
| `--site-id <id>`                                  | `NEXT_PUBLIC_DEFAULT_SITE_ID` / `AVOCADO_SITE_ID`, else the package name                                                    |
| `--secret`, `--token`                             | Read from the env files / `ORCHESTRATOR_ACCESS_TOKEN`                                                                       |
| `--skip <stages>`                                 | `0`–`5`, or `preflight,static,contract,render,invasiveness`                                                                 |
| `--build`                                         | Also run the site's `typecheck` and `build` scripts                                                                         |
| `--max-pages <n>`                                 | Every page                                                                                                                  |
| `--base <ref>`                                    | Merge-base with the default branch                                                                                          |
| `--no-browser`, `--headed`, `--playwright <path>` | Browser use for stage 3                                                                                                     |
| `--json`                                          | Print the JSON report instead of the summary                                                                                |
| `--report <path>`                                 | `.avocado/qa-report.json`                                                                                                   |

The env files are read the way the dev server reads them — `.env`, `.env.development`, `.env.local`, `.env.development.local` — so the command checks the orchestrator, secret and editor origin the site actually uses.
