# Regions

> The clay attribute: how any element takes part in saving, and how your own code can respect a region.

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

One attribute controls how any element takes part in saving. Keywords combine with spaces: `clay="freeze no-undo"`.

## Region reference

| Keyword | Plain meaning | Typical use |
| --- | --- | --- |
| `no-save` | Lives on the page, never written to disk. Live sync leaves your copy alone. Its content is still inside the snapshot sent to peers (they never apply it), so prefer `editor-ui` for UI. | Search boxes, connection badges, edit toolbars |
| `no-snapshot` | Invisible to save and to sync. Every device keeps its own copy. | Per-device panel state |
| `freeze` | A scratchpad: the element saves, syncs and merges like any element; its contents are per-tab, saved as they were when it first appeared and never sent to peers, and a frozen element new to this tab arrives empty. Not for values computed from the records: those are ordinary content. | Clocks, status text, per-tab labels such as "overdue" |
| `no-trigger-autosave` | Saved normally and still counted as unsaved work, but edits here don't start an autosave (any save still writes them). Live sync merges it like ordinary content: an overlap with an incoming frame goes to the conflict ledger. | Heavy editors you save by hand |
| `no-dirty` | Saved normally, but its content is disposable: no autosave, no unsaved-changes warning, and live sync may replace it. | Regions that render themselves: filter bars, projections, drag previews |
| `no-watch` | The mutation system ignores it entirely (implies no autosave trigger, no undo). | Third-party widgets that churn the DOM |
| `no-undo` | Changes here aren't recorded in undo history. | Live regions where undo would feel wrong |
| `no-data` | Excluded from content-aware queries and serialization, but otherwise saved, watched, snapshotted, and undoable normally. Kept in the file and synced, but skipped by `clay.extractData`, the data API and `clay.dom.createContentView`. | Saved metadata or controls that are not part of an API's content view |
| `editor-ui` | A live editing-control bundle: no-data, no-save, no-snapshot, no-watch, and no-undo — shorthand for `no-data no-save no-snapshot no-watch no-undo`: never saved, never sent to peers, never in undo, never seen by autosave. Create it at runtime (an authored `editor-ui` element is gone from the file after the first save). | UI a script fills or changes: counts, status, drawn charts. Static controls (buttons, grips, menus) stay in the file with `show-when:editmode="true"` |
| `live` | On a `merge=` JSON script: the live plugin holds its value in memory and writes it into each save. Saved, but never marks the page unsaved, never undone, and live sync leaves the tag to the plugin. | Game state and other fast-changing data read with `clay.live(name)` |

## Recipes

- Edit-only controls (buttons, grips, toolbars) → `show-when:editmode="true"`
- UI a script fills or changes (counts, status, drawn views) → `clay="editor-ui"`
- Per-tab generated text (clocks, status, "overdue") → `clay="freeze"`
- Third-party embeds → `clay="no-watch"`
- Heavy editors, batch the saves → `clay="no-trigger-autosave"`
- Self-rendering UI (filter bars) → `clay="no-dirty"`

## `no-trigger-autosave` or `no-dirty`?

Ask whether a change in that region is work you would be upset to lose. If it is, use `no-trigger-autosave`: it waits for your save, warns you on close, and an incoming sync frame will not overwrite it. If the region renders itself from other content and its changes mean nothing on their own, use `no-dirty`. Marking real work `no-dirty` means a sync frame may replace it and nothing will warn you, so reach for it only when the content is genuinely derived. A region that mixes both wants two elements, one of each.

Known issue in 1.8.2: when a frame replaces a `no-dirty` region this tab had also changed, while the tab holds an unsaved edit elsewhere, the replacement is recorded in `clay.conflicts` (`detail: "remote-wins"`) and raises the conflict notice. Until it is fixed, acknowledge those records on `clay:sync-applied` with `{reason: "reconciled"}`.

## Respecting regions from your own code

Writing a custom attribute that touches the save? Ask clayjs what a region means instead of matching `[no-save]` yourself. That's what `clay.internals.region` is for: it's the same policy the library uses, so your attribute can't stop agreeing with it two releases from now. It needs one extra script tag, the [internals satellite](https://clayjs.com/docs/internals/).

```html
<script src="https://clayjs.com/v1/clay-internals.js"></script>
```

A `count-of` attribute that writes how many elements match a selector into a badge, and leaves alone the regions that aren't being persisted as they stand:

```js
await clay.loaded.internals;
const { resolveRegionPolicy, addRegionToken, PERSIST } = clay.internals.region;

clay.internals.addDocumentTransform(clone => {
  for (const el of clone.querySelectorAll("[count-of]")) {
    // Inside no-save or freeze? This element isn't going to disk as it stands,
    // so counting into it is either wasted or wrong.
    if (resolveRegionPolicy(el).persist !== PERSIST.FULL) continue;
    el.textContent = clone.querySelectorAll(el.getAttribute("count-of")).length;
  }
});
```

**Keep the callback pure and repeatable.** It runs on the save clone, and it runs again every time clayjs checks whether the page has changed. Write something new on each run, a timestamp being the tempting one, and every check differs from the last, so the page never reads clean and autosave never stops.

When your attribute generates chrome of its own, mark it rather than hoping nobody saves it. `addRegionToken` merges into whatever `clay` attribute is already on the element:

```js
addRegionToken(toolbar, "no-save");   // clay="existing-token no-save"
```

The rest of the surface (`isInert`, `isSnapshotRemoved`, `REGION_ATTRS`, and the raw `selectors` clayjs strips with) is in the [reference](https://clayjs.com/docs/internals/).
