# Getting Started with Statum
## What is Statum
Statum is a stateful web application framework for Vala, built on top of
[Astralis](https://github.com/search/astralis) (HTTP server),
[Invercargill](https://github.com/search/invercargill) (data structures / JSON),
and [Inversion](https://github.com/search/inversion) (dependency injection).
It provides:
- **Server-rendered HTML pages** composed from templates and fragments at build
time (`statum-mkpstm`), precompressed with gzip/zstd/brotli and served with
encoding negotiation and ETags.
- **Client-carried state** — signed and (for private data) encrypted snapshots
that the browser holds and sends back, so the server is stateless between
requests (no session store, no database for ephemeral state).
- **Entrypoint hydration** — after a page loads, a lightweight JS client fetches
directives that set the initial slot data and bind the DOM.
- **GUID actions** — POST/GET handlers auto-mapped to `/_statum/action/{guid}`,
invoked from the client via `stm-action` attributes, carrying strongly-typed
private data.
- **Realtime updates** — a SharedWorker-backed SSE channel pushes slot changes
to subscribed tabs.
### Architecture at a glance
```
Browser Server
─────── ──────
GET / ──────────────────────► StatumPage (precompressed HTML)
page loads, statum.js boots
GET /_statum/entrypoint ────► StatumEntrypoint → directives (set/clear/...)
client applies directives
client binds stm-* attributes
POST /_statum/action/{guid} ► StatumAction → directives
(with X-Statum-Slot headers) (mutate state, push to channels)
```
---
## Quick Start Examples
### 1. A page with an entrypoint
**`home.html`** — the page template, composed with a layout:
```html
/main
Counter: 0
```
**`main.html`** — the layout template:
```html
mainMy App
My App
```
**`HomeEntrypoint.vala`** — hydrates the page:
```vala
public class CounterPublic : Object {
public int value { get; set; }
public Model.ActionDto bump { get; set; }
}
public class HomeEntrypoint : StatumEntrypoint {
public override async DirectiveBuilder handle() throws GLib.Error {
var counter = new CounterPublic();
counter.value = 0;
counter.bump = action_registry.author_private(new BumpPrivate());
return directives().set_typed("counter", Scope.PAGE, counter);
}
}
```
**`Main.vala`** — wires it all up:
```vala
void main(string[] args) {
var app = new WebApplication(8080);
app.add_module();
var statum = app.configure_with();
statum.add_page(); // route from
statum.action(); // GUID auto-assigned
app.run();
}
```
### 2. A guarded page
```html
/dashboardmain
```
The guard redirects unauthenticated visitors to `/` before the entrypoint loads.
---
## Server-side Security
### Authorisation guards on `/_statum/*` endpoints (SA-6)
The framework endpoints — `GET /_statum/entrypoint`, `GET /_statum/channel`
and `PATCH /_statum/channel/{id}` — are open by default: the framework cannot
know which slot data is public. Applications gate them through the
configurator:
```vala
var statum = application.configure_with();
// Return null to allow, or an HttpResult (401 / login redirect) to deny.
statum.guard_entrypoint(ctx => auth.require_login(ctx));
statum.guard_channel(ctx => auth.require_login(ctx));
statum.guard_channel_subscriptions(ctx => auth.require_login(ctx));
```
Spry applications should install Spry guards here (see the Spry README's
session-guard helpers).
### Channel resource bounds (SA-15)
Concurrent channel (SSE) connections are capped globally
(`ChannelEndpoint.MAX_TOTAL_CONNECTIONS`, 256) and per remote address
(`ChannelEndpoint.MAX_CONNECTIONS_PER_IP`, 16); each channel holds at most
`MAX_SUBSCRIPTIONS_PER_CHANNEL` (64) subscriptions. Beyond the caps the
channel endpoint answers `503`.
### Frame expiry on restore (SA-4)
`StateService.restore_slot` rejects (and invalidates) frames past their
snapshot's `invalid_after`, and keeps the snapshot's `as_at` as the slot's
`last_touched` — so replayed frames cannot pin slots forever and the cleanup
sweep still expires restored slots. `verify_frame` also asserts the frame's
`signer` matches the configured signing key (single-signer contract, SA-10).
### Key generation (SA-12)
`statum-genkeys` never prints secret key halves to stdout. Use:
```bash
statum-genkeys --out web-config.json
```
which writes the full config at mode 0600 and prints only the public halves.
---
## HTML Templates
Statum pages are HTML files processed at build time by `statum-mkpstm`.
### Build-time directives (`pstm-*`)
All `pstm-*` elements **must appear inside ``** (except `pstm-content` and
`pstm-fragment`, which live in ``).
| Directive | Where | Purpose |
|---|---|---|
| `/path` | head | The page's route. **Required.** Supports templates: `/cats/{species}`. |
| `main` | head | Wraps this page in the named template (by ``). |
| `main` | head | Names this file so pages/fragments can reference it. |
| `…` | head | Page title, merged up the template chain. |
| `` | body (template) | The outlet where the child page's body is inserted. |
| `` | body | Inlines a fragment (component) by name. |
| `pstm-materialise-as="div"` | `` attr | When this body is composited into a template or fragment, it is wrapped in the named element. |
| `pstm-res-src="logo.png"` | any attr | Rewrites to `src="/_statum/resource/logo.png"`. Also `pstm-res-href`, etc. |
### Head merging
When a page is composed into a template (or a fragment is inlined), the child's
`` children are **merged** into the parent's ``. This lets pages,
templates, and fragments each declare their own `