# AGENTS.md — working with verbal-editor

Guidance for coding agents that add Verbal to an app or change this package. The full docs are Markdown: every page is served as `docs/<slug>.md` next to the site, `llms.txt` indexes them and `llms-full.txt` has them all in one file.

## What it is

A block editor for the web with no runtime dependencies. Every block is its own `contenteditable`; the browser performs plain typing and the view reads it back, so typing costs zero React renders. Core ships one block type (paragraph); everything else is an opt-in module that brings its own CSS.

## Adding it to an app

```js
import 'verbal-editor/tokens.css';                 // once: the whole theme
import { Editor } from 'verbal-editor';            // the editor (no DOM until mounted)
import { Blocks, useEditor } from 'verbal-editor/react'; // React binding
import { mount } from 'verbal-editor/dom';         // or: no framework
import preset from 'verbal-editor/preset';         // every module, or import them one by one:
import heading from 'verbal-editor/blocks/heading';
import bold from 'verbal-editor/marks/bold';
import slash from 'verbal-editor/ui/slash';
```

- React: `const editor = useEditor({ blocks: [heading], marks: [bold], ui: [slash] }); return <Blocks editor={editor} />;`
- No framework: `mount(new Editor({ ... }), document.getElementById('editor'))` returns an unmount function.
- Next.js: the editor renders in the browser only — load its component with `dynamic(() => import(...), { ssr: false })`.
- Read-only: pass `editable: false` and leave out UI modules (slash, toolbar, dnd).
- Save: `editor.on('change', () => save(editor.getDoc()))`. Load: the `doc` option, or `editor.setDoc(doc)`.

## Rules

1. **Never mutate the document.** `editor.doc` and `getDoc()` are read-only views. Change it with a transaction: `editor.dispatch(editor.tx().insertText(id, 0, 'Hi'))`, or a command (`insert`, `setType`, `toggleMark`, `indent`, `remove`, `paste`).
2. **Never write into a block's editable element.** The view owns it. Use transactions; the view repaints.
3. **Import modules explicitly.** Importing registers nothing; pass modules to the editor. Each subpath is its own entry — do not import from `dist/` paths.
4. **Joi is build-time only.** `verbal-editor/config` and `verbal-editor/server` import Joi. Never import them from browser code.
5. **Validate on the server.** Loading in the browser repairs bad documents; `validateDoc(doc, config)` from `verbal-editor/server` rejects them with paths.
6. **Style with tokens.** Override `--v-*` custom properties after `tokens.css`; target block types with `[data-type="..."]`.

## Document shape

```json
{ "version": 1, "root": "doc", "blocks": {
  "doc": { "type": "doc", "children": ["a"] },
  "a": { "type": "heading", "props": { "level": 2 }, "content": [{ "text": "Plan", "marks": [{ "type": "bold" }] }] }
} }
```

Blocks are a flat map; `children` gives order and nesting; `content` is runs of text with sorted marks; void blocks (divider, image, table, chart) have no `content`.

## Writing a module

A block module is a plain object: `{ type, schema, create, view, slash, input, parse, serialize, next, mount }` — only `type` is required. Marks: `{ type, tags, shortcut, markdown, md, toolbar }`. UI: `{ name, mount(editor) → cleanup }`. Keep non-text UI out of the editable element (or mark it `data-skip`), put positioned UI in the top layer, and hide controls under `[data-readonly]`.

## Working in this repository

- `npm test` (unit), `npm run build`, `node scripts/check.js` (size budgets and architecture rules), `npm run site`, then `npx playwright test`.
- Budgets live in `docs/01-prd.md` and `docs/02-ftr.md` and are enforced by `scripts/check.js`. Never raise a budget or weaken a check or a test to make a change fit.
- Core (`src/core/`, `src/index.js`) never imports React or a block other than paragraph; only `src/core/view.js` touches the DOM; `execCommand` and HTML5 drag-and-drop are prohibited.
- Site content is Markdown under `demo/content/`; reference tables and figures are generated by `scripts/content.js` from the package and `bench/results.json`, never typed by hand.
