# Statum Model ## Statum Frames A statum frame consists of a JSON object with three keys: - `content`: a string containing the literal content of the frame. - `signer`: a string containing a signer ID. - `signature`: a base64 encoded signature verifying the content authored by the signer. ## Statum Slot - `key`: a server issued ID for the slot. - `scope`: a string value that can be one of: - `"device"`: persists between browser sessions. - `"session"`: persists within the browser session (e.g. between tabs). - `"flow"`: persists only within the current tab, while the tab remains on the site (e.g. persists between navigations). - `"page"`: persists only while the current page is loaded (e.g. clears on navigate). - `"transient"`: does not persist, will never get sent back to the server - random or `null` key each time. - `type`: an application defined type name. ## Statum Snapshot A statum snapshot captures an aspect of the application state at a particular point-in-time. It is usually wrapped in a frame, which must be retained if it is to be sent back to the server after the `transmit_after` time. It contains: - `as_at`: an ISO 8601 string representing the time the snapshot was authored. - `slot`: a slot object (above) - `transmit_after`: an ISO 8601 string representing the time the slot will no longer be cached by the server and the application will need to send the snapshot. `undefined` if this is a unidirectional snapshot that never needs to be sent to the server. If it equals `as_at`, then the snapshot must always be sent. - `invalid_after`: an ISO 8601 string representing the time this snapshot should be considered invalid and not used. - `private`: a base64 encoded string of data only readable by the application server. - `public`: a JSON object with application-defined key/values. This is the user-facing data that the JS and HTML APIs bind to. It may also contain embedded [Statum Actions](#statum-action), referenced by path (e.g. `typeName.deleteItem`). ## Statum Action - `uri`: The URI to request. - `method`: The HTTP verb to use. - `private`: A base64 encoded string of data only readable by the application server (can be `undefined`) ## Statum Directive - `type`: One of - `"navigate"`: Used to redirect the frontend to a new URI. - `"post"`: Used to perform a full-page form POST navigation to a URI. A `"post"` directive is always executed after all other directives in the same response. - `"set"`: Used to set a slot to a new snapshot. - `"clear"`: Used to clear a slot. - `"error"`: Used to report an error to the frontend. Depending on the type, it will have other properties. ### Navigate Directive - `uri`: The URI to navigate to. ### Post Directive - `uri`: The URI to perform a form post to. - `data`: Key/value pairs for form data. The post directive performs a full-page browser navigation via a real form POST, not an AJAX request, so its response is HTML and is not parsed for directives. Because it navigates away from the page, a post directive is always executed after every other directive in the same response has been applied. ### Set Directive - `snapshot_frame`: A frame containing a snapshot. ### Clear Directive - `key`: The key of the slot to clear. ### Error Directive - `message`: A human-readable error message (optional). - `code`: An application-defined error code (optional). - `data`: Additional key/value error details (optional). An error directive signals a failure to the frontend. When one is received, the `statum.act` promise for the originating action rejects and any `stm-on-error` handling on the originating element runs. An error directive returned from the entrypoint load is surfaced as a load failure. # Statum HTTP API All endpoints that return directives do so as a JSON array of Statum Directives, served with the Content-Type `application/vnd.statum+json`. The frontend uses this content type to distinguish a Statum directive response from an ordinary HTML or JSON response. ## Entrypoint Endpoint `GET /_statum/entrypoint`: Get initial directives based on the URL. Has query param `uri` set to the browser's current `window.location`. Returns a JSON array of Statum Directives. ## Slot Post Endpoint `POST /_statum/slots`: Send a saved state snapshots frame to the server once it has reached its `transmit_after` time window. Sent as a JSON array of Statum Frames. Returns a JSON array of Statum Directives. ## Action Endpoints The URLs of action endpoints are entirely defined by the action itself (in the `uri` property). An additional `X-Statum-Private` header should contain the `private` data of the action if present. Returns a JSON array of Statum Directives. ## Request Headers All HTTP API requests must carry an `X-Statum-Slot` header for each slot the client currently holds (i.e. every snapshot in scope for the current page/flow). Note that sending base64 frames in headers can approach practical header-size limits on some servers and proxies; very large frames are better refreshed via the slot post endpoint flow below. There are two variations of this header: - For slots whose snapshot is still within the `transmit_after` window, an `as_at` timestamp reference is sent, e.g. `X-Statum-Slot: key=d3cceb88-c211-41ee-a0b1-6a93f3b6331b; as_at=2026-07-06T07:33:02.332Z`. - For slots whose `transmit_after` equals its `as_at` timestamp (always-send), the frame is sent in base64, e.g. `X-Statum-Slot: eyAiY29udGVudCI6ICIuLi4uIiwgInNpZ25lciI6ICJzMSIsICJzaWduYXR1cmUiOiAiZXlBaVkyOXVkR1Z1ZENJNklDSXVMaTR1SWl3Z0luTnBaMjVsY2lJNklDSnpNU0lzSUNKemFXZHVZWFIxY21VaU9pQWlJbjA9In0=` ### Snapshot freshness decision For each held slot, the client compares the current time against the snapshot's timestamps: - `now < transmit_after` (and `transmit_after > as_at`): send the `as_at` reference header. - `transmit_after == as_at` (always-send): send the base64 frame header. - `transmit_after < now < invalid_after`: the server's cache for this `as_at` has expired. Before sending the request, issue a pre-flight request to the slot post endpoint; it returns `Set` directives (refreshed snapshots with new `as_at` values), after which the request proceeds with reference headers. - `now >= invalid_after`: the snapshot is no longer usable. Drop it locally, do not send a header for it, and refetch the slot via the entrypoint or an action response. Thus a pre-flight is made only when one or more held snapshots have passed their `transmit_after` time but have not yet reached their `invalid_after` time. # Javascript API The Javascript API provides access to the state and actions via the global `statum` object. It automatically handles pre-flight requests, headers, and executing directives from the server. Note that the Javascript library will also automatically make a request to the Statum Entrypoint URL on page load to get the initial states in order to fill out the page template. State is exposed to the application keyed by **slot type**. Statum assumes at most one slot per type, so each type name maps to a single current snapshot; the slot's `key` is used only on the wire (in headers and directives). ## Managing State State snapshots can be cleaned up via `statum.clear("typeName")`. All snapshots can be cleared via `statum.clear()`. ## Reading State State can be read via `statum.read("typeName")` which returns the `public` data object currently associated with the type "typeName". This is the same value the HTML attributes bind to. To get the full snapshot object (including `as_at`, `slot`, `transmit_after`, etc.), use `statum.getSnapshot("typeName")`. To get the frame that wraps a snapshot, use `statum.getFrame("typeName")`. A full state object can be generated via `statum.state()` which returns a javascript object containing properties named after each type name that currently has a state, their values being that type's `public` data, e.g. `{ "typeName": { "label": "..." } }`. ## Performing an Action To perform an action, use `statum.act(action)` where `action` is a Statum action. Use `statum.act(action, data)` to send parameters along with the request. This is encoded as query params when `action.method` == `GET`, or encoded as form encoded otherwise. `statum.act` returns a promise which resolves when the request has completed and all directives have been run. The act function can also be used with a string referring to an action name ## Reacting to State Callbacks can be attached to statum using `statum.addStateListener("typeName", callback)` which calls `callback` with an object containing: - `type`: the type name - `frame`: the frame (can be `null` if state has been cleared) - `snapshot`: the snapshot object (can be `null` if state has been cleared). It is fired whenever a state of the specified type is set or cleared. A listener can be removed with `statum.removeStateListener("typeName", callback)`. For `"session"` and `"device"` scoped slots, state changes are propagated to other tabs in the same origin via a `BroadcastChannel`, so listeners fire in every tab, not just the one that performed the change. # HTML API The Javascript library also has some functionality inspired by HTMX using attributes prefixed with `stm-`. Usually their values refer to a value reached by following a path into a slot's `public` data, optionally combined with a loop variable (see [Loops](#loops)). The template is re-evaluated each time the state changes. ### Expressions Attribute values used as predicates (`stm-if`, `stm-else-if`, `stm-class.x`) are evaluated as JavaScript expressions against a scope built from the current state. Because the page's HTML is trusted, expressions are evaluated directly rather than via a restricted parser. In this scope each type name is bound to that slot's current `public` object, plus any in-scope loop variables. For example, `stm-if="typeName.level > 5"` evaluates `typeName.level > 5` with `typeName` bound to that type's `public` data. ## URL Overrides The attribute `stm-entrypoint` can be used *only* on the `
` tag, to override the default entrypoint url of `/_statum/entrypoint`. Same goes for the `stm-slots` attribute, which can be used to override the default slot post url of `/_statum/slots`. ## Boot and Loading States The library fetches the entrypoint on load before the template is fully usable. Two attributes control element visibility around this: - `stm-preloader`: the element is shown until the entrypoint response has been processed, then hidden. - `stm-content`: the element is hidden until the entrypoint response has been processed, then shown. These allow a page to display a loading state while state is fetched, then reveal the bound content once it is available. ## Text binding The attribute `stm-text` binds the element's `innerText` to the specified state value, e.g. ``. ## HTML binding The attribute `stm-html` binds the element's `innerHTML` to the specified state value. ## Conditional display The attribute `stm-if` adds or removes the element from the DOM depending on the expression inside the statement, e.g. `