# What's new — the Statum client is now four modules The single-file client (`js/statum.js`, served as `/_statum/resource/statum.js`) is gone. The browser client now ships as four ES5, dependency-free scripts with a strict load order: ```html ``` **Load order is a contract.** `statum-core.js` must be first: it owns the slot protocol and installs the seams (`statum.registerAttribute`, the render driver, the internal bridge) that every later module registers into. The canonical order is core → document → markdown → forms. You may cherry-pick trailing modules (core first, always): core + document gives you the full state-binding walk (`stm-text`, `stm-html`, `stm-if`, `stm-loop`, `stm-action`, …) with no markdown engine and no form extras. Note that `stm-markdown` *and* `stm-h-demotion` live in the markdown module, so a core+document page sees those attributes do nothing — remove them, or load the markdown module. Legacy pages that still say `` keep working: `statum-mkpstm` expands that reference at build time into the four tags above (one build warning per page names the expansion). A page mixing the legacy reference with explicit module tags is deduplicated — each module appears exactly once, in canonical order — and the warning says so. The single-file artifact itself no longer exists; in new pages, just write the four tags. Depth on all of this: `model.md` (wire protocol, directives, every attribute) and `getting-started.md` (tutorial + full JS API). ## stm-prefill — state into controls, without clobbering edits `stm-prefill` writes a state expression into form controls as **properties**, not attributes, every render cycle: ```html ``` The control matrix: - **Text-like inputs and `textarea`** — writes `.value` (`null`/`undefined` become `''`; `undefined` leaves the element untouched). - **Select** — single: writes `.value`; `multiple`: sets each option's `.selected` from an array of string values. - **Checkbox** — writes `.checked`, coerced truthy/falsy. - **Radio** — per-element: give every member of the group the same expression and the one whose `value` matches is checked. ```html ``` - **File inputs are skipped**: assigning `.value` from a script is forbidden by the platform, so there is nothing sensible to write. **Dirty tracking is the point.** The moment a user types in or changes a control (capture-phase `input`/`change` listeners), it is *dirty* and stm-prefill never overwrites it again — the edit outranks the state snapshot. A form `reset` (a reset button, `statum.forms.reset`, or the server's `form-reset` directive — all funnel through the same `reset` event) un-dirties the form's controls so state can re-apply. Because dirtiness is per-element (a `WeakSet`), a form inside a keyed `stm-loop` instance keeps its dirty state across re-renders: the instance owns its nodes. ## stm-clear-on-success and statum.forms A form whose own `stm-action` succeeds can clear itself: ```html
…
``` The clear waits for the action's promise and resets the form only when it resolved (a failed action leaves the fields as they are). The reset also un-dirties the controls, so the next render cycle re-applies `stm-prefill` against the fresh state — and `stm-clear-on-success` is scoped to exactly this form; no other form on the page is touched. The `statum.forms` API: ```js statum.forms.reset(formOrElement); // reset the form owning the element statum.forms.markDirty(el); // force an element dirty (prefill skips it) statum.forms.isDirty(el); // query dirtiness ``` ## statum.markdown — the renderer as an API The CommonMark engine (unchanged, just moved) is now callable directly, on the same code path the `stm-markdown` binding uses: ```js statum.markdown.render(src); // → safe HTML string statum.markdown.renderTo(el, src); // replaces el's content ``` Escape-first posture unchanged: raw source HTML is neutralised and `javascript:`/`data:` URLs never survive as hrefs. ## statum.registerAttribute — write your own bindings Module authors extend the render walk without touching core: ```js statum.registerAttribute('stm-foo', function (el, scope) { // called once per element per render cycle when el carries stm-foo }, { after: 'stm-html' }); // or before: 'stm-class.' ``` A trailing dot makes the name a **prefix matcher** (`'stm-class.'` matches `stm-class.hidden`); the handler then iterates the element's attributes itself. `after`/`before` name another registered attribute and insert relative to it — order matters (`stm-prefill` runs before `stm-class.*`; markdown renders before `stm-h-demotion` demotes). A constraint naming an unregistered attribute warns and keeps registration order; handlers get no per-handler try/catch — a throwing handler aborts that render cycle, exactly like the built-ins. ## The reenter directive A response can ask the client to re-fetch the page's entrypoint for the current URL and apply its directives in place — no page reload: ```json { "type": "reenter" } ``` Use it after an action that invalidates page-level state. `reenter` runs after `form-reset`/`form-submit` and before `navigate`/`post` (navigation still wins if both appear), and the refresh is awaited — the originating action's promise (and so `stm-clear-on-success` and `statum.act`) resolves only after the re-entered state has been applied. A failed refresh is logged and swallowed.