Liking cljdoc? Tell your friends :D

Changelog

Big-rock changes to stube. Released versions may batch more than one development entry.

Unreleased

(No changes yet.)

0.1.7

  • Playwright-based e2e smoke suite. New :e2e deps alias and make e2e target run a browser-driven sanity pass over the live examples catalogue. Twenty tests pin one stube primitive each: morph-by-id, s/keyed-children, :call-in-slot, :url projection, s/decorate, s/preserve, :principal-fn gating, error-frame / s/answer-error, defflow/s/await/wizard-back, dialogs, recursive renderers, structured event payloads, table sort, pagination, inactive-child preservation. The suite is gated to make e2e and make release; make test stays Clojure-only and fast. See test-e2e/dev/zeko/stube/e2e/ for the harness.
  • mint-conversation! bugfix. A fresh shell visit minted a session id for Set-Cookie, then ignored it and minted a second id inside mint-conversation!. The conversation ended up owned by the second id while the browser was told to send the first — every subsequent SSE GET hit the cross-session check and returned 403. Manual browser sessions papered over this by reusing a cookie from an earlier tab, but a fully fresh BrowserContext (or any new install) was broken. shell-handler now threads its sid through to a new 5-arity mint-conversation!. Pinned by shell-set-cookie-matches-conv-owner-token in http_test.
  • examples/reading_list.clj Close button. The per-card Close button used (s/on self :click :as [:close id]), where self is the item — but :reading/item has no :handle, so the click was a no-op and the desk's :close resume never ran. Switched to s/on-target against (:instance/parent self) so the click POSTs to the desk that owns the handler. The e2e test for URL-driven restore + close now exercises the full round-trip.

0.1.6

  • R1 — refactor round 1 (road to 1.0): cleanup, docs, and a shape refresh around the embed / runtime / server seam. No user-visible behaviour change; the public surface stays where it was. See #28 for the umbrella plan.
    • R1-01 (#29): removed the dead 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.
    • R1-02 (#30): cleared seven 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.
    • R1-03 (#31): added a 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.
    • R1-04 (#32): introduced a private 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.
    • R1-11 (#39): new 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.
    • R1-08 (#36): hoisted the duplicated 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.
    • R1-18 (#46): extracted 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.
    • R1-17 (#45): 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.
    • R1-10 (#38): replaced the 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.
    • R1-13 (#41): moved the 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.
    • R1-09 (#37): new 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).
    • R1-12 (#40): new 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.
    • R1-15 (#43): replaced the U+2500 BOX DRAWINGS LIGHT HORIZONTAL character used as a section underline in several ns docstrings with plain ASCII hyphens. Renders the same in monospace terminals but no longer breaks in editors that don't auto-detect UTF-8 or in GitHub's plaintext diff view. AGENTS.md documents the convention.
    • R1-14 (#42): spike-on-hold. The :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.
    • R1-05 (#33): dropped the 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.
    • R1-06 (#34): narrowed 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) ….
    • R1-07 (#35): new ADR 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.
    • R1-16 (#44): restructured the README into two clearly delimited paths — Getting started for downstream users (Clojars coordinate, the counter snippet, Datastar SDK pinning, Ring embedding, kit-clj, host widget integration) and Running this repo for contributors (nix develop, clojure -M:examples, the examples table, Datastar Inspector and (s/inspect cid) hints). Content was reordered, not rewritten.
  • New :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.

0.1.5

  • The dev/debug mode halos improved in terms of experience

0.1.4

  • Deployment error forced this release to keep changelog consistent

0.1.3

  • Road to 1.0 (breaking, pre-1.0): trimmed pre-1.0 surface in preparation for stability.

    • Removed the :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.
    • Removed the dual URL route shape: the :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.
    • Added 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.

0.1.2

  • Fix the :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.

0.1.1

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).

  • Generative kernel test. New 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.
  • Tightened invariant test: examples may not reach into internal namespaces (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.
  • Documentation: cross-process pub/sub is now called out as single-JVM-by-design in 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.
  • SSE heartbeat for reverse-proxy idle timeouts. Every kernel now runs a per-conversation keepalive thread that sends a 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.
  • Application-boundary primitives for embedders:
    • New :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.
    • New :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.
    • Both options are accepted by 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.
  • Extracted 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.

0.1.0

  • API polish pass before 1.0:
    • 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.
    • Updated the Seaside-todo menu example to use s/on-target against the parent iid instead of synthesising a fake parent instance map.
  • Hardened stale-event handling: POSTs for missing instances inside a live conversation are now harmless 204 no-ops instead of ending the whole conversation; missing/ended conversations still surface the stale-page response.
  • Tightened runtime isolation for embedders. Runtime state now lives on kernel values, shell/head helpers are public, and s/publish! routes to the active embedded kernel when called from component code.
  • Made :io a runtime-interpreted effect. Pure dispatch/replay keep it inert unless a runtime hook is bound, preserving the kernel's value-oriented testability.
  • Hardened keyed children: keyed metadata survives handler state replacement, children inherit conversation context, and changed embed args rebuild the child subtree while preserving the root iid.
  • Added public cross-instance event targeting via s/on-target, with s/event-url documented as the low-level escape hatch.
  • Made 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.
  • Improved shell embedding: 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.
  • Improved error targeting so first-render failures patch the shell root correctly instead of assuming the failing instance already exists in the DOM.
  • Removed the unused legacy async registry namespace; timers, pub/sub, SSE sessions, pending roots, and shutdown state are owned by the embeddable runtime.
  • Refreshed the public documentation set: README, tutorial, API reference, internals guide, historical design notes, roadmap, and this changelog now describe the current framework surface.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close