# artoo — the complete reference

Generated by `artoo docs --all`. Each section is also readable on its
own with `artoo docs <topic>`.

---

# Quickstart

*On its own: `artoo docs quickstart`*

artoo builds **artifacts**: self-contained HTML mini-sites that pair a piece of
presentation with the research backing it. An artifact is a directory holding
an `artifact.toml`, and it lives inside whatever repo owns the subject. One
repo can hold many.

## The golden path

```bash
artoo init docs/spending-report --title "Where the money went"
# …author content.md; use raw site/index.html when the form needs it…
artoo status docs/spending-report     # manifest health, firewall, library drift
artoo build  docs/spending-report     # render, pack data, verify, stamp `updated`
artoo verify docs/spending-report     # links, assets, anchors, offline dependencies
artoo review docs/spending-report "A concrete design finding" --page index.html
# commit the artifact and review receipt in the subject's repo; use its private reader
artoo deploy docs/spending-report     # separate, authorized publication step
```

`artoo init` writes a concise `AGENTS.md` for automatic context and an
`ARTOO_REFERENCE.md` carrying the complete layout vocabulary generated from
the libraries that artifact actually vendored. Open the latter only when the
work needs the deeper contract.

## What init creates

```
docs/spending-report/
  artifact.toml           the manifest — source of truth
  AGENTS.md               concise working rules (generated; safe to extend)
  ARTOO_REFERENCE.md      full vendored vocabulary, on demand
  content.md              article source (omit [content] to own raw site/ HTML)
  site/
    index.html            the starter page
    lib/artoo-kit/        vendored, hash-pinned styling
  work/
    artifact-brief.md     private: reader decision, claim, limits, intent
```

Only `site/` is publishable. See `artoo docs firewall`.
Set `look` in `[artifact]` to give the piece its own face — `editorial`
(default), `ledger`, `broadsheet` or `field`; each is a token set in artoo-kit
that holds WCAG AA contrast in light and dark.
For source-owned revisions, Dirk discovery, and feedback that attaches to the
exact render, see `artoo docs review`.

## Choosing a kind and form

`kind` describes the subject; `form` describes how a reader works with it.
Artoo infers a useful default, and `--form` overrides it independently.

```bash
artoo init talks/q3 --kind presentation --title "Q3 review"
artoo init data/places --kind report --form explorer --title "Compare places"
```

Kinds: `explainer`, `report`, `reference-guide`, `research-review`,
`walkthrough`, `presentation`, `case-study`, `explorer`, `note`.

Forms: `article`, `explorer`, `collection`, `deck`. See `artoo docs forms`.

## Authoring rules that matter

1. **Prose in Markdown, structured pieces as blocks.** A callout, a row of
   figures, a captioned table, a dense grid bound to a data pack: write
   `::: callout`, `::: stats`, `::: figure`, `::: grid data=PACK`. Blocks
   cost a fraction of the markup they expand to and fail the build at
   `file:line` when written wrong. Draw the figure that carries the argument
   as a `::: html` island, from the data pack. See `artoo docs blocks`.
2. **Use the vocabulary the artifact vendored.** `ARTOO_REFERENCE.md` and
   `artoo docs artoo-kit` list every class, with its role. `artoo build` fails on a
   class invented inside a library's namespace, and names the nearest real
   one.
3. **Never reach for a CDN.** An artifact must render from a `file://` URL.
   Vendor a runtime with `artoo lib vendor <name> <url>` and it is recorded
   with a pinned hash.
4. **Style SVG with CSS, not presentation attributes.** `fill="var(--chart-1)"`
   does not resolve. Give the shape a class and set `fill` in a `<style>`
   block.
   Draw structure as Mermaid instead: a ` ```mermaid ` fence in Markdown
   content, or `<pre class="mermaid">` with the built-in `artoo-diagrams`
   library. Give nodes roles (`:::accent`), not colors; see
   `artoo docs artoo-diagrams`.
5. **Put research in `work/` or `notebook/`.** Both sit beside the
   presentation and neither can ship.

## Doing it in bulk

```bash
artoo list  .            # every artifact under a root (--json for machines)
artoo doctor .           # repo-wide coherence: manifests, firewall, drift
```

---

# Kinds and presentation forms

*On its own: `artoo docs forms`*

Artoo keeps two decisions separate:

- `kind` says what the artifact is about: report, explainer, research review,
  reference guide, walkthrough, case study, explorer, presentation, or note.
- `form` says how a reader uses it: article, explorer, collection, or deck.

This keeps taxonomy useful without turning it into a template gate. A report
can be an article, an interactive explorer, or a deck. `artoo init` infers the
common form from kind; `--form` overrides it.

## Article

`[content] source = "content.md"` renders a deterministic shell, title and
update metadata, TOC, section anchors, optional evidence panel, and colophon
around conservative Markdown. A paragraph that is only an image,
`![alt](assets/shot.png "caption")`, becomes an `article-figure` with the title
as its caption; keep the file under `site/` so it publishes and verifies. Delete
the `[content]` table when the artifact needs hand-authored HTML; Artoo then
leaves `site/` alone.

## Explorer

The starter includes `artoo-controls`: search, declared filters, active chips,
result counts, reset, URL state, CSV, an accessible empty state, and optional
saved views. It also declares a `[[data]]` pack so the same JSON works over HTTP
and `file://`. Add `artoo-grid` when the evidence is a dense comparison table;
controls and grid remain separate because many explorers render maps, lists,
or custom figures instead.

## Collection

`[content] pages = "content"` renders each top-level Markdown file as a page;
`order = ["index.md", "evidence.md"]` is its page manifest. `index.md` is
required. Navigation, current-page state, breadcrumbs, and previous/next links
derive from that order. Missing or unlisted pages fail the build rather than
silently disappearing from navigation. Older declarations without `order`
fall back to index-first alphabetical order. Artoo records the HTML files it
generated in `work/artoo-content.json`, so a renamed page removes its stale
generated output without touching hand-authored files in `site/`.

## Deck

The deck scaffold owns one-frame-at-a-time reading, acts, speaker notes,
overview, keyboard and touch navigation, and landscape print. It remains raw
HTML because slide composition is intentionally spatial.

---

# Blocks

*On its own: `artoo docs blocks`*

Markdown content has three tiers, and most pages should live in the first two:

1. **Prose** in Markdown: headings, paragraphs, lists, pipe tables, images.
2. **Blocks** for the structured pieces: a callout, a row of figures, a
   captioned table, a dense grid bound to a data pack. Each block is declared
   by the library whose vocabulary it emits, so it renders correct markup
   every time and fails the build, at `file:line`, when it is written wrong.
3. **`::: html` islands** for the figure that carries the argument — a
   chart built for this comparison, an interaction no block covers. The body
   is copied verbatim.

Use blocks for the standard pieces: a block costs a fraction of the markup it
expands to, cannot invent a class, and keeps its numbers in the data rather
than retyped into the page. But do not let the catalog set the ceiling. The
piece a reader remembers is usually one figure made for its question — a
dumbbell of two shares per district, a scatter with the threshold drawn in —
and no generic block draws that. In a blind review of agent-built reports,
pages with one purpose-built figure beat pages assembled only from stock
pieces. Build that figure as an island.

The island keeps the safety net: the invented-class check, `artoo verify`,
the design lint, and the browser checks all still apply to it. Use kit
classes and tokens inside it (`var(--accent)`, `var(--chart-1)`), never raw
colours or fonts, and read numbers from the data pack rather than typing
them:

```markdown
::: html #shares
<figure class="article-figure">
  <svg class="shares" role="img" aria-label="Substitute and textbook shares by district"></svg>
  <figcaption>Share of spending, FY2025. Source: state finance ledger.</figcaption>
</figure>
<style>
  .shares { width: 100%; height: auto; }
  .shares .sub { fill: var(--chart-6); }
  .shares .book { fill: var(--chart-5); }
  .shares text { fill: var(--muted); font: 12px var(--font-ui); }
</style>
<script>
  // window.DATA comes from the [[data]] pack; draw from it, never retype it.
  document.addEventListener("DOMContentLoaded", function () { /* … */ });
</script>
:::
```

## Syntax

```markdown
::: callout warn #caveat title="The denominator changed"
Counts before 2024 exclude **charter schools**.
:::
```

- The opening fence is three or more colons, the block name, then
  attributes in any order: `key=value`, `key="quoted value"`, `#id`, and
  bare words for flags or the block's one positional attribute.
- The body is Markdown, one item per line, or raw HTML, depending on the
  block (listed below).
- A line of the same number of colons closes it. To nest, give the outer
  block more colons: `:::: wide` … `::: callout` … `:::` … `::::`.
- An `#id` lands on the block's root element. Use it to link to the block
  and to name it when you revise: "tighten the `#caveat` callout".

Every block's root also carries `data-block` and `data-src="content.md:12"`.
That is how a browser finding ("overflows at 390px") names the line that
produced the element.

## Revising

Edit the source in place and rebuild. Do not regenerate a page to change
part of it: rewriting a whole page is where earlier fixes silently come
undone. Target the block by its `#id` or its line, change what the finding
names, and run `artoo build` again.

## Errors

A block written wrong fails the build and names the fix:

```
content.md:14: `callout` block has no attribute 'tone' (did you mean "title"?); it takes variant, title
content.md:22: unknown block "calout" (did you mean "callout"?) — `artoo docs blocks` lists the blocks this artifact can use
content.md:31: `grid` block: column field 'prof_pct' is not in the data (it has county, hhi, id, name, prof)
```

The first block that needs a library vendors it (the `grid` block adds
`artoo-grid`); after that the artifact keeps the bytes it pinned, and
`artoo lib update` stays the only upgrade boundary.

## Catalog

### `callout`

A boxed aside for a caveat, warning or result the reader must not miss.

From `artoo-kit`. Body: Markdown.

| attribute | meaning |
|---|---|
| `variant` | tone (one of `note`, `warn`, `danger`, `success`; may be written bare) |
| `title` | bold first line |

```markdown
::: callout warn title="The denominator changed"
Counts before 2024 exclude charter schools.
:::
```

### `stats`

A row of 2–6 headline figures, one `value | label` line each.

From `artoo-kit`. Body: one item per line.

```markdown
::: stats
$4.2M | spent in 2025
38% | of it on staffing
12 | vendors
:::
```

### `pullquote`

A breakout-width quotation; attribute it with cite=.

From `artoo-kit`. Body: one item per line.

| attribute | meaning |
|---|---|
| `cite` | who said it, and where |

```markdown
::: pullquote cite="Superintendent, board minutes, March 2025"
We were budgeting for a district that no longer exists.
:::
```

### `pullnumber`

One large figure with its label, breakout width.

From `artoo-kit`. Body: one item per line.

| attribute | meaning |
|---|---|
| `value` | the figure, formatted as it should read (required) |

```markdown
::: pullnumber value="1 in 7"
builds failed to launch with no checks at all
:::
```

### `figure`

Caption and source for whatever it wraps — a pipe table, an image, a grid or html block; state vintage and denominator in the caption.

From `artoo-kit`. Body: Markdown.

| attribute | meaning |
|---|---|
| `caption` | what the reader is looking at, with vintage and denominator |
| `source` | where the figures come from |

```markdown
::: figure caption="Spending by category, FY2025 (USD)" source="District ledger"
| Category | Amount |
|---|---:|
| Staffing | 1,600,000 |
:::
```

### `wide`

Let its contents run wider than the prose column (breakout width).

From `artoo-kit`. Body: Markdown.

```markdown
::: wide
| A | B | C | D | E | F |
|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 |
:::
```

### `full`

Let its contents run edge to edge.

From `artoo-kit`. Body: Markdown.

```markdown
::: full
![Panorama of the site](assets/panorama.png)
:::
```

### `cards`

Navigation cards to other pages, one `[Title](page.html) — description` line each.

From `artoo-kit`. Body: one item per line.

| attribute | meaning |
|---|---|
| `label` | accessible name for the group (default: Pages) |

```markdown
::: cards
[Method](method.html) — how the figures were built
[Data](data.html) — every table, downloadable
:::
```

### `lede`

The opening paragraph, set larger.

From `artoo-kit`. Body: one item per line.

```markdown
::: lede
The district spent more on substitutes than on textbooks.
:::
```

### `grid`

A sortable dense table bound to a [[data]] pack; one `field | Title | format flags` line per column. Fields are checked against the pack at build time.

From `artoo-grid`. Body: one item per line.

| attribute | meaning |
|---|---|
| `data` | the [[data]] pack's global name (required) |
| `rows` | dotted key of the row array inside the pack, if it is not the root |
| `index` | field that identifies a row |
| `search` | show the free-text filter (flag) |
| `columns` | show the column picker (flag) |
| `download` | CSV filename for the export button |
| `note` | footer: vintage, denominator, source |
| `caption` | figure caption under the table |

```markdown
::: grid data=DISTRICTS index=id search columns download=districts.csv note="ACS 2020–2024"
name | District | name frozen sub=county
hhi | Median household income | currency bar
prof | Proficient | percent heat decimals=1
:::
```

### `html`

Raw HTML, copied verbatim — for the figure that carries the argument, or an interaction no block covers. Use kit classes and tokens (`var(--accent)`); the class check, verify, the design lint and the browser checks still apply.

From core. Body: raw HTML, verbatim.

```markdown
::: html #timeline
<figure class="article-figure diagram">
  <svg viewBox="0 0 320 40" role="img" aria-label="Spending rose each year"><rect class="timeline-bar" x="0" y="10" width="320" height="20"/></svg>
</figure>
:::
```

---

# artifact.toml

*On its own: `artoo docs manifest`*

The manifest is the source of truth for an artifact: listings, deploy routing,
library provenance, and render vintage all derive from it. artoo writes it
deterministically — fixed key order, scalars before tables — so it diffs
cleanly.

```toml
[artifact]
slug = "spending-report"          # [a-zA-Z0-9-_], required
title = "Where the money went"    # required
description = "…"
kind = "report"                   # see the kinds list below
form = "article"                  # article | explorer | collection | deck
look = "ledger"                   # editorial (default) | ledger | broadsheet | field
status = "draft"                  # draft | building | live | archived
created = "2026-08-18"
updated = "2026-08-18"            # stamped by a successful `artoo build`

[build]
site = "site"                     # the publishable root, relative, inside the artifact
commands = ["make data"]          # refresh generated inputs; run with the artifact dir as cwd

[content]
source = "content.md"             # article Markdown rendered deterministically
# pages = "content"               # or a flat collection containing index.md
# order = ["index.md", "evidence.md"]  # collection navigation and page order

[[data]]
source = "work/items.json"        # canonical JSON, outside the publish root
path = "data/items.json"          # generated under build.site
script = "data/items.js"          # optional; defaults to path with .js
global = "ARTOO_DATA"             # offline window global

[evidence]
source = "work/evidence.json"     # provider-neutral artoo-evidence/1 projection

[research]
notebook = "notebook"             # relative path to a flip notebook; may escape with ../
include_private = false           # opt in to projecting a non-public notebook
rendered_uid = ""                 # notebook vintage the site was last rendered from
rendered_updated = ""

[verify]
browser = "auto"                  # auto | always | never: Chromium checks during `artoo build`

[state]                         # optional shared saved-data opt-in
collections = ["feedback"]      # bounded JSON namespaces under artifact/state/

[deploy]
target = "github-pages"           # github-pages | rsync | command
# adapter-specific keys live here too

[workers]
# generator model tiers, e.g. cheap = "codex", strong = "claude"

[verify]
allow = ["raw-color"]             # design-lint rules this artifact breaks on purpose

[[libraries]]                     # written by `artoo lib add`; do not hand-edit
name = "artoo-kit"
version = "0.3.0"
sha256 = "…"                      # content hash of the vendored tree

[[vendor]]                        # written by `artoo lib vendor`
name = "mermaid"
url = "https://…"
sha256 = "…"
path = "site/lib/vendor/mermaid.min.js"
```

**Kinds**: `explainer`, `report`, `reference-guide`, `research-review`,
`walkthrough`, `presentation`, `case-study`, `explorer`, `note`.

**Forms**: `article`, `explorer`, `collection`, `deck`. Older manifests without
`form` are inferred: presentation → deck, explorer → explorer,
reference-guide → collection, everything else → article.

## Rules the validator enforces

- `slug` and `title` are required; `kind` and `status` must be known values.
- `form`, when present, must be one of the four known presentation forms.
- `[content]` declares either `source` or `pages`, never both. Collection
  `order` entries are top-level `.md` files and must match the directory.
- `[[data]].path` and `script` stay inside `build.site`, name `.json` and `.js`
  files respectively, and its source may remain private.
- `build.site` is relative and inside the artifact — no `..`, no absolute path.
- `verify.browser` is `auto` (the default: run browser checks where Playwright
  and Chromium are installed, note the skip elsewhere), `always` (their
  absence fails the build) or `never`. `artoo build --browser` and
  `--no-browser` override it for one run; see `artoo docs verify`.
- `state.collections` is a list of slug names. Only declared collections can be
  served by the shared-state API; no declaration means no state access. `state/`
  must not overlap `build.site`. Disk writes do not commit or push Git; see
  `artoo docs serve` for revision checks, browser import, and recovery.
- `research.notebook` is relative. It **may** escape the artifact with `..`:
  a report can be rendered from a canonical notebook that lives elsewhere and
  is the source of truth. It must never overlap `build.site`.
- `verify.allow` names design-lint rules (`artoo docs verify`, "Design lint").
  An id that is not a known rule fails the build with the list of real ones, so
  a typo cannot silently allow nothing.
- `[[libraries]]` and `[[vendor]]` entries carry their hashes. Editing a
  vendored file by hand shows up as `modified` in `artoo status`; that is
  the intended signal, not a failure.

Check any artifact with `artoo status <path>`, or a whole repo with
`artoo doctor .`.

---

# Deterministic data packing

*On its own: `artoo docs data`*

Explorers repeatedly need one canonical JSON file plus a JavaScript global that
works from `file://`, where fetching a sibling file is blocked. Declare that
join once:

```toml
[[data]]
source = "work/items.json"
path = "data/items.json"
script = "data/items.js"
global = "ARTOO_DATA"
```

`artoo build` validates the source, writes pretty canonical JSON under `site/`,
and writes the sibling loader as `window.ARTOO_DATA = …`. The source can stay
behind the firewall; both generated outputs are deterministic. Omit `script`
to derive it from `path` by replacing the suffix with `.js`.

Every destination must remain under `build.site`, and the global must be a
plain JavaScript identifier. Multiple `[[data]]` entries are allowed.

Use `artoo-controls` for recurring search/filter/download behavior and
`artoo-grid` for dense analytical tables. Neither owns the data preparation;
build commands can refresh `work/items.json` first, then the packer publishes
the exact result.

---

# Verification

*On its own: `artoo docs verify`*

`artoo build` runs static verification after rendering content, packing data,
and projecting evidence. `artoo verify` runs it without changing the artifact.

Hard failures cover:

- missing HTML or CSS assets and internal pages;
- broken fragment anchors and duplicate IDs;
- references that escape `build.site` or target a firewall-withheld path;
- root-relative paths that fail from `file://`;
- remote scripts, styles, fonts, images, and other runtime dependencies;
- literal remote CSS imports or JavaScript fetch/import calls.

Warnings identify images without `alt`, tables without headings or with uneven
row widths, and empty or nowhere-pointing controls. They are review prompts,
not taste scores, and do not fail a build.

## Browser checks

Some faults only exist once a page is drawn: a table that pushes the page
sideways on a phone, a diagram that never rendered, text cut off by its box.
A static pass cannot see them and an author rarely looks at every page at
every width, so `artoo build` opens the pages itself.

After the static checks pass (a structurally broken site fails fast, without
a browser), the build firewall-stages the site, opens every publishable page
in Chromium at phone (390×844) and desktop (1440×900) width, and runs the
checks below in the page.

These fail the build:

- page errors and console errors, with the file and line that raised them;
- horizontal overflow, naming up to three boxes that cause it: the widest
  ones whose right edge passes the viewport, skipping anything inside a
  scroll or clip container and anything inside a box already named;
- broken images: loaded, but not decodable as an image;
- Mermaid diagrams that did not render: a `pre.mermaid` block (or its
  rendered host) with no `<svg>` after a few seconds. A diagram whose source
  failed to parse is reported once, through its console error.

These are warnings, review prompts that do not fail the build:

- clipped text: text cut at the edge of a box with `overflow: hidden` or
  `clip`. Scroll containers, `text-overflow: ellipsis`, line clamps, the
  sr-only pattern and text staged wholly outside its box (an inactive slide)
  are deliberate and skipped;
- small text: visible text under 10.5px. Real artifacts use 11px labels and
  10.8px code in a colophon on purpose; the line is drawn below them;
- contrast under WCAG AA: 4.5:1, or 3:1 for large text (24px, or 18.66px
  bold), against the background composited from the element's ancestors. A
  background image, gradient, blend or positioned box in the way makes the
  background unknown, and unknown is skipped, never guessed. Ornament
  characters such as `·` are not judged;
- overlap: text in an absolutely positioned box whose glyphs land on other
  text. Fixed and sticky chrome floats over content by design and is left out.

Each finding names the page, the viewport, a short selector, the nearest
heading with an id (`§methods`) and, where the page records one, the source
line it came from (`content.md:42`). A rule reports at most three findings per
page and viewport, then "and N more"; a finding seen at both widths is one
line naming both.

### The contact sheet

The build writes a screenshot of every page at both widths to
`work/verification/` and composes them into one image,
`work/verification/contact-sheet.png`, which the build prints as
`look once: …`. Look at it once, after the checks pass: it is the cheapest
way to see the whole artifact the way a reader will, and a better use of a
review than another polishing loop. `work/` never publishes.

### When it runs

`[verify] browser` in `artifact.toml` sets the policy:

- `auto` (the default) runs the checks where Playwright and Chromium are
  installed. Elsewhere the build prints one note line and carries on.
- `always` makes their absence a build failure, for a machine or CI job that
  is meant to prove the pages render.
- `never` skips them.

`artoo build --browser` or `--no-browser` overrides the manifest for one run.
Playwright is a soft dependency that core artoo never requires. To install it
beside a `uv tool` install:

```bash
uv tool install artoo-artifacts --with-executables-from playwright
playwright install chromium
```

`artoo verify` stays static unless asked:

```bash
artoo verify path/to/artifact --browser
artoo verify path/to/artifact --browser --screenshots work/verification
```

`--browser` runs the same checks; `--screenshots DIR` also writes the
per-page screenshots and the contact sheet there.

A clean check proves structural and runtime integrity. It does not promote an
artifact's status or establish factual, editorial, visual, or human acceptance.

## Design lint

A page can pass every integrity check and still read as generated: gradient
headline text, frosted-glass panels, an emoji in front of every heading, "01 /
02" eyebrows, cards that hold content instead of linking anywhere, colours and
fonts typed in by hand instead of taken from the kit, stock model prose. Those
habits are easier to name than to notice, so artoo names them. Each rule below
is deterministic — no model call, no score — and carries its fix in kit
vocabulary.

`artoo build` and `artoo verify` run the lint over the artifact's own
publishable HTML and CSS, including inline `style=` attributes and `<style>`
blocks. Vendored libraries under `site/lib/` are never linted: their CSS is
where the tokens are defined. Findings are **warnings** — printed, and listed
under `"warnings"` in `--json` output — and never fail a build. Each names the
file, line, and rule:

```text
site/index.html:42 [raw-color] color: #1f2430 — use a kit token — var(--text), …
```

A rule reports at most three hits per file and five files per artifact, then
counts the rest, so one shared inline `<style>` across a generated site says
it once.

| rule | what it catches | fix |
|---|---|---|
| `raw-color` | a hex, rgb(), or hsl() colour typed into page CSS or a style= attribute | use a kit token — var(--text), var(--muted), var(--accent), var(--surface), var(--border), var(--chart-1)…var(--chart-8) — or define your own once as a custom property (--brand: #…) and use var(--brand) |
| `foreign-font` | a font-family that names a web font, or sets a family without a kit font token | use var(--font-prose), var(--font-ui), var(--font-display), or var(--font-numeric); the kit stacks are offline system fonts |
| `gradient-text` | text painted with a gradient via background-clip: text | give the text a solid colour such as var(--text) or var(--accent); let size and weight carry the emphasis |
| `glass` | a frosted-glass panel: backdrop-filter with blur() | use an opaque surface: background: var(--surface); border: 1px solid var(--border) |
| `emoji-icon` | a heading, list item, button, or nav link whose text starts with an emoji | drop the emoji and let the words lead; if an icon earns its place, use an inline SVG with aria-hidden="true" |
| `numbered-eyebrow` | a counter used as decoration: an element that is only "01" / "02 —", or a heading that starts "01." | drop the counter and name the section; if order matters, use an &lt;ol> (or class="toc" for contents) |
| `card-as-container` | a nav-card (or card) that is not a link and holds no link | nav-card is for links to destinations: make it &lt;a class="nav-card" href=…>, or use a plain &lt;section> or class="callout" for content |
| `stock-copy` | a stock phrase of generated prose ("delve", "seamless integration", "unlock the power", "it's not just X, it's Y" …) | say the specific thing in plain words: what it does, for whom, by how much |

Two choices keep the rules quiet on good work. Defining a token is the right
pattern, so a colour inside a custom-property definition (`--brand: #0b5`)
passes, as does any `var(--x)` and the fallback inside it; only a literal at a
usage site is reported. Likewise any `font-family: var(--…)` passes, and a
web font is caught where it is first named. A plain `.card` is only a
finding on pages that load artoo-kit, where it is the deprecated alias of
`nav-card`; elsewhere it is the page's own class. A blurred backdrop on a
sticky or fixed bar, a near-opaque surface, or a dialog's `::backdrop` is a
legibility device, not glass. Check marks, arrows, and stars (✓ → ★) are
typography; the emoji rule covers pictographs and anything written with the
emoji selector (⚠️).

An artifact that breaks a rule on purpose — its own design system with
literal colours, say — says so in the manifest, where the exception stays
visible:

```toml
[verify]
allow = ["raw-color", "foreign-font"]
```

An id in `allow` that is not a rule is a build problem that lists the real
ones, because a typo there would silently allow nothing.

---

# Private review and source-owned revisions

*On its own: `artoo docs review`*

Keep each artifact in the repository that owns its subject. Its `artifact.toml`
names the page; `site/` holds the served projection; `work/` holds the brief,
source contract, and review events. A deck, explorer, or raw HTML page follows
the same contract. Agents in Claude, Codex, or another harness should use this
path when Artoo is available rather than making a detached artifact in a
harness-specific surface.

## One path from draft to review

1. `artoo init <repo-path> --kind … --form … --title …`, or add a manifest to
   an existing source-owned HTML tree. Keep generated and research inputs beside
   `site/`, not in a central artifact repository.
2. `artoo build <artifact>` and `artoo verify <artifact> --browser`. Review the
   actual phone and desktop render, task flow, source limits, and links. These
   checks prove rendering and integrity, not editorial quality.
3. Commit the artifact and its source contract in the owning repository. A
   changed render is a new Git revision; do not silently replace a reviewed
   candidate. `artoo review` also records a digest of exactly the files the
   firewall would serve, so an uncommitted trial still has a distinct identity.
4. Where an existing Dirk reader indexes the project repository, use its
   discovered `/a/<repo>/<slug>/` route for private review. Dirk scans
   `artifact.toml` and reads the site live; do not copy the artifact into a
   separate hosting repository or start another server. Check the actual access
   plane before sharing a URL. Artoo itself does not add authentication.
5. Record findings and fixes against the rendered revision:

   ```bash
   artoo review path/to/artifact "The mobile answer is below the first screen" --page answers/device-policy/index.html
   artoo review path/to/artifact "Selected answer now receives focus" --page answers/device-policy/index.html --resolves <finding-id>
   ```

   The append-only `work/reviews.jsonl` records event ids, the artifact slug,
   site digest, Git head and dirty state, page, text, timestamp, and the
   finding resolved. Commit those events with the source-owned artifact. A
   resolution points to the earlier finding and captures the *new* render;
   the reviewer can compare both. `--page` must name a served file. The review
   file stays outside `site/` and is never served or deployed.

`artoo feedback` is a different route: when a Flip notebook is attached, it
opens a question or log in that canonical research record. Use it for source
claim corrections. Use `artoo review` for presentation, flow, and acceptance
findings, including artifacts without notebooks. One accountable source owner
accepts a correction; a green browser check does not do that work.

Dirk discovery is a convenience, not a publication or approval gate. Public
deployment still requires its own authorization and `artoo deploy` or the
owning product's publisher. Access controls belong to the existing reader
plane; do not widen them to make review easier.

---

# The deploy firewall

*On its own: `artoo docs firewall`*

Research material lives next to the presentation. The firewall is what makes
that safe: publishing is **deny-by-default**, and the rules are structural
rather than a list of ignores someone has to maintain.

## What ships

Only files inside the artifact's site root (`site/` unless `build.site` says
otherwise). Everything else in the artifact directory — `work/`, `notebook/`,
`artifact.toml`, `AGENTS.md`, scratch files — stays in the repo.

## What is withheld even inside `site/`

| Rule | Rationale |
|------|-----------|
| Any path segment starting with `_` | The conventional "working file" marker |
| Any path segment starting with `.` | Except `.nojekyll`, which hosts require |
| `notebook.md`, `notes.md`, `priors.md`, `DECISIONS.md`, `HANDOFF.md` | Research filenames, wherever they land |
| `artifact.toml` | The manifest is repo metadata, not site content |
| Symlinks | Never followed out of the artifact |

So `site/_draft/v2.html` and `site/notes.md` are visible to you and invisible
to the world, with no configuration.

## Seeing it before you publish

```bash
artoo status <artifact>    # lists what would be withheld
artoo deploy <artifact> --dry-run
```

`artoo deploy` stages only the publishable set into a temporary tree and hands
*that* to the adapter. An adapter never sees the artifact directory, so it
cannot publish something the firewall declined.

## The one deliberate opening

A private flip notebook is not projected into `site/data/provenance.json`
unless the manifest opts in:

```toml
[research]
include_private = true
```

That renders a non-public notebook in full into a publishable file. Check what
the panel shows before deploying.

---

# Serving an artifact, and where saved state goes

*On its own: `artoo docs serve`*

`artoo serve path/to/artifact --open` serves the artifact's `site/` through
its publication firewall, on loopback by default. It is a working surface,
not an authenticated public host. Private files outside `site/` remain private.

## The shared state contract

The protocol-2 API stores named JSON documents under the owning artifact's
`state/` directory, outside `site/`. A compatible reader such as Dirk can mount
the same API below the artifact URL. Enable only specifically declared state
collections with `[state]` and `collections = ["feedback"]` in `artifact.toml`; never accept
a filesystem path from page JavaScript. Hosting or authentication changes are
separate deployment decisions, not implicit consequences of authoring a page.

Artifacts on the same origin retain that origin's existing trust boundary.
Collection opt-in bounds filesystem writes; it does not sandbox mutually
untrusted pages on one origin. Serve only trusted artifacts within the existing
authenticated access boundary, and do not expand access to enable saves.

| Request below the artifact root | Effect |
|---|---|
| `GET _artoo/state/<collection>` | capability response `protocol: 2`, `durable: true`, entries |
| `GET _artoo/state/<collection>/<name>` | document envelope including `revision`; 404 if absent |
| `PUT _artoo/state/<collection>/<name>` | JSON `{document, expected_revision}` |
| `DELETE _artoo/state/<collection>/<name>` | JSON `{expected_revision}` |

A new document uses `expected_revision: null`; an update uses the exact revision
last read. A stale revision returns conflict (409), never last-writer-wins.
Hosts may deliberately deny DELETE; surface that refusal without fallback.
Collection/document names match `[A-Za-z0-9][A-Za-z0-9._-]{0,63}` and the service
bounds request size. Atomic replacement prevents partial documents; revision
checks protect concurrent edits. A successful save confirms a disk write.
**It does not commit or push Git.** Review and commit only the intended artifact
state JSON files separately. Ignore `state/**/.lock` and `state/**/.pending-*`
(the latter can remain after an interrupted process); never stage the whole repository to capture feedback.

## Authoring with ArtooStore

Upgrade the pinned kit explicitly with `artoo lib update`; existing vendored
copies are not silently rewritten. Load the kit's `store.js` normally from
`lib/artoo-kit/store.js`. It derives the artifact root from that script URL,
including when the current page is nested. An explicit same-origin `base` option
or `<meta name="artoo-state-base" content="…">` overrides the endpoint.

```js
const store = ArtooStore.open("feedback");
// Keep editing disabled until the shared document has been read.
let document = await store.load("review"); // document or null; remembers revision
// Render the shared document and any pending/browser copy for comparison.
const browserCopy = store.inspectLocal("review");
const pending = store.inspectPending("review");
// Enable editing only when unresolved pending data has been handled.
await store.save("review", editedDocument);
status.textContent = store.describe();
```

Serialize saves and catch every failure. Display a saving state before awaiting,
then display `describe()` or equivalent only after the response. Disable edits
until load finishes, preserve the visible edit on errors, and provide a retry or
comparison action. `store.status` distinguishes `unloaded`, `loaded`, `saving`,
`saved`, `conflict`, `unsaved`, `unavailable`, and `browser-only`. `durable` means
the protocol was discovered, not that the latest edit succeeded.

HTTP service errors never silently switch to browser-only saves. Explicit
`ArtooStore.open("feedback", {localOnly: true})` or a `file://` page uses the
legacy browser store, and must visibly say **browser only**. A protocol-1 server
must be upgraded; its successful response cannot establish conflict safety.

## Browser import and interrupted saves

The legacy `artoo:state:<collection>:<name>` browser key is preserved. Inspect it
with `inspectLocal(name)`, show its content alongside a fresh `load(name)`, and
ask the reader to choose/merge differences. Only after that explicit choice:

```js
await store.importLocal("review", {confirm: true, document: reviewedMergedDocument});
```

This saves against the revision remembered by `load` and leaves the original
browser copy intact. Never automatically replace shared feedback with an old
browser copy. Different browsers may contain different legacy copies.

Before a network write, the client retains a separate browser pending envelope
containing the payload and its original expected revision. Each operation has
its own journal ID, so one browser tab cannot clear another tab's failed save. If browser storage
refuses that recovery copy, the write is refused. `inspectPending(name)` exposes
it after a reload; `listPending(name)` returns all outstanding operations.
Pass a `pending_id` to `inspectPending(name, id)`, `recoverPending(name, id)`,
or `discardPending(name, id)` when choosing among several.
`resolvePending` accepts the selected `pending_id` in its options.
`recoverPending(name)` retries that exact revision, after
loading shared state, so recovery cannot overwrite a newer edit. A lost response
may mean the disk write already succeeded: compare the pending and shared copies
before acting. Recovery may correctly conflict in this case.

A pending edit blocks a new save until the reader has recovered or explicitly
resolved it. For a manual merge, retain both copies in the UI, load shared state,
and call `resolvePending(name, {confirm: true, document: reviewedMergedDocument})`
after the reader approves. It writes a separate recovery envelope before the network request, retaining
the original until the merge is acknowledged, avoiding a discard-then-save crash gap.
`discardPending(name)` is available only for an explicit decision to abandon an
edit; never use it automatically on load or on a failed save. Neither action
removes the legacy browser source.

Test a fresh second browser context, reload, concurrent edits, offline failure,
interrupted acknowledgement, and the import comparison flow. A same-session
localStorage read does not demonstrate shared persistence. Keep tests and
captures inside the artifact's private working area, and never put saved reader
feedback into the public `site/` tree.

---

# Evidence and the research roundtrip

*On its own: `artoo docs provenance`*

An artifact can project provider-neutral evidence directly, or attach a
[flip](https://github.com/lavallee/flip) notebook and use Flip as an adapter.
Either route lands the same browser-facing provenance files, so presentation
does not depend on a particular research system.

## Provider-neutral evidence

```toml
[evidence]
source = "work/evidence.json"
```

The source uses `artoo-evidence/1`:

```json
{
  "contract": "artoo-evidence/1",
  "sources": [{"id": "A1", "title": "Primary dataset", "grade": "A"}],
  "claims": [{"id": "C1", "text": "The supported claim", "sources": ["A1"]}]
}
```

Source ids and titles, claim ids and text, and claim-to-source references are
validated. A declared source that is missing or invalid fails the build.

## Flip adapter

When `[evidence]` is absent, an artifact can attach a Flip notebook. Artoo reads
it back out at build time so the published page carries its own lineage.

The Flip route no-ops cleanly with no Flip installed. Artoo core has no hard
dependency on it; the integration is a soft import, discovered on `PATH` or
pinned with `ARTOO_FLIP_BIN`.

## Attaching a notebook

```toml
[research]
notebook = "notebook"
```

`artoo init --notebook` scaffolds one. The path is relative and may point
outside the artifact — the read-direction generator renders a report *from* a
canonical notebook that is the source of truth elsewhere.

## The projection

```bash
artoo provenance <artifact>   # flip export json → site/data/provenance.json
artoo status     <artifact>   # …and reports whether the render is stale
```

`artoo build` refreshes either projection automatically and records a Flip notebook
`uid` + `updated` in the manifest as the render vintage. flip does the
policy filtering; artoo passes `--include-private` only when the manifest sets
`[research] include_private = true`.

A Flip refusal here is never a build failure — no Flip, no notebook, or a
visibility policy that declines all read as a note.

## Rendering the panel

The kit ships the panel; both evidence routes use the same three lines:

```html
<section class="provenance article-breakout" data-artoo-provenance></section>

<script src="data/provenance.js"></script>
<script src="lib/artoo-kit/provenance.js"></script>
```

`data/provenance.js` sets a global so the panel hydrates from `file://`, where
a bare `fetch()` of a sibling JSON is blocked. Both files are written by
`artoo provenance`. With no projection present the panel hides itself and the
page reads exactly as authored, so wiring it early is safe.

Bracketed ids in prose — `[C7]`, `[A3]` — that the projection knows become
stable anchors linking to their panel entry. Ids it does not know are left
alone, and text inside `<code>`/`<pre>` is never rewritten.

## The structural verbs

The loop runs both ways:

```bash
# Read: render a report FROM a canonical notebook.
artoo generate notebook-report --notebook path/to/notebook --out site/report

# Reverse: route a correction back INTO the notebook. Never edits site/.
artoo feedback site/report "C7 overstates the effect" --claim C7
```

## The publish gate

`artoo deploy` runs `flip doctor` on the attached notebook first and refuses to
publish on ERROR-level findings. `--allow-doctor-errors` overrides it
deliberately.

---

# Generators

*On its own: `artoo docs generators`*

A generator produces an artifact's content programmatically. They are plugins,
resolved from the `artoo.generators` entry-point group, so a repo can ship its
own without patching artoo.

```bash
artoo generate                       # list what is installed
artoo generate <name> --help         # a generator's own options
```

## Models stay outside artoo

artoo core makes no model calls and holds no API keys. Model-powered
generators delegate to **agent CLIs already on your PATH** — `claude`,
`codex` — configured per artifact:

```toml
[workers]
cheap = "codex"      # fan-out: per-module analysis, inventory passes
strong = "claude"    # synthesis: the narrative, the argument
```

The split is deliberate: fan-out work is wide and shallow, synthesis is narrow
and deep, and paying strong-tier rates for the first is waste.

## Built in

### `explainer` — a repo explainer

```bash
artoo generate explainer --repo . --out site/explainer
```

Inventories the repo deterministically, builds an evidence-bearing
`artoo-codegraph/1`, fans per-module analysis out to the cheap worker,
synthesizes the narrative with the strong worker, renders architecture
diagrams, and assembles a multi-page site with the kit. Graph ranking gives
each area worker a bounded set of source coordinates to inspect first; the
ranking is navigation help, not architectural authority. Planning starts from
a named reader decision, a supportable headline claim, a
counter-reading, and the licit comparisons — *before* it picks tables or
figures. The output is a dated snapshot with a colophon recording exactly how
it was made.

Every explainer also includes an offline Code map page: saved bounded views,
focus, paths, declared/static/runtime/inferred toggles, and source receipts over
the same graph the workers used. See `artoo docs code-maps`.

### `notebook-report` — a report from a notebook

```bash
artoo generate notebook-report --notebook path/to/notebook --out site/report
```

The read half of the flip roundtrip. See `artoo docs provenance`.

## Writing one

Export a callable and register it:

```toml
[project.entry-points."artoo.generators"]
my-generator = "mypkg.gen:generate"
```

Generators that write into `site/` should use the vendored library vocabulary —
`artoo docs artoo-kit` — so their output passes the same markup check as
hand-authored pages.

---

# Code maps

*On its own: `artoo docs code-maps`*

Artoo code maps are evidence-bearing graphs for interrogating a repository. The
product is not one giant architecture picture. It is one inspectable graph that
can answer many bounded questions: what enters here, what can it reach, what
depends on this, why is this relationship shown, and which facts changed.

## Build the graph

```bash
artoo map build .
# → .artoo/codegraph.json

artoo map build . --out work/codegraph.generated.json --exclude site/explainer/
```

The built-in analyzer is deliberately conservative and currently Python-first.
It parses packages, modules, public and private definitions, local imports,
resolvable local calls, declared project dependencies, and `[project.scripts]`
entry points. Syntax failures and unsupported source languages are preserved in
`snapshot.coverage`; they are never silently treated as an empty graph.

Artoo core makes no model calls. External analyzers can write the same contract
and use every interrogation and rendering surface without becoming an Artoo
dependency:

```bash
artoo map schema > codegraph-v1.json
artoo map view their-graph.json --format mermaid
```

The schema is also published at
`https://lavallee.github.io/artoo/schema/codegraph-v1.json`.

## Four kinds of truth

Every node and relationship carries one truth class:

| class | what it establishes | what it does not establish |
|---|---|---|
| `declared` | intended or configured architecture | implementation or runtime conformance |
| `static` | a relationship found in one source snapshot | whether the path runs or is important |
| `runtime` | behavior observed in one named trace | whole-system absence or typicality |
| `inferred` | a proposed label, group, or relationship | observed fact |

These classes do not collapse. A renderer may filter them but cannot promote
one. A model-generated edge must remain `inferred`, even when plausible. An
unobserved runtime path is unknown, not absent.

## Ask bounded questions

```bash
# A saved overview as renderer-neutral JSON.
artoo map view .artoo/codegraph.json --view overview --budget 80

# Focus on one definition and walk outward two hops.
artoo map view .artoo/codegraph.json \
  --focus artoo.cli.main --direction outgoing --depth 2 --format mermaid

# Inspect the reason and source receipt for a node and its adjacent edges.
artoo map why .artoo/codegraph.json artoo.cli.main

# Find a shortest directed evidence path.
artoo map path .artoo/codegraph.json entrypoint:artoo artoo.cli.main

# Select question-relevant source coordinates under an approximate token budget.
artoo map context .artoo/codegraph.json \
  "where does deploy enter and what can it call?" --budget-tokens 800
```

References resolve from a stable id, repository-relative path, qualified name,
or unique label. An ambiguous name is refused and the error prints candidate
stable ids. `view`, `why`, `path`, and `context` all accept machine-readable
JSON where useful.

The context rank is transparent: query matches, declared entry points, public
definitions, and typed graph degree determine the order. Its token count is an
estimate of one token per four characters. Ranking is navigation help, not a
claim that the highest-scoring code is the most important architecture.

## The contract

`artoo-codegraph/1` has five main parts:

| part | role |
|---|---|
| `snapshot` | repository revision, dirty state, analyzers, coverage, exclusions, truncation |
| `nodes[]` | stable id, kind, label, optional source coordinate, parent, truth, attributes |
| `edges[]` | typed endpoints, truth, confidence, reason, and one or more evidence coordinates |
| `traces[]` | optional named runtime records and their vintage/coverage receipts |
| `views[]` | question, direction, depth, budget, layers, and optional relationship kinds |

Paths must be repository-relative. Absolute local roots are rejected so a
published graph does not leak a workstation path. A dirty snapshot withholds
its remote `source_base`: the local line might not exist at the recorded commit,
so printing the coordinate is more honest than linking to different code.

## Interactive artifacts

`artoo generate explainer` writes:

```text
work/codegraph.generated.json   private retained graph and agent context source
site/data/codegraph.json        publishable renderer-neutral graph
site/data/codegraph.js          identical file://-safe browser global
site/code-map.html              interactive bounded views and receipts
site/lib/artoo-map/             versioned, hash-pinned viewer assets
```

The Code map page supports saved overview/call views, focus, direction and depth,
directed paths, truth-layer toggles, node and edge evidence, URL state, and JSON,
Mermaid, and static SVG exports. Its canvas has an ordinary keyboard-navigable
node table beneath it. The initial layout is breadth-first and stops moving; it
is not a force-directed hairball.

`artoo-map` is a renderer, not an analyzer. Its API and class vocabulary are in
`artoo docs artoo-map`.

## Ownership and extension boundary

Artoo core owns the contract, validation, bounded queries, evidence receipts,
and renderer-neutral projections. The explainer owns optional agent narrative
and uses `artoo map context` semantics to rank where each area analysis begins.
Site libraries own only presentation. External language analyzers and runtime
recorders own their extraction cost and coverage.

This stays inside Artoo during 0.x because it shares artifact custody, vendoring,
verification, documentation, and the publish firewall. A parser or renderer
should become another distribution only after its dependency weight, release
cadence, or independent consumers create a real operational boundary.

---

# artoo-controls 0.1.0

*On its own: `artoo docs artoo-controls`*

Search, filter groups, active chips, result counts, URL state, CSV downloads, and optional saved explorer configurations.

The recurring interaction shell for an explorer: search, declared filters,
active chips, a live result count, reset, URL state, CSV download, and optional
named configurations. It works with ordinary arrays and renders from
`file://`; the explorer remains responsible for presenting the filtered rows.

```html
<div id="controls"></div>
<script src="data/items.js"></script>
<script src="lib/artoo-controls/controls.js"></script>
<script>
const controls = ArtooControls.create("#controls", {
  data: window.ARTOO_DATA,
  search: { fields: ["name", "description"], label: "Search" },
  filters: [{ field: "type", label: "Type" }],
  url: true,
  download: "items.csv",
  onChange(rows, state) { render(rows); },
});
</script>
```

Filter options are inferred from the rows unless an `options` array is
declared. Set `multiple: true` for a multiple select. `controls.state()` returns
the current `{q, filters}` value and `controls.setState(value)` restores one.

Set `presets: "collection-name"` to show Save and Load buttons. When
`ArtooStore` is loaded, saves go through `artoo serve` into reviewable
`state/` files; otherwise controls say that presets are unavailable rather
than implying durability they do not have.

URL state uses `history.replaceState`; it never reloads the page. CSV is built
from the filtered rows. Pass `downloadFields` to control its columns.

## Class vocabulary

| class | role |
|---|---|
| `controls` | root enhanced by ArtooControls.create() |
| `controls-bar` | responsive row containing search, filters, and actions |
| `controls-field` | label and input pair |
| `controls-search` | free-text search input |
| `controls-select` | single- or multiple-value filter |
| `controls-actions` | reset, download, and optional preset actions |
| `controls-summary` | live result summary and active filters |
| `controls-count` | aria-live result count |
| `controls-chips` | active-filter list |
| `controls-chip` | button that removes one active filter |
| `controls-reset` | clear-all button |
| `controls-download` | CSV download button |
| `controls-save` | save-current-configuration button |
| `controls-load` | restore-saved-configuration button |
| `controls-status` | preset durability and result feedback |
| `controls-empty` | accessible empty-result message authored by the explorer |

artoo-controls owns `controls-`. A class starting with one of those that the vendored stylesheet does not define is a guess, and `artoo build` reports it with the nearest real class. Your own classes belong outside those prefixes.

---

# artoo-deck 0.1.0

*On its own: `artoo docs artoo-deck`*

Slides: acts, speaker notes, overview, keyboard/touch nav, landscape print.

Slide machinery for `kind = "presentation"` artifacts. Vendored into an
artifact at `site/lib/artoo-deck/` with a pinned hash; `artoo lib update
artoo-deck` is the explicit upgrade boundary.

| file | role |
|------|------|
| `deck.css` | chrome, slide machinery, content vocabulary, landscape print |
| `deck.js` | navigation, overview, notes, keyboard and touch control |
| `favicon.svg` | default icon |

## The `deck-` prefix is load-bearing

A deck's chrome and its content live in one stylesheet. An unprefixed chrome
class captures content that happens to use the same word — a `.bar` fixed
header will set `height: 46px` on an SVG `<rect class="bar">` and collapse a
chart. Every class here is prefixed so the two vocabularies cannot reach each
other. Keep your own content classes out of the `deck-` namespace and neither
can break the other.

## Markup contract

`deck.js` reads structure from the markup rather than from configuration:

```html
<body class="deck-root">
  <header class="deck-bar">
    <button data-deck-toggle="overview" aria-pressed="false">Overview</button>
    <button data-deck-toggle="notes" aria-pressed="false">Notes</button>
    <nav class="deck-pager">
      <button data-deck-prev>&lsaquo;</button>
      <span data-deck-counter>1 / 1</span>
      <button data-deck-next>&rsaquo;</button>
    </nav>
  </header>
  <div class="deck-progress"><i data-deck-progress></i></div>

  <main class="deck-stage" data-deck>
    <section class="deck-slide" data-act="Opening" data-short="Title">
      <div class="deck-inner">
        …
        <aside class="deck-notes"><b>Speaker note</b><p>…</p></aside>
      </div>
    </section>
  </main>

  <div class="deck-overview" data-deck-overview></div>
</body>
```

- `data-act` groups slides into acts in the overview; `data-short` labels one.
- Slides are deep-linkable: `#4` opens slide four.
- With JavaScript off every slide remains in the document and printing still
  produces the whole deck.

## Printing

`@page` is A4 landscape, one slide per page, speaker notes included — a
printed deck is usually the one being spoken from. Suppress them with
`.deck-notes { display: none }` in the artifact's own stylesheet.

Name the deck in the running foot by setting the token:

```css
.deck-root { --deck-print-id: "Acme — Q3 review ·"; }
```

## Restyling

Tokens on `.deck-root` (`--deck-accent`, `--deck-paper`, `--deck-ink`, the
font stacks) are redefinable from the artifact's own stylesheet. Charts and
bespoke slide components belong there too, not here.

## Class vocabulary

| class | role |
|---|---|
| `deck-root` | on `<body>`; carries the deck's redefinable `--deck-*` tokens |
| `deck-bar` | fixed top chrome holding the mark, toggles, and pager |
| `deck-mark` | the initial or logo mark in the bar |
| `deck-who` | the deck's name in the bar |
| `deck-spacer` | flexible gap pushing bar items apart |
| `deck-pager` | prev / counter / next group in the bar |
| `deck-pager-m` | the mobile pager, shown only on narrow screens |
| `deck-counter` | slide counter; deck.js writes into `[data-deck-counter]` |
| `deck-progress` | progress rail; its inner `<i data-deck-progress>` is the fill |
| `deck-stage` | slide container; must carry `data-deck` for deck.js to find it |
| `deck-slide` | one slide; `data-act` groups it in the overview, `data-short` labels it |
| `deck-inner` | the slide's content box, centered and width-capped |
| `deck-eyebrow` | small rule-trailing label above the slide heading |
| `deck-lede` | opening line on a title slide |
| `deck-notes` | speaker note; hidden until toggled, and printed with the slide |
| `deck-stat-row` | row of figures on a slide |
| `deck-stat` | one figure; `<b>` the value, `<span>` its label |
| `deck-grid` | ruled column layout; add `two`, `three`, or `four` |
| `deck-cell` | one cell of a `deck-grid`; `<b>` heading, `<p>` body |
| `deck-facts` | ruled key/value list; `.k` is the key, `.v` the value |
| `deck-callout` | the sentence you want repeated back to you; `cool` tints it |
| `deck-figure` | figure on a slide; a direct child `<svg>` is sized to fit |
| `deck-table-wrap` | scroll container for a table too wide for the frame |
| `deck-overview` | overview panel root; mark it `data-deck-overview` and deck.js fills it |
| `deck-ov-act` | one act group inside the overview (generated) |
| `deck-ov-grid` | the slide grid within an act (generated) |
| `deck-ov-item` | one slide thumbnail in the overview (generated) |
| `deck-help` | keyboard-help overlay; mark it `data-deck-help` |
| `deck-help-card` | the card inside the help overlay |

artoo-deck owns `deck-`. A class starting with one of those that the vendored stylesheet does not define is a guess, and `artoo build` reports it with the nearest real class. Your own classes belong outside those prefixes.

---

# artoo-diagrams 1.10.1+artoo.1

*On its own: `artoo docs artoo-diagrams`*

Mermaid-syntax diagrams rendered offline by Pele 1.10.1: flowcharts, sequences, state, ER, timelines, and more, following the page's theme.

Mermaid-syntax diagrams that render offline, in the page's own type and
colors. Vendored into an artifact at `site/lib/artoo-diagrams/` with a pinned
hash; `artoo lib update artoo-diagrams` is the explicit upgrade boundary.

You write [Mermaid](https://mermaid.js.org) syntax. The renderer is
[Pele](https://pele.run), which draws every diagram type built into Mermaid
12.1 with its own layout and reads its colors from CSS, so a diagram recolors
when the reader switches theme instead of being redrawn.

| file | role |
|------|------|
| `pele.global.js` | Pele's browser bundle (MIT), byte for byte as published on npm |
| `diagrams.js` | finds Mermaid blocks, renders them with Pele, reports errors |
| `diagrams.css` | binds Pele's colors and fonts to the artoo-kit tokens; node roles |
| `PELE-LICENSE` | Pele's license |

## Use it

Markdown content needs nothing: a ` ```mermaid ` fence in an article or
collection page renders as a diagram, and `artoo build` vendors this library
the first time a page needs it.

Hand-written HTML vendors the library once:

```bash
artoo lib add artoo-diagrams
```

then loads it on each page that has a diagram, and only those (the bundle
is about 700 KB):

```html
<link rel="stylesheet" href="lib/artoo-diagrams/diagrams.css">
...
<script src="lib/artoo-diagrams/pele.global.js"></script>
<script src="lib/artoo-diagrams/diagrams.js"></script>
```

and writes each diagram as a source block inside the kit's `diagram`
wrapper:

```html
<figure class="diagram">
<pre class="mermaid">flowchart LR
  accTitle: How a build gates publication
  accDescr: Content is rendered, checked by the firewall, then verified.
  render[Render content] --> firewall{{Publish firewall}}:::accent --> verify[Verify]
</pre>
  <figcaption>The takeaway, in a sentence.</figcaption>
</figure>
```

Rendering waits for the document, so the two scripts can sit anywhere in
either order. With JavaScript off, the reader sees the source.

## Write diagrams that hold up

- **Use a diagram for structure, inline SVG for numbers.** Flows, sequences,
  states, schemas, architecture, and timelines are what diagrams are for. A
  chart that carries a quantitative claim belongs in inline SVG on the
  `--chart-*` palette, with its vintage, denominator, and source in the
  `figcaption`. Pie, XY, and quadrant charts are fine for sketches.
- **Start with `accTitle:` and `accDescr:`.** They become the SVG's
  accessible name and description. The `figcaption` carries the point.
- **Name a role, never a color.** Mermaid `style`, `classDef`, and `linkStyle`
  take literal colors, which stay fixed when the theme changes, and the parser
  rejects `var(--token)` there. Tag the node instead, and the color comes from
  the kit:

  | role | use |
  |------|-----|
  | `accent` | the node the diagram is about: a gate, a decision, the subject |
  | `warn` | a failure path or a risk |
  | `muted` | context: out of scope, deprecated, external |

  Write `id[Label]:::accent`, or `class a,b muted` for several nodes. For a
  role of your own, tag `:::name` and style it in your stylesheet with tokens,
  e.g. `.mermaid .pele-node.name > :not(text) { stroke: var(--success); }`.
- **Escape `<`, `>`, and `&` in hand-written HTML.** The block's text is the
  source, so write `A[List&lt;T&gt;]`. Markdown fences need no escaping.
- **Let wide diagrams leave the prose column.** In an article, put
  `article-breakout` on the figure (`class="diagram article-breakout"`).
  Below 640 px a left-to-right flowchart turns vertical, and a diagram that
  had to shrink gets zoom controls, so write one that reads in either
  direction.
- **Leave out what Pele does not render.** HTML labels, `click` callbacks,
  Mermaid themes and `themeVariables`, ZenUML, and other external Mermaid
  plugins are not supported. Labels support line breaks, bold, and italic.
- **One diagram, one idea.** Past a dozen or so nodes, split it, or use
  `artoo-map` for a code graph a reader should explore.

## Check it

A syntax error is drawn in place of the diagram, and `diagrams.js` logs it to
the console with the block's id (or position), the line and column, and the
offending line. `artoo verify --browser` reports those messages, so give
diagrams an `id` when a page has several.

`artoo build` fails a page that has a Mermaid block but does not load
`pele.global.js` and `diagrams.js`, because that page would ship a wall of
source text.

## Diagrams added after load

An explorer that inserts diagram source at runtime calls
`ArtooDiagrams.render(element)` to render the blocks inside `element`. It
returns Pele's mounted diagrams; each re-fits itself when its container
resizes.

## Versions and mermaid.js

The library's version is `<pele version>+artoo.<revision>`, so `artoo lib
status` shows which Pele an artifact carries. A newer artoo ships a newer Pele;
your artifact keeps its pinned copy until you run `artoo lib update
artoo-diagrams`. Look at the page after updating: Pele's layout still changes
between minor releases.

If you need mermaid.js itself, for HTML labels or an external Mermaid plugin,
install the separate `artoo-mermaid` library and load `mermaid.min.js` with
the kit's `kit.js` instead. Use one renderer per page.

## Class vocabulary

| class | role |
|---|---|
| `mermaid` | a Mermaid source block (`<pre class="mermaid">`); diagrams.js replaces it with the rendered diagram, themed from the kit's tokens |

---

# artoo-grid 0.2.0

*On its own: `artoo docs artoo-grid`*

Declarative dense data tables: sorting, frozen identity columns, in-cell bars and heat, column picker, CSV export, and an honest missing-value convention.

Dense comparison tables for artifacts whose evidence is a few hundred entities
across a few dozen measures. Vendored into an artifact at
`site/lib/artoo-grid/` with a pinned hash; `artoo lib update artoo-grid` is the
explicit upgrade boundary.

| file | role |
|------|------|
| `grid.js` | the `ArtooGrid` wrapper: column specs, formats, toolbar, domains |
| `grid.css` | the artoo-kit-token theme over Tabulator's structural stylesheet |
| `tabulator.min.js` | vendored Tabulator 6.3.1 (MIT), virtual scroll and frozen columns |
| `tabulator.min.css` | Tabulator's structural stylesheet |

Load Tabulator before the wrapper:

```html
<link rel="stylesheet" href="lib/artoo-grid/tabulator.min.css">
<link rel="stylesheet" href="lib/artoo-grid/grid.css">
<script src="lib/artoo-grid/tabulator.min.js"></script>
<script src="lib/artoo-grid/grid.js"></script>
```

## Declaring a grid

```js
ArtooGrid.create("#table", {
  data: rows,
  index: "id",
  columns: [
    { field: "rank", title: "#",        format: "rank", frozen: true, width: 52 },
    { field: "name", title: "District", format: "name", frozen: true, sub: "county" },
    { field: "hhi",  title: "Median household income",
      group: "Means",       format: "currency", bar: true },
    { field: "prof", title: "Proficient",
      group: "Achievement", format: "percent",  heat: true, decimals: 1 },
    { field: "move", title: "Δ rank",   format: "delta", invert: true },
  ],
  toolbar: { search: true, columns: true, download: "districts.csv" },
  note: "ACS 2020-2024 · NJDOE 2024-25 · missing values shown as —, never as 0",
});
```

### Column keys

| key | meaning |
|-----|---------|
| `field`, `title` | the data key and its header |
| `format` | `text` `name` `chip` `number` `currency` `percent` `rank` `delta` |
| `group` | column-group header; consecutive columns sharing one are grouped |
| `decimals`, `scale` | number precision; `scale: 100` for a 0-1 fraction in `percent` |
| `bar` | proportional background bar, domain from the data |
| `heat` | percentile tint, domain from the data |
| `invert` | for `delta`: up is bad, so colour by meaning not by sign |
| `invertHeat` | for `heat`: low values are the notable ones |
| `frozen` | pin to the left; frozen columns are not hideable |
| `hidden` | ship the column, start it off |
| `sub` | for `name`: a second field rendered smaller beneath the label |
| `missingLabel` | override the em dash for this column |

### Two rules the grid will not let you break

**Missing is not zero.** `null`, `undefined`, `NaN` and `""` all render as an
em dash in `.grid-missing`, in every format, and that is not configurable. A
grid that lets a caller print `0` for a value nobody measured will eventually
do it by accident, and in a ranking a zero that should have been a blank is the
most expensive kind of wrong number. A row carrying `<field>_state ===
"suppressed"` says *suppressed* instead, because a value withheld by a
publisher and a value never collected are different facts.

**Blanks sort to the bottom, both directions.** A district with no measurement
has not scored zero and must never top a ranking by ascending sort.

### Domains are recomputed, never cached

`bar` and `heat` derive their scale from the data each time `setData` runs. A
table that reranks on a slider therefore rescales honestly instead of comparing
today's numbers against yesterday's maximum — a stale bar looks like a
measurement and is an artefact.

## Live reranking

```js
const grid = ArtooGrid.create("#table", { … });
slider.addEventListener("input", () => {
  grid.updateRows(rescore(rows, readWeights()));  // keeps scroll and sort
});
```

`setData` replaces the row set and redraws; `updateRows` updates in place and
holds the viewport, which is what a weight slider wants. Both rescale the
derived visuals.

## Saving what the reader configured

Pair it with `ArtooStore` from artoo-kit, which writes named JSON documents
into the artifact's `state/` directory through `artoo serve` — real files
outside `site/`, so the deploy firewall never publishes them and a diff shows
what changed:

```js
const presets = ArtooStore.open("presets");
await presets.save("means-heavy", weights);
```

With nothing serving the artifact it falls back to `localStorage` and reports
`durable === false`; print `presets.describe()` rather than let a reader
believe a save reached disk when it did not.

## The `grid-` prefix

Every class this library defines is prefixed `grid-`, and a test enforces it.
Tabulator's own `tabulator-*` classes are restyled in `grid.css` against the
artoo-kit tokens, but they are Tabulator's vocabulary, not this library's
contract — never write one by hand.

## Licence

Tabulator is MIT-licensed, © Oli Folkerd. The vendored files are unmodified
`tabulator-tables@6.3.1` dist builds.

## Class vocabulary

| class | role |
|---|---|
| `grid` | the grid root; ArtooGrid.create() adds it and fills the element |
| `grid-toolbar` | row above the table holding the count, filter, column picker, and export |
| `grid-count` | live row count; reads 'n of N rows' whenever a filter is active |
| `grid-search` | the free-text filter input |
| `grid-btn` | toolbar button |
| `grid-picker` | column-picker wrapper (button plus popover) |
| `grid-picker-menu` | the popover listing hideable columns; frozen columns are omitted |
| `grid-picker-group` | a column-group heading inside the picker |
| `grid-body` | the element Tabulator mounts into |
| `grid-note` | footer strip for vintage, denominator, and source |
| `grid-num` | tabular right-aligned figures |
| `grid-name` | primary row label |
| `grid-sub` | smaller secondary label under a name, from a column's `sub` field |
| `grid-rank` | bold ordinal |
| `grid-bar` | numeric cell with a proportional background bar; `--grid-fill` is the width |
| `grid-heat` | numeric cell tinted by percentile; `--grid-alpha` is the strength |
| `grid-delta` | signed change, coloured by direction via `data-sign` |
| `grid-chip` | short categorical label in a pill |
| `grid-missing` | the em-dash rendering of an unmeasured or suppressed value |
| `grid-nil` | muted text for a deliberately empty cell |
| `grid-row-pinned` | row held at the top of the table for comparison |

artoo-grid owns `grid-`. A class starting with one of those that the vendored stylesheet does not define is a guess, and `artoo build` reports it with the nearest real class. Your own classes belong outside those prefixes.

---

# artoo-kit 0.8.0

*On its own: `artoo docs artoo-kit`*

Long-form article layout, evidence regions, and the provenance panel.

The built-in site library: an Artoo-owned, self-contained foundation for public
artifacts. Vendored into an artifact at `site/lib/artoo-kit/` with a pinned
hash — changing the kit here does not rewrite already-vendored bytes; `artoo
lib update artoo-kit` is the explicit upgrade boundary.

Files (all under `assets/`, all vendored):

| file | role |
|------|------|
| `tokens.css` | design tokens: color, type roles, spacing, the chart palette |
| `base.css` | resets and page defaults |
| `article.css` | long-form article grid (prose + margin gutter) |
| `components.css` | nav, cards, callouts, badges, stats, colophon, provenance |
| `kit.js` | theme toggle, mobile nav, legacy mermaid.js boot |
| `provenance.js` | provenance panel hydration + in-prose claim anchors |
| `favicon.svg` | default site icon (link it to silence the `/favicon.ico` 404) |

## Favicon

Every page should carry a favicon; without one the browser requests
`/favicon.ico` and logs a 404 on every load. Reference the vendored icon in
`<head>`:

```html
<link rel="icon" href="lib/artoo-kit/favicon.svg">
```

## Provenance panel

The panel renders an artifact's lineage — sources with grades and
independence, claims with status and verification-method badges, counts, and
an optional notebook vintage — from `site/data/provenance.json`. `artoo build`
and `artoo provenance` write it from provider-neutral `artoo-evidence/1` or
adapt an attached Flip notebook's `flip-render/1` projection.

It is **progressive**: with no projection present the panel hides itself and
the page reads exactly as authored. To add it to a hand-authored page:

```html
<!-- where the panel should appear -->
<section class="provenance article-breakout" data-artoo-provenance></section>

<!-- before </body>: the offline data global, then the hydrator -->
<script src="data/provenance.js"></script>
<script src="lib/artoo-kit/provenance.js"></script>
```

`data/provenance.js` sets `window.__ARTOO_PROVENANCE__` so the panel hydrates
from a `file://` URL with no server (a bare `fetch()` of a sibling JSON is
blocked under `file://`; a `<script>` assignment is not). If you omit it, the
hydrator falls back to `fetch("data/provenance.json")`, which works over HTTP.
Both files are written by `artoo provenance`; the deterministic article and
collection renderers wire the panel automatically when a projection exists.

### Claim anchors

`provenance.js` also rewrites `[C7]` / `[A3]` bracket references **that exist
in the projection** into stable anchors: the first occurrence gets
`id="claim-C7"` and every occurrence links to its entry in the panel
(`#prov-claim-C7`), with the claim text and status in a tooltip. Ids the
projection does not know are left untouched, and text inside `<code>`/`<pre>`
is never rewritten. Scope defaults to `[data-claim-anchors]`, then `<main>`,
then the body — put `data-claim-anchors` on the article element to bound it.

This is done client-side, matching the kit's existing client-side enhancements
(theme, nav, legacy mermaid.js). Nothing rewrites the committed HTML; the anchors are an
enhancement layered at load time.

## Media figures: loops, toggles, tables

Markdown content (`[content]`) renders these without hand-written HTML:

```markdown
![What happens, for a reader who can't see it](assets/clip.mp4 "Caption. Say if it's sped up.")
```

A paragraph that is only an image whose `src` is a video (`.mp4`, `.webm`, `.mov`,
`.m4v`) becomes an `article-figure` holding a silent loop: `<video class="article-loop"
autoplay loop muted playsinline>`, named by the alt text through `aria-label`. If
`assets/clip.poster.png` exists in `site/`, it becomes the `poster`. kit.js pauses loops
and shows controls for readers who prefer reduced motion; a click toggles play. Keep
words out of the loop itself: the alt text and caption carry them.

```markdown
```toggle
Proposed: ![The backups box is dashed: proposed](assets/dashed.png)
Accepted: ![The same box, now solid](assets/solid.png)
caption: Flip between the proposal and the accepted change.
```
```

A `toggle` fence of 2–4 `Label: ![alt](src)` lines (images or videos) and an optional
`caption:` line becomes an `article-toggle` figure: one panel at a time, switched by
labelled radios. Pure CSS, so it works from `file://`. Use it when the reader should flip
a state themselves rather than watch a loop of it.

Pipe tables (`| a | b |`, a `|---|---:|` separator, then rows) render as `<table>`;
`---:` right-aligns a column and marks it `numeric`. Each sits in an `article-table`
region that scrolls sideways when the table is wider than the screen, and long inline code
wraps (in a cell or a paragraph), so neither widens the page on a phone.

A collection page can open with front matter between `---` lines: `description` (its own
dek and meta description), `kicker`, `image` (a site-relative social card, written as
`og:image` with a large Twitter card) and `image_alt`. Other keys are ignored.

## Gotchas

### SVG and CSS custom properties

`fill="var(--accent)"` **does not resolve** — SVG presentation *attributes* are
not CSS and custom properties are not looked up there. Style SVG with CSS
instead: give shapes classes and set `fill` in a `<style>` block or a
stylesheet.

```html
<!-- broken: attribute never resolves the token -->
<rect fill="var(--chart-1)" .../>

<!-- works: class + CSS property -->
<style>.bar-1 { fill: var(--chart-1); }</style>
<rect class="bar-1" .../>
```

(An inline `style="fill: var(--chart-1)"` attribute also works, because that is
the CSS `fill` *property*, not the SVG presentation attribute.)

### Charts and figures

The kit ships no chart primitive. Author a quantitative chart as inline SVG
(self-contained, no runtime); author structure — flows, sequences, states,
schemas, timelines — as a Mermaid diagram with the `artoo-diagrams` library
(`artoo docs artoo-diagrams`). Guidance:

- **Palette.** Use the Okabe–Ito tokens `--chart-1 … --chart-8` (in
  `tokens.css`); they are colorblind-safe and ordered by draw order. Do not
  invent per-chart colors.
- **Theme.** Because token colors differ between light and dark, style series
  with the classes-plus-`<style>` pattern above so a chart follows the theme;
  never bake a hex value that only reads in one theme.
- **Numerics.** Use `font-variant-numeric: tabular-nums` (the `--font-numeric`
  role sets it) so figures align.
- **Honesty.** A figure earns its place by helping the reader make a valid
  comparison — include vintages, denominators, and a source note in the
  `<figcaption>`, so the comparison can be interpreted honestly.

### Shared document saves

`store.js` implements protocol-2 ArtooStore. Await `load(name)` before editing;
`save(name, document)` uses its revision for conflict protection. Surface errors
and save status: durable disk writes do not commit or push Git. HTTP failures
never silently become browser-only saves. Inspect and explicitly compare legacy
browser data with `inspectLocal` / `importLocal`; inspect failed writes with
`inspectPending` / `recoverPending`. See `artoo docs serve` for the complete
authoring, migration, and recovery contract.

## Class vocabulary

| class | role |
|---|---|
| `article` | the long-form grid container; children sit in the prose column by default |
| `article-full` | wrapper: let this child run edge to edge |
| `article-breakout` | wrapper: wider than prose, still centered |
| `article-mrow` | row that pairs a prose block with a margin note |
| `article-masthead` | top bar; `__name` is the artifact link, `__label` the standfirst tag |
| `article-header` | the title block at the head of the piece |
| `article-kicker` | small label above the title (conventionally the artifact kind) |
| `article-title` | the h1 |
| `article-dek` | standfirst under the title |
| `article-byline` | publication line; wrap the date in `<time datetime=…>` |
| `article-lede` | opening paragraph, set larger |
| `article-figure` | figure wrapper for a table or chart; put vintage, denominator, and source in the `figcaption` |
| `article-table` | scroll region around a table (Markdown pipe tables get one): wide tables scroll sideways inside it instead of widening the page |
| `article-loop` | a silent looping video inside an `article-figure` (Markdown: an image whose src is a video); named by `aria-label`, paused with controls for reduced motion, click toggles |
| `article-toggle` | figure variant: one of 2–4 images or loops shown at a time, switched by labelled radios; `__bar` holds the labels, `__panels` the media (Markdown: a ```` ```toggle ```` fence) |
| `article-marginnote` | right-gutter note, inline on mobile; `--def`, `--source`, `--callout` variants, `.term` for the term defined |
| `article-pullquote` | breakout-width quote; attribute it with `<cite>` |
| `article-pullnumber` | breakout-width figure; `.num` is the value, `.label` its caption |
| `article-colophon` | how-it-was-made block closing the piece |
| `site-nav` | multi-page nav; `.brand`, `.nav-toggle` + `.nav-links`, and current-page links (wired by kit.js) |
| `page` | container for a non-article page; `--narrow` tightens it |
| `card-grid` | responsive grid of navigation cards |
| `nav-card` | navigation card; use on links to destinations, not as a generic container |
| `card` | deprecated compatibility alias for nav-card; prefer nav-card in new work |
| `callout` | boxed aside with a `.callout-title`; `--warn`, `--danger`, `--success` variants |
| `badge` | inline label; `--accent`, `--success`, `--warn` variants |
| `stat-row` | row of figures |
| `stat` | one figure; `.value` then `.label` |
| `toc` | table of contents, as an ordered list |
| `diagram` | wrapper for an inline SVG or an artoo-diagrams Mermaid block |
| `colophon` | build and provenance footer |
| `collection-pager` | previous and next navigation at the foot of a collection page |
| `numeric` | tabular figures, so columns of numbers align |
| `no-print` | hide this element when the page is printed |
| `provenance` | the provenance panel root; mark it `data-artoo-provenance` and provenance.js fills it from data/provenance.json |
| `claim-ref` | written by provenance.js onto `[C7]`-style references; never hand-author it |

artoo-kit owns `article-`, `provenance`. A class starting with one of those that the vendored stylesheet does not define is a guess, and `artoo build` reports it with the nearest real class. Your own classes belong outside those prefixes.

---

# artoo-map 0.1.0

*On its own: `artoo docs artoo-map`*

Interactive evidence-bearing code maps: saved views, focus, paths, truth layers, source receipts, and JSON, Mermaid, and SVG exports.

An interactive projection of an `artoo-codegraph/1` document. The library does
not analyze code and does not decide what the architecture is. It renders the
truth class and evidence receipt that each node or relationship already carries.

Vendored into an artifact at `site/lib/artoo-map/` with a pinned hash;
`artoo lib update artoo-map` is the explicit upgrade boundary.

| file | role |
|---|---|
| `cytoscape.min.js` | vendored Cytoscape.js 3.34.0 graph renderer (MIT) |
| `map.js` | bounded views, focus, directed paths, receipts, and exports |
| `map.css` | restrained Artoo UI over the canvas and accessible fallback |
| `CYTOSCAPE-LICENSE` | upstream MIT license text |

## Loading a map

The graph can be loaded from a JavaScript global, so the artifact still works
from `file://` without a fetch or local server:

```html
<link rel="stylesheet" href="lib/artoo-map/map.css">
<div id="code-map" class="map" aria-label="Interactive code map"></div>
<script src="lib/artoo-map/cytoscape.min.js"></script>
<script src="data/codegraph.js"></script>
<script src="lib/artoo-map/map.js"></script>
<script>
ArtooMap.create("#code-map", {
  graph: window.ARTOO_CODEGRAPH,
  view: "overview",
  budget: 120,
});
</script>
```

`artoo generate explainer` writes and wires these files automatically. For a
hand-authored artifact, generate the graph with `artoo map build`, put the same
JSON object on `window.ARTOO_CODEGRAPH`, and vendor this library.

## What the reader can ask

- switch a saved view;
- focus on one path, symbol, package, entry point, or external dependency;
- traverse incoming, outgoing, or both directions at a bounded depth;
- find a shortest directed path between two nodes;
- toggle declared, static, runtime, and inferred truth layers independently;
- select any node or edge to see its reason and source coordinates;
- export the current bounded view as JSON, Mermaid, or a static SVG;
- use the node table instead of the canvas with a keyboard or screen reader.

The opening layout is breadth-first and stops moving after it is computed. It
does not use a live force simulation. A bounded view is the product; a repository
hairball is not.

## Truth is not blended

The four layers mean different things:

| layer | establishes |
|---|---|
| `declared` | intended or configured architecture |
| `static` | a relationship found in the source snapshot |
| `runtime` | behavior observed in one named trace |
| `inferred` | a proposed label, group, or relationship |

Turning a layer off hides it. Turning one on does not promote it. An inferred
edge stays visibly inferred; a runtime edge does not imply unrecorded paths are
absent.

## Source links and dirty worktrees

`snapshot.source_base` may name a pinned repository URL. Artoo omits it for a
dirty worktree because the local coordinate may not exist at the recorded
commit. The inspector still prints the path and line, while the status names
the dirty snapshot rather than linking to misleading source.

## Licence

Cytoscape.js is MIT-licensed, © 2016-2026 The Cytoscape Consortium. The vendored
license is in `CYTOSCAPE-LICENSE`. Artoo's wrapper and styles are MIT-licensed
with the rest of Artoo.

## Class vocabulary

| class | role |
|---|---|
| `map` | interactive code-map root enhanced by ArtooMap.create() |
| `map-toolbar` | wrapping controls for views, focus, layers, paths, and exports |
| `map-field` | label and input pair inside the toolbar |
| `map-search` | node focus or path endpoint input |
| `map-select` | saved-view, direction, or depth selector |
| `map-layers` | truth-layer checkbox group |
| `map-layer` | one declared, static, runtime, or inferred layer toggle |
| `map-actions` | map reset and export action group |
| `map-button` | button used for actions and accessible node focus |
| `map-status` | aria-live summary of the visible bounded view and coverage |
| `map-stage` | responsive graph and evidence-detail split |
| `map-canvas` | Cytoscape canvas container |
| `map-detail` | selected node or edge evidence inspector |
| `map-detail-title` | heading inside the evidence inspector |
| `map-detail-meta` | kind, truth, confidence, and relationship metadata |
| `map-evidence` | list of source coordinates supporting the selection |
| `map-coordinate` | one source coordinate, optionally linked to the pinned revision |
| `map-truth` | truth-class badge |
| `map-fallback` | keyboard-navigable table alternative to the graph canvas |
| `map-table` | node table inside the accessible fallback |
| `map-empty` | message shown when filters remove every node |

artoo-map owns `map-`. A class starting with one of those that the vendored stylesheet does not define is a guess, and `artoo build` reports it with the nearest real class. Your own classes belong outside those prefixes.
