# Plugins

> Every plugin, what each one does, and how to load it by name.

Source: https://clayjs.com/docs/plugins/

Everything past the core is a plugin. Plugins load through the URL, in the browser only: `?plugins=cms` asks for one by name, `?plugins=wire,sync` for several, comma separated, and `?exclude=source` turns a default one off. Whatever a page loads is then reachable from `clay`.

Three plugins have no page of their own. `indicator` is a ready-made save-status chip driven by the `clay:save-*` events; it shows Saving… and Saved, and with `clay-ui.js` loaded a failed or offline save is reported by a persistent toast instead of the chip, so no save is reported twice. `sync` is live sync: outside file changes and other open tabs merge into the page in place, preserving focus, caret, and unsaved edits (a three-way merge: both sides' edits survive, and the incoming change wins where both changed the same words); it powers `clay.morph` and the `merge="name"` JSON script-tag merging. `demo` saves into browser storage instead of a host, which is what clayjs.com's live demos use; `clay.demo.reset()` forgets the browser copy.

## cms

A content sidebar that builds itself from your page; load it with `?plugins=cms`. [cms](https://clayjs.com/docs/plugins/cms/)

Declare content with a rules tag; whoever has edit access sees friendly fields (text, rich text, images, lists), not markup:

```html
<script src="https://clayjs.com/v1/clay.js?plugins=cms"></script>
<script data-rules-name="cms" data-rules-version="1" type="application/json">
{
  "title":    ".page-title",
  "intro":    "p.intro",
  "avatar":   "img.avatar@src",
  "tags":     "ul.tags li[]",
  "products": [".product", { "name": ".name", "price": ".price" }]
}
</script>
```

Each entry is a field: a name, then the selector it edits. `@src` becomes an image-upload control, `li[]` a reorderable list, and the `products` shape a list of cards. Open the sidebar with `?cms=true` on the URL or `clay.cms.open()`. The same rules tag can carry more tokens (`data-rules-name="api cms"`) to also power the data library's JSON API.

## quickcrop

A crop modal for image uploads. It loads with cms, or you can ask for it by name. [quickcrop](https://clayjs.com/docs/plugins/upload/)

`const shot = await clay.quickcrop(file, { aspect: 16/9 })` resolves `{blob, dataURL, width, height}` or `null` if cancelled. The CMS uses it for any image field marked `data-hcms-crop="1:1"` (aspects: `1:1`, `16:9`, `free` on the attribute; a plain number in code).

## richclay

The `editable` attribute: floating toolbar, clean HTML output. On by default, so it needs no plugin list. [richclay](https://clayjs.com/docs/plugins/richclay/)

Tokens: `single-line`, `no-toolbar`, `toolbar-on-select`. In the saved file the attribute is an inert marker; no editor chrome is written to disk.

## bind-text

Keeps long text in a record's text element and the editor outside the saved records, by binding a textarea in a `clay="freeze"` region to one record. Ask for it by name. [bind-text](https://clayjs.com/docs/plugins/bind-text/)

## unsent

Text typed into chrome and not yet sent outlives the view that holds it. Ask for it by name. [unsent](https://clayjs.com/docs/plugins/unsent/)

An input or textarea inside `clay="editor-ui"` or `clay="freeze"` marked `unsent="<name>"` is kept per record (its nearest `[data-id]` ancestor) and name in this tab, outside the file: reopening the detail, a sync redraw or a reload brings the text back, and the close warning stays on while any of it is kept. Nothing enters the file. Sending or cancelling calls `clay.unsent.clear(input)`. Events: `clay:unsent-restored`, bubbling from the control with `detail: {base, current, stale}` (`stale` says the record's saved value changed since the person started typing), and `clay:unsent-changed` on `document` with `detail: {count}`.

## menu

An Edit switch and a small menu in the top right, shown only to someone who can edit, so they can get back into editing without the keyboard. Ask for it by name. [menu](https://clayjs.com/docs/plugins/menu/)

## undo

Document-wide undo/redo built on DOM mutations: moves, deletions, and attribute changes all reverse cleanly. Ask for it by name. [undo](https://clayjs.com/docs/plugins/undo/)

It respects `clay="no-undo"` regions.

## upload

`clay.upload(file)` posts a file you supply to the host and resolves with its URL. It is not a picker. Ask for it by name. [upload](https://clayjs.com/docs/plugins/upload/)

## people

Who is editing and who wrote what: `clay.me`, `clay.people`, `clay.author`. Ask for it by name. [people](https://clayjs.com/docs/plugins/people/)

Names live in the document as `<div clay-people hidden><data value="id">Name</data></div>`, and authored elements carry `data-by="id"`.

## message

A visitor's contact form to the document's owner. Ask for it by name. [message](https://clayjs.com/docs/plugins/message/)

A form with `message-form="<confirmation text>"`, or with `action="/_/message"`, is POSTed as JSON to `/_/message` with the `Document-URL` header, carrying a one-time token from `GET /_/form-token` (the host's bot check). `clay.message.send(data)` resolves `{ ok, error }` and never rejects. Events on the form: `clay:message-sent`, `clay:message-failed` (cancel to skip the toast).

## wire

A per-file control channel between the page and a process running in the user's terminal (see `htmlclay wire serve <file> -- <cmd>`). Load it with `?plugins=wire,sync`. [wire](https://clayjs.com/docs/plugins/wire/)

The page sends a request, the process answers with progress, and the process edits the file; HTML never travels over the wire. The change reaches the open page as an ordinary external file change, through live sync.

```js
const run = clay.wire.send({ ask: "tighten the intro" });
clay.wire.on((state, frame) => console.log(state.state, frame && frame.text));
const outcome = await run.done;
```

`send` returns its handle immediately; `handle.done` resolves with the final snapshot and never rejects. `on` takes a function, not an event name, and returns its own unsubscribe. A listener's second argument is the inbound frame when one caused the update, which today means `wire/status` and nothing else: `state.text` is sticky across later states, so the frame is the only way to tell a new status line from the same line arriving again.

Also `cancel`, `get`, `list`, `isBusy`. Works in view mode too.

## live

`clay.live(name)` gives you a JSON tag's data as an object you can change like any object. Load it with `?plugins=sync,live`. [live](https://clayjs.com/docs/plugins/live-sync/)

Mark the tag with `clay="live"`; every change becomes a small operation, and `add` makes counters that never lose a point to a player scoring at the same moment.

## page

Opens one `article[data-page]` with one `merge="page" clay="live"` Yjs store in the page editor. Ask for it by name. [page](https://clayjs.com/docs/plugins/page/)

It loads with edit mode (a live entry imports it while the tab still shows view) and requires a host that advertises the `page` extension; without that extension, the saved article stays readable. `clay.page.flush()` waits for its edits to reach a checkpoint. It brings `live`, `sync` and `upload` with it: image and file blocks store their files through `clay.upload`, and on a host that stores no files the editor offers to embed them instead.

## visitor-drafts

A free document: a visitor who is not the owner gets the same editing UI the owner gets, and their work is kept as the page's own JSON in their own browser. Ask for it by name. [visitor-drafts](https://clayjs.com/docs/plugins/people/)

Never a save, a sync or an upload to the host. On every load that JSON is applied over the file you published, so a fix you make still reaches them. The plugin also draws the download prompt, the optional intro area and the owner's chip.

## ai-edit

Select text and ask an agent to rewrite it. Opt in with `?plugins=ai-edit`, which brings `wire` with it. [ai-edit](https://clayjs.com/docs/plugins/ai-edit/)

Select words in any block (a paragraph, heading, list item, table cell...) and press ⌘J (Ctrl+J elsewhere), or click the small **AI** chip at the end of the selection. The panel opens under the selection with the words highlighted. The request carries the block's HTML, the selected text, and the text's character offsets inside the block; a selection across blocks sends the nearest container, and one across top-level blocks sends the whole page. ⌘J with nothing to open is left to the browser, and so is ⌘J in a page's own input, textarea or select. Pages built from `[data-edit-id]` sections also get the hover chip, a click on bare section padding, and the **AI** bubble for the whole page (just left of the CMS's Edit content toggle when there is one).

The box composes and nothing else. Any click on the page away from it closes it, a click inside the block being edited included, and the click still lands where it was aimed. Text typed and not sent is kept as that target's draft and comes back when the box is opened on the same target again, so a click away costs nothing. A sent comment is that target's draft too until its reply is ready, so Error and Stop reopen the box with it. The box's own Stop, Keep and Revert are gone: once a request is sent the whole live state moves to a bar fixed at the bottom centre of the viewport, and the ring stays on the block being edited while the bar is up.

The bar shows one state at a time. **Working** shows the host's own progress line and **Stop**, which cancels the request and leaves the page exactly as it was. **Ready** shows `Edit ready.` with any warning the reply carried and the model that wrote it, and offers **Keep** and **Revert**. **Error** shows the host's message and an X that closes the bar, with the edit already rewound; a cancellation on the host's own side, the AI Editing switch going off or HTML Clay quitting, reports `HTML Clay stopped this edit.` there. After Keep the bar reports `Saving…` and then the host's answer, and closes itself about a second and a half later. Keep saves what the live preview shows, a fix made by hand in it included, and a save that fails or conflicts stays on the bar with its message and its X instead of closing. Revert rewinds the edit and closes the bar. While the reply waits, every save is held, explicit ones included (`clay.save()`, ⌘S, `[persist]`, the wire's pre-send flush): Revert lets a save asked for meanwhile run, and Keep releases the hold and saves the rewrite through `clay.save()`. A reply that adds a script, an inline event handler, a `javascript:` URL, `srcdoc`, or an `iframe`, `frame`, `object` or `embed` the original did not have is refused before it is shown, and the bar says so.

Escape reads the bar first: in Working it stops the request, in Ready it keeps the edit, in Error it closes the bar, and in Saving it does nothing. With no bar and the box open it closes the box and keeps the draft. It stands down when the page has a claim on it too: the event was already handled, a `[data-clay-modal]`, `dialog[open]` or `[aria-modal="true"]` element is open, or the caret is in a field the page owns. On a page with `[data-edit-id]` sections a second click on the whole page bubble closes an empty box and keeps a typed one open. A leading `@token` picks the engine, `@file.ext` adds context, and `@page` saves the page first so the helper reads it from disk.

A page can open the composer itself, prefilled with a suggested request:

```js
await clay.aiEdit.open(element, { prompt: "Add a bar chart above the table. Keep the table." });
```

`open` opens the existing composer and returns true when opened, false when unavailable, busy or the target cannot be edited. It does not send automatically. The prompt seeds an empty per-target draft; previously typed text is preserved. The target must be a connected content element in this document. Controls and transient UI are not valid targets. A listed but disabled helper opens the existing setup explanation with Send unavailable, just like the keyboard shortcut.

Edit mode only, and dormant unless `clay.wire.helpers()` lists an `ai-edit` helper. Listed as `unavailable` (the host's switch is off), ⌘J opens the panel with a note and no Send. HTML Clay and Hyperclay Local both answer it.

## source

Saves the file rather than a fresh printout of the page. On by default; `?exclude=source` turns it off. [source](https://clayjs.com/docs/plugins/source/)

Without it, every save rebuilds the document from the DOM, which reorders attributes, renormalises quoting and reprints every tag: a save that changes nothing rewrites about 88% of a hand-written file's lines. With it, clay keeps the bytes the file was loaded from, pairs each node to its place in them, and reprints only what changed. Edit mode only.

```js
clay.source.stats();   // { installed, paired, saves, reprints, partialReprints, lastReprint, ... }
document.addEventListener("clay:save-reprinted", (e) => console.warn(e.detail.scope, e.detail.reason));

clay.source.text();      // the bytes clay believes are on disk right now
clay.source.locate(el);  // { from, to, line, column } for a live element, or null
```

`text()` and `locate()` are the pair an editing tool needs: read the file as it will be written, point at an element in the live page, and express an edit as a range in those bytes rather than as a DOM mutation. No ids written into the file, and no map that has to survive a reload. Both answer for the file as of the last accepted save or the last live-sync frame from disk, whichever came later: a refresh still waiting runs first.

**The offsets and `column` are UTF-16 code units into `text()`, not bytes.** Slice the string `text()` returns and the answer is exact. Hand the same numbers to something that counts bytes, such as a file seek or most Python file APIs, and it writes to the wrong place, silently, and only on documents with non-ASCII content: one line of `café 🎉 naïve` puts the same position at 51 code units and 55 bytes.

`locate()` answers `null` rather than guessing, and the reasons differ enough to matter: the element was created after load, so it is not in the file yet; or it is an implied `<html>`, `<head>` or `<body>` the author never wrote and that holds nothing; or the plugin never installed, which is the whole document rather than that one element. `stats().installed` and `stats().refused` tell the last case from the first two. An implied tag that does hold content locates to the bytes it occupies, which are its children's, because there are no tag bytes to include.

Every save is reparsed and compared against the page before it is sent. Where it differs, the element it differs in is printed the way the browser would print it and the rest is still copied, widening to the parent while the check still fails. Only a failure that reaches `<html>`, or has no element to point at, sends the ordinary full serialization, so the worst case is the behaviour without the plugin. `clay:save-reprinted` fires for an accepted save that needed either, with `detail.scope` `'partial'` or `'full'` (and `detail.printed`, the element count, on a partial one), and `stats()` counts them as `partialReprints` and `reprints`. A render the host never accepted counts nothing. It costs an HTML parser (about 48 KB compressed) and one request for the file's own bytes at load, both in edit mode only; `exclude=source` is how a page declines to pay that. It does nothing, silently and safely, when that request fails, redirects, is not `text/html`, or returns something whose doctype and document-level comments are not this document's. It also declines two document shapes it cannot describe: one where the parser moves content out of a `<table>` it was written inside, so the source order and the tree order disagree, and one with no content inside `<html>` at all. `clay.source.stats().refused` says which, and saves on such a page are the ordinary full serialization.

## sap

Reactive templates with the DOM itself as the state: attributes and elements are the source of truth, and templates re-render when the DOM they depend on changes. It is a separate library, not a plugin of `clay.js`. [sap](https://clayjs.com/docs/plugins/sap/)

Reactivity without a virtual DOM or a store, made for malleable files, where the page already is the database.

## data

Read the page's content in and out as JSON. Load it with `?plugins=data`, or as the `clay-data.js` satellite. [data](https://clayjs.com/docs/plugins/data/)

`clay.extractData(rules?)` reads structured JSON from the live page, and `clay.applyData(values, {rules})` applies structured JSON to the live page atomically. Core adds `clay.readData()` and `clay.writeData()` for hosts that announce the matching saved-file capabilities.

## sortable

Drag-drop reordering; fires `clay:sorted`. [sortable](https://clayjs.com/docs/plugins/sortable/)

## upgrade

Notices that the template an app was copied from has a new version, and pulls the new design in while pouring the existing data into it. Ask for it by name. [upgrade](https://clayjs.com/docs/plugins/upgrade/)

A copied app stays upgradable while it carries the two meta tags, `hyper-source` and `hyper-version`, and the whole mechanism is client-side: download the new file, move your labeled data in, save.
