Big-rock changes to stube. Released versions may batch more than one development entry.
(No changes yet.)
dev.zeko.stube.routes
namespace and the stray test that exercised its private fn.
The standalone server has long since used
dev.zeko.stube.adapter.ring/ring-handler; the internals
module map drops the line too.clj-kondo warnings (one in
src/, six in test/). The genuine false positive — kondo
can't see through runtime/cid-lock — gets an
#_:clj-kondo/ignore with a one-line explanation; the rest
are real cleanups.make lint target and made
make test depend on it. clj-kondo exits non-zero on any
warning, so the standard pre-PR check now catches lint
regressions before tests even run. AGENTS.md documents the
workflow and the #_:clj-kondo/ignore escape hatch.defalias macro in
dev.zeko.stube.core and rewrote every plain
(def ^{:doc "..."} foo target/foo) re-export with it.
:arglists now flows through, so (doc s/answer),
(doc s/patch), (doc s/end) — and CIDER eldoc — show the
target signature. clj-kondo learns the form via
:lint-as clj-kondo.lint-as/def-catch-all so dependent
namespaces still resolve s/... names.core_test/every-public-name-has-doc-and-arglists
sweeps ns-publics of dev.zeko.stube.core and fails if any
non-:no-doc var loses its docstring, or any function var
loses :arglists. Locks the R1-04 invariant against
regression.replay-event helper
out of core.clj and runtime.clj into a single public
conversation/replay-event. Both callers (core/replay and
runtime/replay-with) now share the event-shape normalisation
rule.conversation/snapshot-for-dispatch
from inside kernel/dispatch. The [:back] carve-out (a
handler walking history backwards must not have its own
pre-state pushed onto that history first) now lives next to
the conversation data it shapes; kernel/dispatch reads
the snapshot decision in one line instead of ten.registry/register! now throws ex-info
when a defcomponent form declares both :foo and
:component/foo for any lifecycle key (silent lift used to
overwrite the long-form value). The registry ns docstring and
docs/api.md document why lifecycle keys are a closed set
while resume keys (:on-foo, :on-error-foo) pass through
verbatim.subvec + apply hash-map
accessors in effects/call-resume and
effects/slot-call-resume with positional destructure
(Option A from the issue). The wire shape stays a vector and
the kernel multimethod is unchanged; the per-effect
allocation overhead in call/call-in-slot goes away.answer-error fallback warning's
once-per-pair dedup from a JVM-global atom in kernel.clj
onto the kernel value (:!answer-error-warned, reset on
halt!). Two embedded kernels in the same JVM now each emit
their own one-time message. Kernel-less paths (pure
s/dispatch / s/replay) skip the warning entirely.Dynamic bindings section in
docs/internals.md catalogues every ^:dynamic var (15 of
them across kernel/render/effects/errors/flow/dev) by where
it is bound, where it is read, and what nil means. Also
fixes the stale render/*conv* docstring (frame/render-frame
is the binder, not the kernel).load_direction_test codifies the
pure/impure split. Walks ns-aliases transitively from each
pure namespace (conversation, effects, fragments,
kernel, frame, lifecycle, registry, render, plus
the dev/errors/keyed helpers they pull) and fails if
any reach runtime, server, http, the adapters, or the
kit glue.AGENTS.md documents the convention.:url machinery
(S-11) is wired directly into kernel/dispatch via
maybe-emit-url; a hook list would generalise it, but a
hook list with one consumer is harder to read than the
straight call. Decision: not yet. docs/internals.md picks
up a one-paragraph note next to the dispatch-path diagram so
the seam is grep-able when a second consumer appears.requiring-resolve indirection
in dev.zeko.stube.embed. The namespace now contains only
the ten documented public fns
(make-kernel/mint-conversation!/shell-for/head-tags/
dispatch!/replay-with/halt!/shutting-down?/publish!),
each a thin direct delegate to dev.zeko.stube.runtime. The
~25 ^:no-doc plumbing fns it used to expose for adapters
have moved back to runtime; http, halos/http,
adapter/ring, and server :require runtime directly.dev.zeko.stube.server to
lifecycle (start!, stop!, mount!, unmount!, mounts,
reset-state!, the reaper) plus a small default-kernel
convenience surface (default-kernel, conversation,
active-conversations, end!, inspect, publish!). The
~20 wrapper fns that nothing outside the namespace needed
are gone; the two consumer test files (server_test,
http_test) switch to rt/foo (server/default-kernel) ….docs/decisions/0006-embed-as-direct-runtime-facade.md
records the decision behind R1-05 and R1-06. The original
requiring-resolve choice was never written down; this
ADR is the first written form, and the index table also
picks up the missing 0005 entry along the way.nix develop,
clojure -M:examples, the examples table, Datastar
Inspector and (s/inspect cid) hints). Content was reordered,
not rewritten.:example-ring deps.edn alias runs the plain-Ring embedded
example (examples/dev/zeko/stube/examples/embedded_ring.clj)
via clojure -M:example-ring instead of needing a REPL.Road to 1.0 (breaking, pre-1.0): trimmed pre-1.0 surface in preparation for stability.
:emit-on-mount colocated key; use :start
directly. :start already covers the effect-only case
((fn [self] [self effects])), so the sugar layer was net
cruft. Tutorial chapter and reading_list.clj updated. The
S-12 CHANGELOG note below describes the original sugar.:legacy standalone
paths (/conv/:cid/sse, /conv/:cid/:iid/:event, …) are gone;
every kernel now uses the :adapter paths (/sse/:cid,
/event/:cid/:iid/:event, …). :route-style is no longer
an option to s/start!, make-kernel, or the shell.s/with-app and s/with-principal macros so component
tests no longer reach into dev.zeko.stube.kernel/*current-*
directly. Docstrings, docs/api.md, docs/tutorial.md, and
ADR 0004 updated to use them.S-15: s/on-unmount Hiccup helper for preserved hosts. Mirrors
s/on-mount: returns a data-stube-on-unmount attribute carrying
a synchronous JS expression with el bound to the host element.
preserve.js grew a document-wide MutationObserver that fires the
expression once when the host genuinely detaches — queueMicrotask
defers the check so Idiomorph's detach+reattach swap dance can't
double-fire. Logs to console.error on throws; never blocks the
morph. CodeMirror/Chart.js/<video> integrations now have a real
teardown path. README and preserved_widget.clj updated.
S-14: (s/answer-error ex) + :on-error-<key> resume keys.
Symmetric child→parent failure routing — the child catches its
exception and emits (s/answer-error ex); the parent declares
:on-error-saved next to :on-saved and receives the exception
verbatim. Three-tier fallback: explicit :on-error-<key> →
:on-<key> with [:error ex] plus a one-time deprecation
warning per cdef → the default error-frame banner. New ADR
0005-answer-error-and-resume.md; new worked example
error_answer.clj; todo.md §2 entry removed.
S-13: New docs/api.md section "Reading dependencies — app
vs context vs principal" with a comparison table, decision
tree by question, three common mistakes (notably reading
(s/context self) from :init), and a worked migration moving
the DB choice from :app to :context-fn. Cross-refs from the
existing s/app / s/context / s/principal entries, from the
defcomponent :init row, and from make-kernel's opts list.
S-12: Shareable-URL bootstrap recipe and :emit-on-mount sugar.
Declaring :emit-on-mount (fn [self] effects) lifts to
:component/start at registration; declaring both is a register-time
error. New worked example reading_list.clj demonstrates the
three-piece pattern (:init-args-fn → :emit-on-mount →
:url) end-to-end. New tutorial chapter "Shareable views — URL as
durable state" walks through the same flow. docs/api.md
cross-refs from mount! and keyed-children.
S-11: Root-component :url key for declarative URL sync. Returns
nil, a string, or [:replace|:push url]; the kernel diffs against
:conv/last-url after every dispatch and auto-emits a [:history …]
effect on change. Explicit (s/history …) from the handler always
wins. Only the root frame's :url applies. :init-args-fn on
mount! pairs with this to read the URL back in on a fresh mount.
url_state_counter.clj refactored to use the new form;
url_state_counter_manual.clj preserves the hand-rolled version for
comparison.
:call-in-slot previous-chain leak surfaced by
kernel-property-test during the 0.1.1 sweep. New
conversation/subtree-ids walks :instance/previous chains
alongside :instance/children and :instance/keyed-slots; the
frame-destruction paths (pop-top, :replace, :end, root-frame
:answer, keyed-child removal, runtime halt!) use it so
previous-chain instances get their :stop hooks and are swept
from :conv/instances. The narrow descendant-ids survives for
paths where the previous gets restored (answer-from-slot,
mark-rendered, history wakeup). Pinned by a focused regression in
embed-test and by tightened structural assertions in
kernel-property-test.Road-to-1.0 sweep. See todo.md for the items deliberately deferred
past this release (composition spikes, popstate, extra HTTP routes,
the :call-in-slot previous-chain leak surfaced by the new property
test).
kernel_property_test uses test.check
to walk random event sequences through kernel/dispatch against a
stub registry, asserting after every step that (a) no throw,
(b) pr-str/read-string round-trip yields an equal conversation,
(c) every :elements/:error fragment that names an iid selector
refers to an instance that exists post-dispatch, and (d) the call
stack and :instance/children only reference live instances.
The test surfaced a real leak: :call-in-slot's
:instance/previous chain is orphaned when the parent frame is
replaced or ended before the slot child answers. Out of scope here;
named and bounded by a comment in the test so it does not get
forgotten.kernel, server, conversation, runtime,
render, frame, fragments, http, lifecycle, effects,
registry). embedded_ring.clj stays the documented exception
for dev.zeko.stube.embed as the host-embedder surface.docs/internals.md, with a sketched
Publisher protocol for the eventual seam if a real bus is ever
wanted, plus a working-today recipe using :app as a bring-your-own
bus. New docs/decisions/ folder with four short ADRs covering
resume-key naming, EDN-clean conversation state, the embed/call
split, and the :app + :principal-fn contract.stube-keepalive
event (an SSE event-type Datastar ignores) every
:sse-keepalive-ms milliseconds, default 15000. The heartbeat
stops when the SSE channel unregisters or the kernel halts. Pass
nil or 0 to disable. docs/internals.md carries an "SSE behind
a reverse proxy" section with nginx, ALB, Caddy, and HAProxy knobs.defflow durability is now documented as a deliberate property,
not a pending gap. A conversation containing a defflow is
in-memory only by design (its cloroutine continuation is not
EDN-serialisable). For long-running flows that must survive a
restart, write the same shape as a hand-rolled task component with
:start + named resume keys; the tutorial now shows the two side
by side. The store's skip-on-defflow warning is more pointed about
the workaround.:app option on make-kernel carries an opaque host value
(typically a map of long-lived dependencies) that component code
reads via (s/app). The value is not serialised with the
conversation; rebuild it from live JVM state on each
make-kernel call.:principal-fn option is invoked once when a conversation is
minted; its result is persisted on the conversation under
:conv/principal and surfaced through (s/principal). The
principal is fixed for the life of the conversation — end and
re-mint to change identity. There is no set-principal!
operation by design.s/start! for the standalone
server. The protected_counter example now reads its principal
via (s/principal) instead of carrying app-level login state on
the conversation.dev.zeko.stube.embed as the documented host-embedder
namespace. dev.zeko.stube.kernel is back to being just the pure
effect fold (step, run-effects, dispatch, boot,
resume-top, redraw-top). Internal callers, tests, examples, and
docs have been updated. The §15.4 line-count invariant has been
replaced by a structural one: the runtime stays organised around a
single effect multimethod.s/back is now a zero-arity function (s/back) returning the
[:back] effect, matching every other effect constructor. Call
sites that used the bare value need a pair of parens.dev.zeko.stube.kernel/replay is now kernel/replay-with so the
kernel-aware host helper no longer collides in name with the
kernel-less core/replay used by component-author tests.registry/register! now lifts every colocated author key —
:start, :stop, :wakeup, :children in addition to the
previous :init/:render/:handle/:keep/:doc/:state — to
its :component/<name> home. Component definitions registered via
any entry point (defcomponent, register-component!,
decorate!) are now uniform; the kernel reads them under a single
namespace.s/on-target
against the parent iid instead of synthesising a fake parent
instance map.204 no-ops instead of ending the
whole conversation; missing/ended conversations still surface the
stale-page response.s/publish! routes
to the active embedded kernel when called from component code.:io a runtime-interpreted effect. Pure dispatch/replay
keep it inert unless a runtime hook is bound, preserving the kernel's
value-oriented testability.s/on-target, with
s/event-url documented as the low-level escape hatch.s/call, s/become, and s/call-in-slot accept existing embed
specs directly, so stock helpers like (s/confirm "?") compose
without reaching into internal effect constructors.stube/head-tags now returns the stock CSS,
preserve bridge, Datastar, and optional halos assets for the current
kernel/base path; examples no longer reach into shell internals.Can you improve this documentation?Edit on GitHub
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 |