Ver código fonte

Flesh it out

Billy Barrow 3 semanas atrás
pai
commit
da4ae9d501
1 arquivos alterados com 61 adições e 20 exclusões
  1. 61 20
      model.md

+ 61 - 20
model.md

@@ -13,7 +13,7 @@ A statum frame consists of a JSON object with three keys:
 - `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. persits on between navigation)
+    - `"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.
@@ -25,9 +25,9 @@ A statum snapshot captures an aspect of the application state at a particular po
 - `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 IOS 8601 string representing the time this snapshot should be considered invalid and not used.
+- `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-defines key/values.
+- `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
 
@@ -39,9 +39,10 @@ A statum snapshot captures an aspect of the application state at a particular po
 
 - `type`: One of
     - `"navigate"`: Used to redirect the frontend to a new URI.
-    - `"post"`: Used to cause the frontend to POST some data to a 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.
 
@@ -54,20 +55,32 @@ Depending on the type, it will have other properties.
 - `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
 
-- `slot_id`: The ID of the slot to clear.
+- `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 diretives based on the URL. has query param `uri` set to the browsers current `window.location`.
+`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
@@ -76,34 +89,45 @@ Returns a JSON array of Statum Directives.
 
 ## Action Endpoints
 
-The URLs of action endpoints are entirely defined by the action iteself (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.
+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 have a `X-Statum-Slot` header for each relevant slot snapshot in scope.
+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 that have a snapshot that are within the `transmit_after` time window, an `as_at` timestamp is sent, e.g. `X-Statum-Slot: key=d3cceb88-c211-41ee-a0b1-6a93f3b6331b; as_at=2026-07-06T07:33:02.332Z`.
-- For slots that have a snapshot which has a `transmit_after` timestamp equal to its `as_at` timestamp, the frame is sent in base64, e.g. `X-Statum-Slot: eyAiY29udGVudCI6ICIuLi4uIiwgInNpZ25lciI6ICJzMSIsICJzaWduYXR1cmUiOiAiZXlBaVkyOXVkR1Z1ZENJNklDSXVMaTR1SWl3Z0luTnBaMjVsY2lJNklDSnpNU0lzSUNKemFXZHVZWFIxY21VaU9pQWlJbjA9In0=`
+- 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=`
 
-Before an HTTP request is sent, if one or more slot snapshots's `transmit_after` timestamp is in the past, and it is not equal to its `as_at` timestamp, a pre-flight request should be made to the slot post endpoint, which will in turn provide updated snapshots and a new `as_at` timestamp.
+### 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 snapshot object currently associated with the type "typeName".
+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 current frame of a type, use `statum.getFrame("typeName")` which will return the frame object that encapsulates the snapshot.
+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, thier values being the current snapshot object, e.g. `{ "typeName":  {"as_at": "...`.
+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
 
@@ -121,16 +145,31 @@ Callbacks can be attached to statum using `statum.addStateListener("typeName", c
 
 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-`. Ususally their values refer to something along the JSON object path of the current `statum.state()`. The template is re-evaluated each time the state changes.
+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.
 
-The following attributes are supported:
+### 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 `<body>` 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. `<span stm-text="typeName.label">`.
@@ -153,17 +192,17 @@ The attribute `stm-class.x` where `x` is any class name can be used to toggle cl
 
 ## Actions
 
-The attribute `stm-action` binds the elements primary action (e.g. `onclick` for buttons, or `onsubmit` for forms), overriding their default behaviour to instead perform the statum action referenced (via state path). For example `<button stm-action="typeName.deleteItem">Delete</button>`.
+The attribute `stm-action` binds the element's primary action (`click` for buttons and links, `submit` for forms), overriding the default behaviour to instead perform the Statum action referenced by the given path into state. For example `<button stm-action="typeName.deleteItem">Delete</button>`.
 
-It can be used with `stm-data` to also provide additional data to the action, e.g. `<button stm-action="typeName.deleteItem" stm-data="{'confirm': true}">Delete</button>`.
+Additional data can be attached to the action using one or more `stm-data-{key}` attributes, where `{key}` is the data key and the attribute value is its value, e.g. `<button stm-action="typeName.deleteItem" stm-data-confirm="true">Delete</button>`. These values are merged with any form input values when the request is sent.
 
-The attribute will also disable the element (and any children) while the action is being processed, and restore the original state once completed (or on error). In addition to this, the attribute `stm-busy-class` can be used to specify one or more css classes to apply during this time.
+The attribute will also disable the element (and any children) while the action is being processed, and restore the original state once completed (or on error). In addition to this, the attribute `stm-busy-class` can be used to specify one or more css classes to apply during this time, and `stm-on-error` can specify one or more css classes to apply while the element is in an error state (cleared the next time the action is attempted). The `statum.act` promise for the action also rejects on failure.
 
 When used on a form, any form inputs are sent in the JSON or query string of the action, bound by their `name`, as they would for a regular native form submission.
 
 ## Loops
 
-the attribute `stm-for-x-in=` attribute (where `x` can be anything) repeats the *current* element multuple times for each item in the referenced array, and makes available the specified variable name in all `stm-` attributes in its children, and for all other `stm-` attributes on itself. For example:
+The `stm-for-{x}-in` attribute (where `{x}` is the loop variable name) repeats the *current* element once for each item in the referenced array, and binds the loop variable for use in all `stm-` attributes on its descendants, and in all other `stm-` attributes on the element itself. For example:
 
 ```html
 <div class="list">
@@ -175,3 +214,5 @@ the attribute `stm-for-x-in=` attribute (where `x` can be anything) repeats the
     </div>
 </div>
 ```
+
+When the referenced array changes, the loop is re-rendered. If a `stm-key="{expression}"` attribute is present, items are matched across renders by the key value, preserving DOM identity (and therefore focus, selection, and input state) for items whose key is unchanged. When no `stm-key` is given, the loop falls back to a full re-render.