# 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 deploy docs/spending-report     # firewall-stage, then publish
```

`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`.

## 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. **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.
2. **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.
3. **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.
4. **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. 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.

---

# 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
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 = ""

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

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

[[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.
- `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`.
- `[[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.

## Optional browser proof

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

Browser verification is a soft Playwright integration. When requested, it
firewall-stages the site, opens every publishable page in Chromium at phone and
desktop widths, reports page and console errors, checks horizontal overflow,
and optionally records full-page screenshots. Core Artoo does not depend on Playwright; if it is absent the
command names the install needed or lets you omit `--browser`.

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

---

# 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`*

Most artifacts are finished the moment they render, and `file://` is the right
way to read them. Explorers are the exception. A page whose value is the
configuration a reader arrived at — a set of weights, a chosen comparison, a
saved view — is only useful the second time if that configuration survives.

```bash
artoo serve site/my-explorer          # http://127.0.0.1:8765/
artoo serve site/my-explorer --open --port 9000
```

## What it serves

Files read live from `site/`, with **every request checked against the same
firewall rule a deploy applies**. So the server shows exactly what a deploy
would show — a withheld file is *absent*, not merely unlinked, and a page that
only works because it reached a private working file fails here rather than
after publication — while an edit still appears on reload without a restart.

A withheld path answers 404 whether or not the file exists, so the refusal is
not an oracle for what the artifact is hiding.

Loopback only, no authentication. This is a working surface, not a host.

## The state store

One API lives under `/_artoo/state`, and it reads and writes named JSON
documents into the artifact's own `state/` directory:

| request | effect |
|---------|--------|
| `GET /_artoo/state/<collection>` | list documents: name, updated, bytes |
| `GET /_artoo/state/<collection>/<name>` | read one document |
| `PUT /_artoo/state/<collection>/<name>` | write it, body `{"document": …}` |
| `DELETE /_artoo/state/<collection>/<name>` | remove it |

`state/presets/means-heavy.json` is a real file next to the work, committed
with it and readable in a diff. Browser storage is the cheap alternative and
the wrong one: per-browser, invisible to the repo, lost on a profile reset,
and impossible for a second person to read.

**`state/` is a sibling of `site/`.** That is the whole point of putting it
there — the firewall only ever ships from the site root, so nothing a reader
saves can reach a publish by accident.

Writes are atomic (temp file, then rename), so a crashed or concurrent save
cannot leave a half-parsed preset the page will then refuse to load.

## Two rules on the write path

Collection and document names must match `[A-Za-z0-9][A-Za-z0-9._-]{0,63}`.
The path is built from a validated name and never from raw request text, so
`..` and absolute paths cannot appear. Bodies are capped at 4 MB; a saved view
is small, and anything larger is a mistake worth surfacing.

## From the page

`ArtooStore` in artoo-kit is the client:

```js
const presets = ArtooStore.open("presets");
await presets.save("means-heavy", weights);   // → state/presets/means-heavy.json
await presets.list();                          // [{name, updated, bytes}, …]
await presets.load("means-heavy");
await presets.remove("means-heavy");
```

Opened from `file://` or a plain static host there is no server to write to, so
the store falls back to `localStorage` and reports `durable === false`. Print
`presets.describe()` in the interface rather than let a reader believe a save
reached disk when it did not.

---

# 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, 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. 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.

### `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.

---

# 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-grid 0.1.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.5.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, optional mermaid 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, mermaid). Nothing rewrites the committed HTML; the anchors are an
enhancement layered at load time.

## 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 charts as inline SVG (self-contained,
no runtime) or as a Mermaid diagram. 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.

## 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-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 a 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.
