The pure runtime: step, run-effects, dispatch.
Hosts embedding stube do not call into this namespace directly; the
documented embedder surface lives in dev.zeko.stube.embed. This
file is the value-language only: every function takes a conversation
value (plus the event for dispatch) and returns the next conversation
value plus the fragments to emit.
Everything in this namespace operates on plain values. A handler
returns [self' effects]; run-effects folds those effects into the
conversation, producing the next conversation value and the list of
fragments that must be pushed to the browser. A fragment is just a
small map describing one Datastar event — see [[fragment-shape]] for
the schema.
No I/O, no atoms, no SSE — that all lives one layer up in
dev.zeko.stube.runtime / dev.zeko.stube.http / dev.zeko.stube.server.
This means the entire interaction loop is testable from a REPL with
dispatch and a hand-built conversation value, with no server running.
[:call <embed> :resume <k>] push a child frame; on `:answer` the
parent's `:k` function is invoked
[:call-in-slot <slot> <embed> :resume <k>]
temporarily swap one embedded slot;
the child answers back to the parent
without taking over the page
[:set-keyed-children <slot> <[[key embed]…]>]
reconcile an ordered, key-addressed
set of children; emits per-child
append/remove/outer fragments
instead of re-rendering the parent
[:answer <value>] pop this frame; deliver `value` to
the parent under its resume key
[:replace <embed>] pop this frame and push another in
its place (Seaside `become:`)
[:patch <hiccup>] extra DOM patch (no stack change)
[:patch-signals <map>] push a Datastar signal patch
[:execute-script <js>] run literal JS in the browser
[:history :replace|:push url] sync browser URL (replaceState/pushState)
[:io <fn>] ask the bound runtime to run `(fn)`
off-thread; pure folds leave it inert
[:after ms event] schedule a future event for this instance
[:subscribe topic event] subscribe this instance to published messages
[:unsubscribe topic?] remove this instance's topic subscription(s)
[:back] restore the previous conversation
from `:conv/history` (slice 3)
[:end <value>] terminate the conversation
All effects produce zero or more fragments and an updated conversation.
Beyond the keys read directly by :render / :handle, a component
may include:
:start (fn [self] [self' effects])
:stop (fn [self] [self' effects])
:wakeup (fn [self] [self' effects])
:start runs once immediately after instantiation for both stack
frames and embedded children, which lets "task" components launch
their first :call without a synthetic user event and lets widgets
subscribe or schedule timers. :stop runs just before a frame/subtree
is removed; :wakeup runs when a persisted or history-restored frame
becomes live again.
The pure runtime: `step`, `run-effects`, `dispatch`.
Hosts embedding stube do not call into this namespace directly; the
documented embedder surface lives in [[dev.zeko.stube.embed]]. This
file is the value-language only: every function takes a conversation
value (plus the event for `dispatch`) and returns the next conversation
value plus the fragments to emit.
Reading guide
-------------
Everything in this namespace operates on plain values. A handler
returns `[self' effects]`; `run-effects` folds those effects into the
conversation, producing the next conversation value and the list of
*fragments* that must be pushed to the browser. A fragment is just a
small map describing one Datastar event — see [[fragment-shape]] for
the schema.
No I/O, no atoms, no SSE — that all lives one layer up in
[[dev.zeko.stube.runtime]] / [[dev.zeko.stube.http]] / [[dev.zeko.stube.server]].
This means the entire interaction loop is testable from a REPL with
`dispatch` and a hand-built conversation value, with no server running.
Effect vocabulary
-----------------
[:call <embed> :resume <k>] push a child frame; on `:answer` the
parent's `:k` function is invoked
[:call-in-slot <slot> <embed> :resume <k>]
temporarily swap one embedded slot;
the child answers back to the parent
without taking over the page
[:set-keyed-children <slot> <[[key embed]…]>]
reconcile an ordered, key-addressed
set of children; emits per-child
append/remove/outer fragments
instead of re-rendering the parent
[:answer <value>] pop this frame; deliver `value` to
the parent under its resume key
[:replace <embed>] pop this frame and push another in
its place (Seaside `become:`)
[:patch <hiccup>] extra DOM patch (no stack change)
[:patch-signals <map>] push a Datastar signal patch
[:execute-script <js>] run literal JS in the browser
[:history :replace|:push url] sync browser URL (replaceState/pushState)
[:io <fn>] ask the bound runtime to run `(fn)`
off-thread; pure folds leave it inert
[:after ms event] schedule a future event for this instance
[:subscribe topic event] subscribe this instance to published messages
[:unsubscribe topic?] remove this instance's topic subscription(s)
[:back] restore the previous conversation
from `:conv/history` (slice 3)
[:end <value>] terminate the conversation
All effects produce zero or more fragments and an updated conversation.
Component lifecycle keys
------------------------
Beyond the keys read directly by `:render` / `:handle`, a component
may include:
:start (fn [self] [self' effects])
:stop (fn [self] [self' effects])
:wakeup (fn [self] [self' effects])
`:start` runs once immediately after instantiation for both stack
frames and embedded children, which lets "task" components launch
their first `:call` without a synthetic user event and lets widgets
subscribe or schedule timers. `:stop` runs just before a frame/subtree
is removed; `:wakeup` runs when a persisted or history-restored frame
becomes live again.Opaque host value attached to the kernel via the :app option. Bound
by the runtime during dispatch and render so component code can read
it through dev.zeko.stube.core/app without threading a kernel
reference.
Opaque host value attached to the kernel via the `:app` option. Bound by the runtime during dispatch and render so component code can read it through `dev.zeko.stube.core/app` without threading a kernel reference.
Runtime kernel currently folding an effect. Standalone helpers use
this to route regular functions such as s/publish! to the embedded
kernel when called from component code, while preserving the historical
default-kernel behaviour outside a runtime dispatch.
Runtime kernel currently folding an effect. Standalone helpers use this to route regular functions such as `s/publish!` to the embedded kernel when called from component code, while preserving the historical default-kernel behaviour outside a runtime dispatch.
Authenticated principal stamped onto the conversation at mint time
via :principal-fn. Bound by the runtime around dispatch and render
so component code can read it through dev.zeko.stube.core/principal.
Authenticated principal stamped onto the conversation at mint time via `:principal-fn`. Bound by the runtime around dispatch and render so component code can read it through `dev.zeko.stube.core/principal`.
Optional side-effect hook bound by the runtime for [:io f]. Keeping
this as a hook preserves pure run-effects/replay: without a runtime
binding, :io is data that produces no fragments and does not execute.
Optional side-effect hook bound by the runtime for `[:io f]`. Keeping this as a hook preserves pure `run-effects`/`replay`: without a runtime binding, `:io` is data that produces no fragments and does not execute.
Optional side-effect hook bound by the server while folding effects. The kernel stays ignorant of threads and SSE; it only describes the delayed event that should be sent later.
Optional side-effect hook bound by the server while folding effects. The kernel stays ignorant of threads and SSE; it only describes the delayed event that should be sent later.
Optional side-effect hook bound by the server for [:subscribe …].
Optional side-effect hook bound by the server for `[:subscribe …]`.
Optional side-effect hook bound by the server for [:unsubscribe …].
Optional side-effect hook bound by the server for `[:unsubscribe …]`.
(boot flow-id)(boot flow-id init-args)Mint the initial set of effects for a freshly minted conversation
whose root flow is flow-id. Pulled out so the http layer can ask
for them on first SSE connect. flow-id may also be an embed spec,
which lets an adapter preserve root init args until the SSE attaches.
Mint the initial set of effects for a freshly minted conversation whose root flow is `flow-id`. Pulled out so the http layer can ask for them on first SSE connect. `flow-id` may also be an embed spec, which lets an adapter preserve root init args until the SSE attaches.
(dispatch conv {:keys [instance-id event payload signals] :as ev})Apply one client event to a conversation. event is the map
{:instance-id "ix-7e2"
:event :submit ; or any keyword the component knows
:payload any-edn-value ; optional, from (s/on ... :as [:event v])
:signals {:answer "42"}}
Returns [conv' fragments]. Pure: no I/O, no globals.
Stale events — those whose instance-id no longer exists in the
conversation — are dropped silently. This matters for buttons that
are still in the DOM after their owning frame has been popped (e.g.
the user double-clicks an OK button: the first click :answers and
removes the instance; the second click arrives with an iid that's
already gone). Throwing here would surface as a 500 in the http
layer for what is, semantically, a no-op.
Apply one client event to a conversation. `event` is the map
{:instance-id "ix-7e2"
:event :submit ; or any keyword the component knows
:payload any-edn-value ; optional, from (s/on ... :as [:event v])
:signals {:answer "42"}}
Returns `[conv' fragments]`. Pure: no I/O, no globals.
Stale events — those whose `instance-id` no longer exists in the
conversation — are dropped silently. This matters for buttons that
are still in the DOM after their owning frame has been popped (e.g.
the user double-clicks an OK button: the first click `:answer`s and
removes the instance; the second click arrives with an iid that's
already gone). Throwing here would surface as a 500 in the http
layer for what is, semantically, a no-op.(redraw-top conv)Re-render the current top frame in-place without running :wakeup
or snapshotting. Returns [conv' [frag]]. Used by dev-tooling
paths (e.g. enabling halos on a live conversation) that want a fresh
frame against the current state.
Re-render the current top frame in-place without running `:wakeup` or snapshotting. Returns `[conv' [frag]]`. Used by dev-tooling paths (e.g. enabling halos on a live conversation) that want a fresh frame against the *current* state.
(resume-top conv)Run :wakeup for the current top frame and render it as a restored
frame. Used by the http layer when a persisted conversation reattaches.
Run `:wakeup` for the current top frame and render it as a restored frame. Used by the http layer when a persisted conversation reattaches.
(run-effects conv effects)Apply a sequence of effects to conv left to right, collecting
fragments. Returns [conv' fragments].
Apply a sequence of effects to `conv` left to right, collecting fragments. Returns `[conv' fragments]`.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |