WHATS_NEW.md 6.0 KB

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:

<script src="/_statum/resource/statum-core.js"></script>
<script src="/_statum/resource/statum-document.js"></script>
<script src="/_statum/resource/statum-markdown.js"></script>
<script src="/_statum/resource/statum-forms.js"></script>

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 <script pstm-res-src="statum.js"></script> 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:

<input type="text" name="name" stm-prefill="profile.name">
<select name="theme" stm-prefill="profile.theme">…</select>
<input type="checkbox" name="news" stm-prefill="profile.news">

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.

    <input type="radio" name="cadence" value="daily" stm-prefill="profile.cadence">
    <input type="radio" name="cadence" value="weekly" stm-prefill="profile.cadence">
    
  • 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:

<form stm-action="profile.saveAction" stm-clear-on-success>…</form>

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:

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:

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:

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:

{ "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.