Liking cljdoc? Tell your friends :D

hyper.subview

Unified per-tab sub-region lifecycle registry.

A subview is a managed region of a page that owns a slice of lifecycle: it subscribes to a set of Watchable :deps, optionally renders (the reactive case), and is swept when it disappears from the view tree. This is the single mechanism the higher-level subsystems route through:

  • hyper.reactive (a render-bearing subview, :on-change :partial) — re-renders only itself when its deps change.
  • (future) h/watch! (a deps-only subview, :on-change :full) and h/async (a :mount/:unmount-bearing subview) plug into the same registry.

State layout (per tab): [:tabs id :subviews sid] -> the subview spec (see register-subview!) [:tabs id :subview-watches sid] -> {watch-key dep} bookkeeping for teardown

Deps are reference-counted via hyper.watch so a source shared across subviews/tabs is only disposed when the last consumer releases it. A subview's :on-change decides what a dep change triggers: :partial re-renders just this subview (a targeted Datastar fragment), :full triggers a full page re-render.

Unified per-tab sub-region lifecycle registry.

A *subview* is a managed region of a page that owns a slice of lifecycle:
it subscribes to a set of Watchable `:deps`, optionally renders (the
reactive case), and is swept when it disappears from the view tree.  This
is the single mechanism the higher-level subsystems route through:

- `hyper.reactive` (a render-bearing subview, `:on-change :partial`) —
  re-renders only itself when its deps change.
- (future) `h/watch!` (a deps-only subview, `:on-change :full`) and
  `h/async` (a `:mount`/`:unmount`-bearing subview) plug into the same
  registry.

State layout (per tab):
  [:tabs id :subviews        sid] -> the subview spec (see register-subview!)
  [:tabs id :subview-watches sid] -> {watch-key dep} bookkeeping for teardown

Deps are reference-counted via `hyper.watch` so a source shared across
subviews/tabs is only disposed when the last consumer releases it.  A
subview's `:on-change` decides what a dep change triggers: `:partial`
re-renders just this subview (a targeted Datastar fragment), `:full`
triggers a full page re-render.
raw docstring

get-subviewclj

(get-subview app-state* tab-id sid)

Retrieve a subview's spec from app-state.

Retrieve a subview's spec from app-state.
sourceraw docstring

key->tokenclj

(key->token k)

Derive a stable, id-safe token from a region :key value. Simple values pass through; anything else collapses to a hash.

Derive a stable, id-safe token from a region `:key` value.  Simple values
pass through; anything else collapses to a hash.
sourceraw docstring

partial-renderclj

(partial-render app-state* tab-id sid)

Re-render a single render-bearing subview and return the HTML string for a targeted Datastar fragment. Updates the cache. Returns nil when the subview is no longer registered or has no render-fn.

Re-render a single render-bearing subview and return the HTML string for a
targeted Datastar fragment.  Updates the cache.  Returns nil when the
subview is no longer registered or has no render-fn.
sourceraw docstring

register-subview!clj

(register-subview! app-state* tab-id sid spec)

Record/refresh a subview during a render, marking it live for this render cycle so it survives the post-render sweep, and wire its dep watches when a renderer is present (see wire-subview!). Wiring can enqueue a partial render if a dep already changed. Idempotent.

spec keys (all optional unless noted):

  • :deps vector of Watchable sources to subscribe (ref-counted)
  • :on-change :partial (default) | :full — what a dep change triggers
  • :scope :render (default) | :mount — sweep lifetime (see below)
  • :render-fn thunk returning hiccup (render-bearing subviews)
  • :cached-html last rendered HTML string (for partial re-render)
  • :html-id the element id targeted by partial fragments
  • :dep-vals snapshot of (mapv deref deps) at last render
  • :mount (fn [] -> resource) run once when the subview first appears
  • :unmount (fn [resource] ...) run when the subview is swept/torn down
  • :resource value returned by :mount, threaded into :unmount

:scope decides the sweep lifetime: :render subviews (reactive regions) are re-registered every full render and swept when absent; :mount subviews (h/watch!, async workers — registered once in form-2 setup / a form-3 :mount) survive per-render sweeps and are torn down only on page-view remount (navigation) or tab disconnect.

Returns the stored spec.

Record/refresh a subview during a render, marking it live for this render
cycle so it survives the post-render sweep, and wire its dep watches when a
renderer is present (see `wire-subview!`).  Wiring can enqueue a partial
render if a dep already changed.  Idempotent.

`spec` keys (all optional unless noted):
- :deps        vector of Watchable sources to subscribe (ref-counted)
- :on-change   :partial (default) | :full — what a dep change triggers
- :scope       :render (default) | :mount — sweep lifetime (see below)
- :render-fn   thunk returning hiccup (render-bearing subviews)
- :cached-html last rendered HTML string (for partial re-render)
- :html-id     the element id targeted by partial fragments
- :dep-vals    snapshot of (mapv deref deps) at last render
- :mount       (fn [] -> resource) run once when the subview first appears
- :unmount     (fn [resource] ...) run when the subview is swept/torn down
- :resource    value returned by :mount, threaded into :unmount

`:scope` decides the sweep lifetime: `:render` subviews (reactive regions)
are re-registered every full render and swept when absent; `:mount`
subviews (h/watch!, async workers — registered once in form-2 setup / a
form-3 :mount) survive per-render sweeps and are torn down only on
page-view remount (navigation) or tab disconnect.

Returns the stored spec.
sourceraw docstring

register-watch!clj

(register-watch! app-state* tab-id source)
(register-watch! app-state* tab-id source scope)

Register an external source as a full-render subview keyed by source identity (dedup), returning the subview id. This is the shared primitive behind every kind of external-source watch:

  • :mount scope (default) — user h/watch! and route-level :watches; torn down on page-view remount (navigation) / disconnect.
  • :tab scope — framework watches (routes/head Vars, the component registry); set up once at SSE connect, torn down only on disconnect.

A source watched via several paths collapses to a single subview that triggers a full re-render on change and is wired by setup-new-watches!.

Register an external `source` as a full-render subview keyed by source
identity (dedup), returning the subview id.  This is the shared primitive
behind every kind of external-source watch:

- `:mount` scope (default) — user `h/watch!` and route-level `:watches`;
  torn down on page-view remount (navigation) / disconnect.
- `:tab` scope — framework watches (routes/head Vars, the component
  registry); set up once at SSE connect, torn down only on disconnect.

A source watched via several paths collapses to a single subview that
triggers a full re-render on change and is wired by `setup-new-watches!`.
sourceraw docstring

render-async!clj

(render-async! app-state*
               tab-id
               session-id
               router
               key
               fallback-id
               user-deps
               fetch-fn
               render-fn)

Render an h/async region during a full page render.

Coordinates the backing fetch (see coordinate-async!), renders the body via (render-fn @cell) where @cell is the status map {:status :loading|:reloading|:ready|:error :result … :error …}, injects the component id for targeted partial fragments, caches the HTML, and registers a render-scoped, partial-on-change subview whose deps are [cell & user-deps]:

  • a write to cell (the worker finishing) re-renders only this region;
  • a change to a user dep re-runs the body, whose coordination step detects the change and refetches (stale-while-revalidate).

:unmount (run when the region is swept — it disappeared from the view tree — or on disconnect) interrupts any in-flight fetch and drops the coordination store. The render body must return a single rooted hiccup element (as with reactive), so the id can be injected. Returns the hiccup.

Render an `h/async` region during a full page render.

Coordinates the backing fetch (see `coordinate-async!`), renders the body
via `(render-fn @cell)` where `@cell` is the status map
`{:status :loading|:reloading|:ready|:error :result … :error …}`, injects
the component id for targeted partial fragments, caches the HTML, and
registers a render-scoped, partial-on-change subview whose deps are
`[cell & user-deps]`:

- a write to `cell` (the worker finishing) re-renders only this region;
- a change to a user dep re-runs the body, whose coordination step detects
  the change and refetches (stale-while-revalidate).

`:unmount` (run when the region is swept — it disappeared from the view
tree — or on disconnect) interrupts any in-flight fetch and drops the
coordination store.  The render body must return a single rooted hiccup
element (as with `reactive`), so the id can be injected.  Returns the
hiccup.
sourceraw docstring

render-reactive!clj

(render-reactive! app-state* tab-id key fallback-id deps render-fn)

Render a render-bearing subview inline during a full page render.

Always re-executes render-fn because the body may close over parent data (cursor values, fn args, watched snapshots) that changed but is not tracked in deps. Injects the subview id onto the returned element (or uses an existing :id), caches the serialized HTML for later partial re-renders, and registers the subview as live. Returns the hiccup.

Identity resolves as key (path-scoped) > the root element's :id > fallback-id (the positional id), and keys the registry and morph anchor alike. A key also scopes the region path for nested regions. :on-change defaults to :partial — a dep change re-renders only this region.

Render a render-bearing subview inline during a full page render.

Always re-executes `render-fn` because the body may close over parent data
(cursor values, fn args, watched snapshots) that changed but is not tracked
in `deps`.  Injects the subview id onto the returned element (or uses an
existing :id), caches the serialized HTML for later partial re-renders, and
registers the subview as live.  Returns the hiccup.

Identity resolves as `key` (path-scoped) > the root element's `:id` >
`fallback-id` (the positional id), and keys the registry and morph anchor
alike.  A `key` also scopes the region path for nested regions.
`:on-change` defaults to `:partial` — a dep change re-renders only this
region.
sourceraw docstring

scoped-idclj

(scoped-id prefix tab-id token)

Compose a CSS-safe region id from prefix, tab-id, the ambient *region-path*, and token. A nested region encodes its parent path as a hex hash segment rather than a path-joined string, so the id stays a valid #id selector for Datastar morphing.

Compose a CSS-safe region id from `prefix`, `tab-id`, the ambient
`*region-path*`, and `token`.  A nested region encodes its parent path as a
hex hash segment rather than a path-joined string, so the id stays a valid
`#id` selector for Datastar morphing.
sourceraw docstring

setup-new-watches!clj

(setup-new-watches! app-state* tab-id trigger-render! enqueue-partial!)

Set up dep watches for subviews that don't already have them. Called after each full render to wire up new/changed subviews.

trigger-render! is a zero-arg fn (full re-render) for :full subviews; enqueue-partial! is a one-arg fn (component-id) for :partial subviews. Either may be nil when that change policy is not in use.

Set up dep watches for subviews that don't already have them.  Called after
each full render to wire up new/changed subviews.

`trigger-render!` is a zero-arg fn (full re-render) for `:full` subviews;
`enqueue-partial!` is a one-arg fn (component-id) for `:partial` subviews.
Either may be nil when that change policy is not in use.
sourceraw docstring

setup-subview-watches!clj

(setup-subview-watches! app-state*
                        tab-id
                        sid
                        deps
                        on-change
                        trigger-render!
                        enqueue-partial!)

Set up watches on a subview's deps. When any dep changes, the callback fires according to on-change: :full invokes trigger-render! (a full page re-render), anything else (:partial) invokes (enqueue-partial! sid). Uses reference counting via retain/release on each dep.

Set up watches on a subview's deps.  When any dep changes, the callback
fires according to `on-change`: `:full` invokes `trigger-render!` (a full
page re-render), anything else (`:partial`) invokes `(enqueue-partial! sid)`.
Uses reference counting via retain/release on each dep.
sourceraw docstring

spawn-worker!clj

(spawn-worker! app-state* tab-id sid session-id router worker-fn)

Register (idempotently) a mount-scoped background-worker subview keyed by sid. On first registration spawns a virtual thread that runs worker-fn with *request* rebound to this tab's context, stores the Thread as the subview :resource, and installs an :unmount that interrupts it.

The worker runs on a fresh virtual thread, which starts with its own thread-locals, so:

  • *request* is rebound explicitly (like the action macro) so cursor reads/writes resolve to this tab;
  • the render guard sees a clean thread, so the worker may write cursors freely (the whole point of a worker);
  • cursor writes hit the live app-state directly.

Mount-scoped: it survives the per-render sweep and is torn down only on page-view remount (navigation / handler redefinition) or tab disconnect, at which point :unmount interrupts the thread.

Idempotent: a form-1 render body re-invokes h/spawn! on every render, so only the first occurrence (when sid is absent) actually spawns; later renders are a no-op.

Register (idempotently) a mount-scoped background-worker subview keyed by
`sid`.  On first registration spawns a virtual thread that runs
`worker-fn` with `*request*` rebound to this tab's context, stores the
`Thread` as the subview `:resource`, and installs an `:unmount` that
interrupts it.

The worker runs on a fresh virtual thread, which starts with its own
thread-locals, so:
- `*request*` is rebound explicitly (like the `action` macro) so cursor
  reads/writes resolve to this tab;
- the render guard sees a clean thread, so the worker may write cursors
  freely (the whole point of a worker);
- cursor writes hit the live app-state directly.

Mount-scoped: it survives the per-render sweep and is torn down only on
page-view remount (navigation / handler redefinition) or tab disconnect,
at which point `:unmount` interrupts the thread.

Idempotent: a form-1 render body re-invokes `h/spawn!` on every render, so
only the first occurrence (when `sid` is absent) actually spawns; later
renders are a no-op.
sourceraw docstring

sweep-stale!clj

(sweep-stale! app-state* tab-id live-ids)

Remove render-scoped subviews that were not re-registered during the last full render: tear down their watches, release dep refcounts, and run any :unmount. Mount-scoped subviews (h/watch!, async) are immune — they are torn down by teardown-mount-scoped! (on remount) or teardown-all! (on disconnect).

Remove *render-scoped* subviews that were not re-registered during the last
full render: tear down their watches, release dep refcounts, and run any
:unmount.  Mount-scoped subviews (h/watch!, async) are immune — they are
torn down by `teardown-mount-scoped!` (on remount) or `teardown-all!` (on
disconnect).
sourceraw docstring

teardown-all!clj

(teardown-all! app-state* tab-id)

Remove all subviews for a tab. Called on disconnect.

Remove all subviews for a tab.  Called on disconnect.
sourceraw docstring

teardown-mount-scoped!clj

(teardown-mount-scoped! app-state* tab-id)

Tear down all mount-scoped subviews for a tab (h/watch!, async workers). Called when the page-view remounts (navigation / handler redefinition) — the new mount re-registers whatever it still needs.

Tear down all *mount-scoped* subviews for a tab (h/watch!, async workers).
Called when the page-view remounts (navigation / handler redefinition) — the
new mount re-registers whatever it still needs.
sourceraw docstring

teardown-subview-watches!clj

(teardown-subview-watches! app-state* tab-id sid)

Remove watches and release dep refcounts for a subview.

Remove watches and release dep refcounts for a subview.
sourceraw docstring

watched-sourcesclj

(watched-sources app-state* tab-id)

The distinct external sources watched via mount-scoped, full-render subviews (i.e. h/watch!). Used by hyper.test to report a render's watches.

The distinct external sources watched via mount-scoped, full-render
subviews (i.e. h/watch!).  Used by `hyper.test` to report a render's
watches.
sourceraw docstring

wire-subview!clj

(wire-subview! app-state* tab-id sid)

Wire a subview's dep watches when an SSE renderer is present and the subview has deps not already wired. A no-op before a renderer exists — the first full render's setup-new-watches! wires it then.

If a dep changed between the render and this wiring (e.g. an async fetch that landed before the watch was attached), fires the change once so the update is not missed.

Wire a subview's dep watches when an SSE renderer is present and the subview
has deps not already wired.  A no-op before a renderer exists — the first
full render's `setup-new-watches!` wires it then.

If a dep changed between the render and this wiring (e.g. an async fetch that
landed before the watch was attached), fires the change once so the update is
not missed.
sourceraw docstring

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