- ClayJS
- How ClayJS works
- The model
The model
The two forms of a malleable document, what gets saved, and the loop every app follows.
Saved file and open page#
A malleable document exists in two forms: the saved file and the open page.
- The saved file is the bytes on the host. A visitor downloads these exact bytes. They must be correct and complete with no JavaScript running: the content, its order, the state of every checkbox, and any totals the reader needs.
- The open page is the live DOM in one browser tab. It holds the saved content plus everything the tab adds at runtime: editing toolbars, a filter box, an open dialog, selection, computed badges, and drag ghosts.
A save turns the open page back into the saved file. ClayJS clones the whole document, removes everything marked as runtime-only, applies a few transforms, serializes the full HTML, and posts it to the host (POST /_/save). There is no partial save and no separate data store. The document is the database.
Record, chrome and derived DOM#
Every element on an open page is record, chrome, or derived DOM. Decide which before you write it.
| Kind | What it is | Where it lives | Saved | Sent to peers | Edited by people |
|---|---|---|---|---|---|
| Record | The work: card titles, checklist items, amounts, block text, order | Ordinary elements, attributes, text, and persist control values | yes | yes | yes |
| Chrome | UI for editing: buttons, grips, toolbars, menus, filter boxes, status text | Authored in the file, hidden from viewers with show-when:editmode="true". UI a script fills or changes goes in clay="editor-ui" (Chrome) | hidden: yes, editor-ui: no | hidden: yes, editor-ui: no | no |
| Derived | Values computed from the record: counts, progress, totals, outlines, a status pill’s colour | sapjs paints (attr:, class:, css:, text, show, sap.js: records, views and totals in markup), clay="editor-ui" (tab only), or clay="no-dirty" (saved, remote wins) | paints and no-dirty: yes | no-dirty only; paints are repainted, never merged | no |
Nearly every bug in a malleable file happens when one kind leaks into another: chrome that a script changes gets saved into the file, a derived count gets merged as if someone typed it, or a record is edited only in a copy that never reaches the DOM.
Rules for DOM state#
Follow these rules to keep record, chrome, and derived state separate.
- The DOM is the record. Everything durable is an element, an attribute, text, child order, or the value of a control marked
persist. Never keep the authoritative copy in a JavaScript object. - Chrome is authored in the file and hidden from viewers with
show-when:editmode="true". This default needs no script and is in place before ClayJS loads. Useclay="editor-ui"only for UI a script fills or changes while the page is open, or that viewers must not receive (Chrome). - Derived values are recomputed, never stored as edits. Store the value once as the record (
bind), and declare every other place it shows as a sapjs paint:attr:data-state="item.state",class:done="item.done",text,show(sap.js: records, views and totals in markup). ClayJS loads the sapjs core for any page with a[sap]root and treats painted values as derived. Without sapjs, write derived values intoeditor-uielements, or intoclay="no-dirty"elements when the saved file must show them. Never copy a record into a second attribute with a script. The copy goes stale in the file, and a merge brings the stale copy back. - Every repeated item has a unique
data-idfrom birth. Mint a fresh one on every create and every duplicate. Sync pairs elements by it. - Per-tab state stays out of the record. Keep which card is open, the filter, and the current view in the URL hash,
localStorage, or memory. Paint them throughephemeralattributes (Ephemeral state: per-tab state on saved elements), a runtime<style>, oreditor-uielements. - A command writes the record once, in one place, wrapped in an undo commit. The mutation is what autosave, undo, and sync see.
- Adopt the file as it is on load. Never rewrite the record at boot. Doing so makes the page dirty, can fall outside the save baseline, and races peers.
- Reconcile after changes you did not make. A peer’s frame, an agent’s disk edit, and an undo replay change the DOM under you. Rebuild derived and chrome state on
clay:sync-appliedand on undo and redo.
What the host does#
ClayJS runs in the page, while a host serves the file and accepts saves. The client never decides who may write: it sends the save, and the host accepts or refuses it. Hosts that run ClayJS pages today:
- HTML Clay, the desktop app that makes
.htmlclayfiles open and save like documents. Single user, local disk. - Hyperclay Local, the desktop sync client. Local folder, device sync to hyperclay.com.
- hyperclay.com, hosted files with accounts, teams, share links, presence, and version history.
- Any server that implements
POST /_/save(and optionally/_/meta,/_/sync,/_/upload). A plain static server or a file opened from disk can view the file but cannot save it.
Hosts, assets and privacy covers what each host adds.