Global slots let the server broadcast shared state (e.g. a live score) to all
connected clients. They use a deterministic key ("global:" + type_name)
so every client references the same slot. The existing slot machinery (signing,
SSE push, stale-as_at auto-refresh) handles everything else — no new transport,
no new channel protocol.
Global slots are explicit opt-in: each entrypoint/action that needs a global
calls .set_global(type) (current value) and/or .subscribe_global(type) (live
SSE updates). Pages that don't opt in never receive the global.
A global slot's key is "global:" + type_name (e.g. "global:score"). This is
set as the slot's id at creation, so all existing methods (get_slot,
sign_slot, update, clear_slot) work with it unchanged.
statum.global<ScorePublic>("score", new ScorePublic { home = 0, away = 0 });
Creates the slot with the deterministic key and the initial model (mapped via
GObjectMapping). Scope is PAGE (the server always has the authoritative
copy; the client doesn't need to persist it).
// Entrypoint: include the current value + subscribe for live updates
return directives()
.set_global("score")
.subscribe_global("score")
.set_typed<CounterPublic>("counter", Scope.PAGE, counter);
// Action: update via update_held (client holds it from the entrypoint)
return directives().update_held<ScorePublic>("score", s => s.home += 1);
update_held resolves the held slot's key (the deterministic "global:score"),
mutates, signs, and pushes to all subscribed channels. The requesting client
gets a set in the response; other clients get the SSE push. Clients that miss
the push refresh on their next request via the stale-as_at auto-set.
// From a timer, webhook, or any non-request context
yield state_service.update_global_typed<ScorePublic>("score", s => s.home += 1);
Pushes via SSE to all subscribers. Stale clients refresh on next request.
| Mechanism | How it works for globals |
|---|---|
| Signing | sign_slot("global:score") — same as any slot |
| SSE push | update("global:score", state) → push_set to all channels subscribed to that key |
Stale as_at auto-set |
HeldSlotResolver compares the client's as_at with last_touched; stale → auto set appended |
| Subscribe/unsubscribe | subscribe directive with key "global:score"; cleanup on page close as normal |
StateService global slot support — src/StateService.valanew_global_slot(string type_name, Scope scope, State state) — creates a slot
with id = "global:" + type_name, caches it. If the key already exists (e.g.
on restart), updates the current state.update_global(string type_name, State state) async — delegates to existing
update("global:" + type_name, state) (sign + push).update_global_typed<TPublic>(string type_name, owned UpdateMutator<TPublic> mutator)
async — typed read-mutate-write (maps via GObjectMapping, preserves private).clear_global(string type_name) async — delegates to clear_slot.src/DirectiveBuilder.vala.set_global(string type_name) — derives "global:" + type_name, calls
state_service.sign_slot(key), emits set. Signs synchronously at call time
(sign_slot is sync)..subscribe_global(string type_name) — emits subscribe with the derived key.src/Statum.vala
global<TPublic>(string type_name, TPublic initial) — injects StateService,
maps the initial model via GObjectMapping.to_properties, calls
state_service.new_global_slot(type_name, Scope.PAGE, state).example/set_global + subscribe_global in the home
entrypoint, updated by a background timer.stm-text="announcement.text".model.md: add a "Global slots" section describing the deterministic-key
model, explicit opt-in via set_global / subscribe_global, background
updates via update_global_typed, and the SSE/stale-refresh fan-out.getting-started.md: add set_global / subscribe_global /
update_global_typed to the DirectiveBuilder and StateService tables; add a
"Global state" quick-start example.set_global + subscribe_global) returns the
global's set + subscribe.update_held<T>("global_type", ...) in an action pushes the update
(the requesting client gets set; a second client's stale as_at gets an
auto-set on its next request).