Skip to content

API reference

Every public name Fresco ships, with the signature it ships with, grouped by the namespace that exports it.

This page is for looking something up. It states what a door takes, what it answers, and the one or two facts about it that a signature cannot carry. It does not teach: the numbered chapters do that, and each entry points at the one that owns it.

Every signature below was read off the source in implementation/fresco/, never off another document. Where a door's behaviour is decided somewhere else — React's own contract, core's rf/make-frame, the platform's <dialog> — the entry says so rather than restating it as Fresco's.

How to read an entry

Each namespace opens with one block carrying every name it exports, then a table saying what each one is for. A name written (name args) is called; a name written [name props children] is a Hiccup head; a name with no parentheses is a value.

A macro is marked as one. It matters here more than usual, because two of Fresco's doors are macros that expand to a defh/defview and h/defhost — so they are written at the top level of a namespace and never inside a body.

Refusals are ex-infos carrying a stable :rf.error/… id in ex-data. Entries name the ids a door raises; Errors explains the shape and Troubleshooting indexes them.

What this page does not enumerate

Three questions are answered authoritatively on another page of this guide. Copying an answer here would produce a second one that goes stale the day the first moves, so this page points at each instead.

Question Where it is answered
What each surface does on the server, and under hydration SSR and hydration
Every refusal id, its reason and its recovery Troubleshooting, with Errors for the shape each one carries
Which doors this guide teaches under a spelling the code does not yet carry the Status block on the guide index

A fourth — which spellings changed, when, and under whose ruling — has no other page to be answered on, and a lookup page is where a reader meets it: you type a name you remember, the compiler says it does not exist, and you want to know what it became. So it is answered here, at Names that changed.

re-frame.fresco — the door

The one namespace an ordinary application requires. Fourteen names, and every optional module is reached separately so that an application which never asks for one carries none of it.

(ns my.app
  (:require [re-frame.fresco :as h]))

;; authoring — macros, written at the top level
(h/defview name docstring? [props] body )
(h/defhost name docstring? component opts?)
(h/event [args ] body )

;; reads — called inside a body
(h/sub query-v)
;; the frame doors are core's: (rf/current-frame-id) and (rf/capture-frame)

;; roots
(h/client-root)
(h/render!  handle view container opts?)
(h/unmount! handle)

;; frame boundaries — written IN the tree
[h/frame-root     {:id :app/main :initial-events [] make-frame opts} child ]
[h/frame-provider {:frame :app/main} child ]

;; markup
[h/error-boundary {:fallback f :reset-key k :on-error e} child ]
[h/portal {:target node :fallback markup} child ]
(h/route-link {:to route :params p :query q :fragment s} child )
(h/as-element hiccup)
(h/as-component view)

;; local state
(h/reg-state concern opts?)

Authoring

Name What it is
h/defview Macro. Mints a boundary — a real React function component, and a legal Hiccup head. argv is the ordinary one-props-map argument vector. The macro reads no body: it expands to a def of the minted head plus a source coordinate, so a refusal raised while the body runs can name where the boundary was written. The fn it emits is anonymous, so nothing it binds can shadow a helper of the same name. Taught in Views and reads.
h/defhost Macro. Declares a crossing to a foreign React component once, and answers a var usable as a Hiccup head anywhere. Two shapes, (defhost name component) and (defhost name component opts), each with an optional docstring in second position. Anything past opts is refused with :rf.error/fresco-bad-host-declaration rather than dropped. Taught in Interop.
h/event Macro. The one callback form, for a position where the event itself is wanted. It expands to a marked fn and nothing else, so the value is an ordinary function and the contract comes from the position it is written at. Taught in Events as data.

h/defhost's opts map carries four keys and refuses any other:

Key Value Meaning
:callbacks an optional map from prop name to :event or :render an override for a prop whose spelling infers the wrong contract — a vendor's on*-named render prop. Contracts are otherwise inferred from the spelling, exactly as on a native tag
:slots a set of prop names the ReactNode positions. Hiccup written at one is lowered under the writing boundary's frame; at an undeclared prop, hiccup stays data
:server :client-only (the default) or :render whether the crossing contributes to a server response. :render is an assertion that the component is safe to run on the server, and mints no gate at all
:fallback inert Hiccup what renders in the host region while a :client-only crossing is absent. Refused beside :server :render, and refused if it contains a boundary head

Reads

Name Signature What it is
h/sub (h/sub query-v) The ambient collector. A plain function call, legal anywhere in a body — inside a when, a for, or an inlined helper — because the edge is recorded where the read happens. A branch not taken contributes no edge.

The frame doors are core's own and are legal inside a body: (rf/current-frame-id) answers the rendering boundary's frame id keyword, and zero-arity (rf/capture-frame) captures a frame api locked to it. Neither reads nor dispatches, which is why the render discipline admits them; see Events as data.

Roots

Three doors, one handle, and every one of them root-scoped: a page may hold as many roots as it likes, and no call here reaches a root the caller did not name. It is the grammar every React view adapter publishes (Spec 006 §The client root).

Name Signature What it is
h/client-root (h/client-root) Allocates an inert, opaque handle. No DOM work and no React call, so it is defonce-safe at namespace load. One handle owns at most one root at a time.
h/render! (h/render! handle view container) / (h/render! handle view container opts) The root door AND the hot-reload door. The FIRST call through a handle creates the root at container — or, with {:hydrate? true}, adopts the server-rendered DOM already there; every later call updates that same root inside flushSync, so React reconciles the new tree against the one on the page and no second constructor runs. container and opts are read on the first call only. Answers nil.
h/unmount! (h/unmount! handle) Takes this root down and touches nothing else — no sibling root's state, and not the container, which React empties and leaves in the document. Idempotent, and a later h/render! through the handle mounts afresh.

opts carries root options only — two keys, both read on the first call:

Key Meaning
:hydrate? Adopt container's existing server-rendered DOM rather than replacing it: hydrateRoot in place of createRoot, with this root's own adoption window and recoverable-error reporter. A first-call MODE, so a later call through a live handle ignores it rather than hydrating twice, and a hydrating first call returns before adoption has finished
:identifier-prefix React's identifierPrefix, handed to createRoot / hydrateRoot untouched. No default, no coercion, no validation. A page with two roots gives them distinct prefixes or watches their useId values collide

Because the root sits in the package's active set, rf/destroy-adapter! releases a still-live handle's root exactly once, an already-unmounted handle is not released again, and a h/render! after either release mounts afresh.

A key the roster does not carry is REFUSED, not ignored: :frame and :initial-events raise :rf.error/fresco-frame-config-misplaced naming the head that takes them, and anything else raises :rf.error/fresco-unknown-root-option.

The frame is written in the tree

Two heads, one verb each — the pair every re-frame2 view substrate spells (Frames spec).

Head Verb Contract
[h/frame-root {:id :f …} child …] ENSURE Creates the frame if absent and REUSES it if present: durable state (app-db, runtime-db, sub-cache, queue) survives and :initial-events are re-recorded but never replayed. A re-acquire is rf/make-frame's idempotent replacement, so the record CONFIG does refresh — and it is REPLACED wholesale, not merged, so re-declaring a live :id with a partial opts map drops what the creator set. frame-root DECLARES; to JOIN a frame another root already ensured, use [h/frame-provider {:frame :f} …], which takes no opts. Takes the whole rf/make-frame option map: :initial-events, :images, :url-bound?, :fx-overrides, :preset, every record-config key. :id is required and must be a keyword. Unmounting destroys nothing
[h/frame-provider {:frame :f} child …] SCOPE Provides an ALREADY-LIVE frame to the subtree, and creates, refreshes and destroys nothing. :frame takes a frame-id keyword or the live frame value rf/make-frame returns. An absent frame is :rf.error/frame-provider-frame-absent rather than a subtree scoped to nothing

Each refuses the other's key by name: :frame on a frame-root is :rf.error/frame-root-given-frame pointing at frame-provider, and :id on a frame-provider is :rf.error/frame-provider-given-id pointing at frame-root. Changing a MOUNTED boundary's :id or opts is :rf.error/frame-root-reconfigured; pass a React :key and remount to point at a different frame.

The ENSURE is commit-owned, and the first paint is still the seeded one. frame-root's first render emits no subtree, and the frame is made in a useLayoutEffect — so a render React discards creates nothing and seeds nothing. The layout-phase state flip re-renders synchronously before the browser paints, and h/render! renders inside flushSync, so the door returns with the seeded markup on the page.

Which verb an adopting root takes

A hydrating root SCOPEs, and the reason is SHAPE rather than state. frame-root's ENSURE is commit-owned, so its first render emits no descendant subtree and the children arrive on a second pass. An adopting root has to render the server's element shape on its FIRST pass — that is what hydrateRoot matches against, useId positions included — so a frame-root here would hand React an empty tree where the server's markup is. frame-provider renders its children immediately, so the shapes agree.

Neither hydration door creates the frame. ssr/hydrate! DISPATCHES :rf/hydrate at a frame that must already exist, so the frame is made first, the payload installed second, the DOM adopted third:

(rf/make-frame {:id :app/main})                       ;; 1. frame
(ssr/hydrate! {:frame :app/main})                     ;; 2. state
(h/render! app-root                                   ;; 3. DOM
  [h/frame-provider {:frame :app/main} [views/page {}]]
  node
  {:hydrate? true})

A boot that never made the frame is caught rather than silent: the :rf/hydrate dispatch into an absent frame is a no-op, and frame-provider then fails loud on it. What that does NOT catch is a frame that is live but never hydrated — liveness is the whole of the check, and an unhydrated frame passes it.

A hydrating root needs the same :identifier-prefix its server render used. React numbers useId per root and prefixes it with this option, so a hydrating root given a different prefix — or none, where the server had one — resolves every id in the tree differently from the bytes it is adopting. server/render takes the same key.

Adoption is also concurrent. A hydrating FIRST render performs no flushSync, so the DOM on the line after the call is still the server's; a test waits for the adoption window to close rather than for a flush. Every later render through the same handle is an ordinary synchronous update.

SSR and hydration teaches the whole route.

Markup

Name Signature What it is
h/error-boundary [h/error-boundary opts child …] The runtime's own error boundary, and a legal Hiccup head. opts carries :fallback (Hiccup, or (fn [error] hiccup)), :reset-key (any value, compared with =; a change clears the caught failure and re-mounts the children) and :on-error (an intent vector dispatched with the error appended, or a plain function called with it), and refuses any other key. Taught in Errors.
h/portal [h/portal opts child …] Hiccup into createPortal. :target is the DOM container; :fallback is markup for the portal's own tree position while the page is server-rendered. Events bubble through the React tree, a changed :target is a remount, and a :target that is not a DOM container is React's own error at the client render.
h/route-link (h/route-link props child …) One real anchor, as data — :href and the click decision taken whole from routing's late-bound seams. Called, not written as a head: it is a plain function, mints no boundary and adds no hook. Taught in Routing and navigation.
h/as-element (h/as-element hiccup) The one explicit Hiccup-to-ReactNode conversion, under the frame of the boundary currently rendering. It exists because a :render callback's return crosses unconverted; it is also the answer past a [:>] escape and past the native fence. Explicit rather than inferred: nothing in the codec asks whether a value looks like Hiccup.
h/as-component (h/as-component view) The outward bridge — a real React component for a Hiccup head, so a native parent, a UIx component or plain JavaScript can mount a minted view under the frame it is already in. Declared once at top level, beside the view.

Local state

(h/reg-state concern opts?) registers a per-instance state concern and answers concern. It mints one parametric subscription, one setter event and the shared clear event, under [:ui concern instance-key]:

(def open? (h/reg-state ::open? {:default false}))

(h/sub [::open? panel-id])                  ;; read
[:button {:on-click [::open? panel-id true]}]   ;; write
[:button {:on-click [::h/clear ::open? panel-id]}]  ;; back to the default

concern must be a namespace-qualified keyword — it is a sub id, an event id and an app-db key at once. opts carries :default and nothing else; an unknown option is refused rather than ignored. Re-registering with the same :default is a refresh; re-registering with a different one refuses. Taught in Ephemeral state.

The marker keywords

The door exports no keyword, and none needs exporting: they already read :re-frame.fresco/…, so aliasing this namespace as h resolves the auto-resolved spelling with no keyword changing value.

Keyword Where it goes What it does
::h/value inside an intent vector at an on-* prop substitutes the event target's current value at dispatch time
::h/checked the same substitutes the target's checked flag
::h/prevent as an intent head, wrapping another intent calls .preventDefault before dispatching the intent it wraps
::h/revision an attribute on a controlled field a change re-baselines the field to the model without remounting it
::h/clear as an event head removes an h/reg-state instance, back to the concern's default

Substitution is top level only: [:todo/edit id ::h/value] reads, and a marker nested inside a map or a sub-vector does not.

The presence override markers are not in this roster: they are the motion module's own vocabulary — ::motion/mounting / ::motion/unmounting — documented under re-frame.fresco.motion.

re-frame.fresco.forms

The optional forms module. One view and its protocol; nothing new underneath it.

(ns my.app
  (:require [re-frame.fresco :as h]
            [re-frame.fresco.forms :as forms]))

[forms/buffered-field {:control     [:todo id :title]
                       :value       (h/sub [:todo/title id])
                       ::h/revision (h/sub [:todo/title-revision id])
                       :on-commit   [:todo/title-committed id]
                       :on-cancel   [:todo/edit-cancelled id]}]

forms/drafts       ;; the h/reg-state concern every draft lives under

The field's protocol is three ordinary events in the module's own keyword namespace — ::edit on :on-input, ::commit on Enter and blur alike, ::cancel on Escape — written into the field's intents rather than exported as names. A test that drives the field by hand names them through the kit's re-frame.fresco.test.forms (tf/edit-id, tf/commit-id, tf/cancel-id).

forms/buffered-field is a controlled <input> with an app-db draft in front of the committed value. :control is an opaque address, not a path; :value is the committed value; ::h/revision is the caller's generation counter and is what a rejection is made of. :value, :on-commit, :on-cancel, :key and ::h/revision are the field's own, and every other prop reaches the <input> unchanged with :type defaulting to "text".

The three id vars are public as ids and not as doors: they are written into the field's own intents, so they are visible in the rendered tree, in Xray and in a captured intent, and a test asserting on a tree has to be able to spell them.

forms/drafts is the address an application reaches for when it has to end a durable draft — route entry, an explicit cancel, a successful save reply:

(dispatch [::h/clear forms/drafts [:todo 7 :title]])

Taught in Forms.

re-frame.fresco.overlay

The optional overlay module. Two heads, and the module owns exactly one thing about an overlay: the imperative call that enters the browser's top layer.

(ns my.app
  (:require [re-frame.fresco.overlay :as overlay]))

[overlay/modal   {:open? o :on-dismiss d :label l :light-dismiss? b} child ]
[overlay/popover {:open? o :on-dismiss d :label l :anchor id :placement p} child ]
Option Which head Meaning
:open? both whether the overlay exists at all. False renders nothing — no element, no listener, no anchor claim
:on-dismiss both the intent the platform's own dismissal dispatches. Without one, the platform is told not to dismiss at all
:label both the accessible name, as aria-label
:anchor popover the DOM id of the trigger to position against. An :anchor naming no element refuses with :rf.error/fresco-overlay-anchor-missing; omitting it stays legal and silent
:placement popover a compass word, which becomes a CSS position-area against the anchor. The word-to-position-area table is re-frame.fresco.impl.overlay/position-areas, public there so a witness can drive it rather than restate it — and a :placement outside it is not refused: it is passed through as a literal position-area value
:light-dismiss? modal whether a backdrop click dismisses. Default false

Every other key is an ordinary attribute and reaches the element unrenamed. Initial focus is tree order — the platform's own dialog-focusing steps take the first focusable control, so order the controls rather than reaching for an autofocus attribute, neither spelling of which reaches the platform here. Taught in Overlays and focus.

re-frame.fresco.motion

The optional motion module. One head, and the module owns exactly one thing about an animation: retention.

(ns my.app
  (:require [re-frame.fresco.motion :as motion]))

[motion/presence {:timeout-ms 300} keyed-child ]

motion/presence retains exiting keyed children for :timeout-ms, merging each child's own ::motion/mounting / ::motion/unmounting override map into it while it is in that phase — into an element's attributes, or into a view's props, the same map either way. It inserts no wrapper node and stamps no data-*. :timeout-ms is mandatory: it is the retention length and the hard terminal bound at once, so a child leaves on time whether or not any CSS ran.

There is no easing, spring or keyframe API, no timeline, no transitionend subscription and no gesture state. Taught in Motion and presence.

re-frame.fresco.native

The two hooks a React island uses to reach Fresco state. An island is a raw React or UIx component mounted through h/defhost; this namespace adds only what React cannot supply — a read that joins Fresco's own cell table, and the frame's incarnation-pinned operations. Nothing else lives here.

(ns my.app
  (:require [re-frame.fresco.native :as n]))

;; real React hooks — top level of the component, unconditional
(n/use-frame)
(n/use-sub query-v)
Name What it is
n/use-frame rf/capture-frame's bundle — {:frame :dispatch :dispatch-sync :subscribe} — for the frame this island is mounted in. Reference-stable, and pinned to the frame's incarnation rather than to its keyword.
n/use-sub Reads one subscription from a React component. The island counterpart to h/sub, and a real hook: two calls in one component are two subscriptions, where a body's several h/sub reads are one.

Both hooks refuse with :rf.error/no-frame-context when rendered outside every frame. Taught in Islands.

re-frame.fresco.server

The optional server module: one request in, one document out, rendered by the Fresco runtime itself under Node's react-dom/server. Three public names.

(ns app.server
  (:require [re-frame.fresco.server :as server]))

(server/render opts)
(server/payload-script payload-edn)
(server/document {:html h :payload-script s :app-element-id id
                  :script-src src :title t})

server/render renders one request and answers:

{:frame-id       :the-per-request-gensym
 :html           "the app root's INNER markup"
 :payload        {}      ;; the :rf/hydration-payload map
 :payload-edn    "that map, pr-str'd"
 :payload-script "<script …>"
 :document       "the whole page"}

Its opts:

Key Meaning
:hiccup required. The root Hiccup form
:payload required. re-frame.ssr.payload-policy's fail-closed contract verbatim — a non-empty allowlist vector of top-level app-db keys, or :rf.ssr.payload/whole-app-db as an explicit opt-in. This module hands the value straight to the framework's validator and adds no check of its own, so an absent policy raises :rf.error/ssr-missing-payload-policy from there
:snapshot a map seeded whole through :rf/set-db
:initial-events ordinary events, run after the snapshot
:client-frame-id the stable wire :rf/frame-id, or absent to omit the key
:identifier-prefix React's identifierPrefix. The hydrating root must be handed the same string
:app-element-id, :script-src, :title the document envelope's
:frame-opts merged under the id and the setup vector, for a request needing :images, :url-strategy or :fx-overrides. :id and :initial-events are this module's and cannot be overridden
:version, :schema-digest passed to the payload builder

The other two are composition helpers, public by decision rather than by omission — each passes the same test, that an external host does something with it which server/render's returned values alone cannot do. server/payload-script re-wraps a payload a host has mutated, keeping the tag byte-identical to the framework's own and the escaping correct. server/document rebuilds the envelope for a host post-processing :html.

The determinism check — render the same request twice and compare the documents byte-for-byte, answering {:first :second :identical? :differs-at} — is a test's business rather than a running host's, so it lives in the test kit as re-frame.fresco.test.server/render-twice. Its own namespace, because it is the one kit door that requires the server module and react-dom/server.

The frame id in :frame-id is a per-request gensym, destroyed before render returns. It is there to be asserted on, not used. Taught in SSR and hydration.

re-frame.fresco.tool

The tool-tier reader door — the four reads Xray and an AI pair consume, and the only door either of them has. Every one of them answers nil in a production build.

(ns my.tooling
  (:require [re-frame.fresco.tool :as tool]))

(tool/read-mounted-boundaries)
(tool/read-read-attribution)
(tool/read-intents)
(tool/explain-render)
Name The question it answers
tool/read-mounted-boundaries which boundaries hold live read edges right now, over which frames
tool/read-read-attribution which boundaries read each subscription — the reverse edge, exactly. The one read here that is exact without qualification
tool/read-intents what was dispatched inside Spec 009's retained window, oldest first
tool/explain-render which reads changed, and which boundaries hold them

Each answers an envelope carrying :schema, :producer, :read, :complete? and :loss. Read the envelope before the roster: tool/explain-render is structurally incomplete and says so, because the commit seam carries no cascade identity, so it reports {:reason :uncorrelated} and offers :candidates as leads. In a development build every mounted row, reader and explanation also carries :views — the declared views currently mounted on that boundary's edge set, each with the source coordinate defview captured — or :unknown for a body minted without a name. A view that unmounts leaves the list, and a render React discarded never joins it. Taught in Diagnostics.

re-frame.fresco.evidence

The evidence vocabulary and the one door every envelope goes through. A consumer reads this namespace; a producer calls it.

(ns my.tooling
  (:require [re-frame.fresco.evidence :as evidence]))

;; identity and vocabulary
evidence/schema  evidence/producer  evidence/reads
evidence/unknown evidence/loss-reasons

;; the door
(evidence/envelope read complete? loss body)
Name What it is
evidence/schema the schema version every envelope carries. A consumer validates it first and refuses what it does not recognise. There is no compatibility adapter and no acceptance path for a superseded shape
evidence/producer which substrate produced the envelope. The schema is adapter-neutral, so this is carried rather than inferred
evidence/reads the four read operations, stamped on every envelope as :read
evidence/unknown the explicit value for a fact a read does not hold, and the :dropped count for a loss it cannot size. Unknown is never encoded as an empty collection
evidence/loss-reasons why a read could not carry something: :cap, :opaque, :host-opaque, :uncorrelated. Each names a different remedy
evidence/envelope answers body stamped with the five envelope fields for read, or throws naming every problem: a read outside the vocabulary, a loss with a foreign reason or no sizeable :dropped, or :complete? true beside a loss. The only door — there is no lenient variant

re-frame.fresco.test — the L0/L1 test kit

Ships from test_kit/src — in the jar, but off the artifact's :paths, so a :local/root consumer names that root explicitly and no shipping namespace ever requires it. Twenty-three names, and none of them mounts anything: this tier reads values and runs bodies.

(ns my.app-test
  (:require [re-frame.fresco.test :as ht]))

;; L0 — what a value IS
(ht/boundary? v)   (ht/host? v)   (ht/callback? v)
(ht/view-name v)   (ht/host-policy v)
ht/ladder

;; L1 — what the codec does with one form
(ht/element-props form)
(ht/controlled? form)
(ht/revision form)
(ht/materialize intent-v {:value v :checked c})
(ht/canonical-dom node)
(ht/capture-intents frame-kw f)
(ht/fire! frame-kw form prop event)

;; L2 — the structural tree
(ht/tree form)
(ht/tree form {:subs fixtures})
ht/tree-version
(ht/find-all tree pred)   (ht/find tree pred)
(ht/attrs node)           (ht/text node)      (ht/intents tree)
(ht/role node)            (ht/accessible-name tree node)
(ht/unnamed-controls tree)
Name What it answers
ht/boundary? is v a minted boundary — the value h/defview defines? False for the plain function its body is
ht/host? is v a minted crossing — the value h/defhost defines? False for the foreign component it named
ht/callback? is v the one callback form? False for an identically written plain fn
ht/view-name the "<ns>/<sym>" name a minted boundary or host carries; nil for anything unminted
ht/host-policy the :server policy a crossing was declared with. Refuses anything that is not a minted crossing rather than answering nil
ht/ladder the testing ladder as data — five rows, each with :tier, :proves, :mechanism and :here?
ht/element-props the emitted prop slots of one native form, as a map of slot name to value. A lowered handler records as {:rf.ui/opaque :fn}
ht/controlled? does the codec install the controlled shadow for this form? The runtime's own decision, not a re-derivation
ht/revision the ::h/revision value a native form carries, read pre-merge-conversion where the codec reads it
ht/materialize the marker law as a pure function: what an intent materializes to, given what the event target carried
ht/canonical-dom a DOM subtree serialised with every element's attribute names sorted — the fairness gate two renderings are compared through
ht/capture-intents {:value … :intents […]} for f, captured at the substrate's event-observation registry. Other frames' events are ignored
ht/fire! lowers one handler position and invokes it with an event described as data; answers {:intents […] :prevented? bool}
ht/tree runs one hook-free body under injected read fixtures and answers its versioned semantic tree. opts' roster is closed at :subs, so a misspelled key is refused rather than ignored
ht/tree-version the structural-tree schema version ht/tree stamps on its root
ht/find-all / ht/find every node, or the first node, for which pred is truthy, in document order. nil threads through a missed match
ht/attrs the merged attribute projection of a node — :attrs with :events for an element, the passed props for a boundary call, {} for a fragment
ht/text the concatenation of a node's text descendants. Over a boundary node this is what the call site wrote, never the child's own rendering
ht/intents every event vector the tree carries, in document order — what a rendering offers to dispatch, where ht/capture-intents says what it did
ht/role the ARIA role of a node — written, else implicit — as a keyword, or nil. Total over the node set
ht/accessible-name the accessible name a node carries within a tree. The tree is not decoration: a name is a fact about the markup. A node outside the tree is a refusal, not a nil
ht/unnamed-controls every operable node with no accessible name, in document order. It does not exempt a control inside an aria-hidden subtree

ht/tree is not a renderer: no React element is created, no hook runs, and nothing is mounted or painted. It refuses a h/defhost crossing, a raw React element and an unforced delay anywhere in the tree, and it refuses a read no fixture answers. Taught in Testing.

re-frame.fresco.test.mounted — the L3 test kit

The mounted tier: a real React root, a real frame, a real DOM. Fifteen names.

(ns my.app-test
  (:require [re-frame.fresco.test.mounted :as hm]))

(hm/mount! form)
(hm/mount! form {:initial-events es :container node :clock true})
(hm/hydrate! form opts)
(hm/hydrate! form opts budget-ms)

(hm/rerender! handle form)
(hm/dispatch-and-settle! handle event)
(hm/settle! handle)
(hm/settle-until! handle pred)
(hm/settle-until! handle pred {:label "what is being waited for"})
(hm/advance-clock! handle ms)

(hm/unmount! handle)
(hm/residue handle)
(hm/assert-clean! handle)

(hm/census)
(hm/bodies-run f)
hm/counted
hm/this-frame
(hm/shadow! opts)
Name What it does
hm/mount! mounts form on a fresh React root under a frame of this mount's own, and answers the handle. :initial-events seeds that frame in core's own vocabulary; :container renders into an element you already have; :clock true installs a virtual clock before anything else this call does
hm/hydrate! mounts by adopting server bytes, and answers a promise of the handle, resolved once this root's adoption window has shut. :html supplies the bytes, or :container a container you already filled. The default budget is 3000 ms
hm/rerender! renders form into the existing root — same root, same frame, same DOM nodes wherever React can keep them
hm/dispatch-and-settle! dispatches into this mount's frame through the runtime's own synchronous door, drains it, commits the echo, and answers the handle
hm/settle! lets everything React has already scheduled commit. The empty flushSync, with no work of its own — it cannot reach work that is merely enqueued
hm/settle-until! waits for pred, settles once, and answers a promise of the same handle. The door for work a router has enqueued rather than scheduled
hm/advance-clock! moves this mount's virtual clock forward and runs what falls due. Throws without {:clock true}, because an advance with no clock under it would assert nothing
hm/unmount! tears the root down and touches nothing the runtime holds, which is what lets hm/assert-clean! see what leaked. The container stays in the document, emptied
hm/residue a promise of the report — :clean?, :leaked, :baseline, :now and the frame — asserting nothing and resetting nothing
hm/assert-clean! waits for quiescence, compares against this mount's baseline, reports through cljs.test/do-report, and only then resets. It never throws, so the promise never rejects
hm/census everything the facade counts as one map: the five residue counters plus :frames, a set of live frame ids, so a delta names the frame that outlived the mount
hm/bodies-run how many boundary bodies ran while f did — what a change cost, where the census says what the page retains
hm/counted the five residue counters, in report order, as data
hm/this-frame the stand-in each mount's own frame keyword normalises to in a shadow report, so a difference is never merely the two mounts being two mounts
hm/shadow! mounts a reference and a candidate against isolated copies of one seeded frame, drives both with one script, and compares canonical DOM and the intent stream at every checkpoint. opts carries :reference, :candidate, :initial-events and :script — a closed roster, so a retired spelling is refused rather than ignored — and a script step is {:click selector} or {:type [selector text]}, in order. Answers {:status :green :checkpoints n}, or a red naming the checkpoint

Green from hm/shadow! means the two implementations were indistinguishable for the flows in the script, and proves nothing about a path the script did not walk.

Names that changed

Fresco is pre-alpha and some of its doors have been renamed since they were first written down. There is no alias and no deprecation path for any of them: an old spelling is gone rather than deprecated, so what you get is a compile error rather than a warning. This is what to type instead.

What you may have written What it is today When, and on whose authority
hfn, taught as h/fn h/event Ruled by the project operator on 2026-08-11 and swept through code and guide alike on 2026-08-15. event is the word this project already reserves for turn the invoker's arguments into one event vector, or nil, while handler names imperative work whose return is ignored — so handler would have been a false friend to anyone arriving from another adapter
h/root!, taking the frame keyword positionally h/mount!, over a config map — (node config view); then RETIRED, along with h/hydrate!, into the one h/client-root / h/render! / h/unmount! grammar (rf2-kuky.59) Named by the same 2026-08-11 ruling, but it could not be carried out as a rename: the config map this guide teaches carries :initial-events, and the door underneath implemented no such option. It landed on 2026-08-15 as the contract rather than the spelling, which is why it arrived after the sweep that renamed the callback form. The second supersession collapsed four spellings of one React-level job into one handle with a first-call mode, so Fresco spells the root lifecycle the way every other React view adapter does
h/hydrate-root! h/hydrate!; then RETIRED into h/render!'s {:hydrate? true} first-call mode (rf2-kuky.59) The same ruling. That half was a true rename and landed first, which is why the two doors changed on different days; the second supersession made hydration a MODE rather than a verb, because a root that adopts and a root that creates differ only in which constructor the first render calls
hm/render!, on the mounted test kit hm/rerender! Ruled 2026-08-11, swept 2026-08-15. render! would have collided with the product facade's own h/render!, and a test that reads render! should not have to know which of the two it is looking at
ht/render, with a {:reads …} fixture ht/tree, with a {:subs …} fixture Applied on 2026-08-11. L2 answers a data tree and never DOM — the kit's own docstring says it is not a renderer — so render both misdescribed the door and collided with two others
:ssr, on a defhost declaration :server Applied without waiting on the naming sitting, because by then the two spellings had diverged code-against-code inside one shipped artefact, which is a defect rather than an open question of taste. :ssr names the technique where :server names the side that renders, which is what the two values distinguish. A declaration still carrying :ssr now raises :rf.error/fresco-bad-host-declaration
server/fresh-frame-id, server/setup-events Neither is public. The server module's whole public surface is server/render, server/payload-script and server/document Operator override of 2026-08-15, argued name by name against what an external host can actually do with each. Nothing an application writes calls either of the two: server/render mints its own frame id and refuses to have it overridden, and the event setup is a short fold over options server/render already accepts directly
server/render-twice re-frame.fresco.test.server/render-twice Moved 2026-08-30 (rf2-6c12m.15). A determinism probe is a test's instrument, not a running host's door; the kit namespace is its own so that react-dom/server is required only by the one test that asks

One rule explains most of what looks inconsistent above.

A refusal id is not a spelling, and never follows one. An id is frozen for the life of the refusal and is never reused after retirement, because a consumer's stored errors and an error monitor's grouping rule both outlive the code. So :rf.error/fresco-test-bad-reads still carries the retired word reads and always will — it names the refusal, not the option the refusal was about. The message beside it names the current spelling.

Naming questions are consolidated and settled by the project operator in a working design record that is not published, so the table above is the published account of what has been decided: a row appears here when the change has landed in the shipped package, not when it is proposed.

How this page is kept honest

One gate reads the shipped source rather than this page, which is what makes a mismatch between it and an entry here a real finding rather than a matter of opinion: implementation/fresco/scripts/check_guide_samples.py resolves every alias/verb a fenced block in the guide names against the source that defines it, and pins each block's text to a digest, on every pull request.

Two more gates stood beside it until 2026-08-30 — a facade-inventory diff and a public-name census, each policing a programme ledger — and were retired with those ledgers (rf2-6c12m.8). What the census last measured, on 2026-08-15, was 103 public names across ten namespaces with none unrostered; that figure and this page's roster differed by exactly one name, forms/buffered-field, which is minted by h/defview rather than a bare def and so was invisible to a reader of def* heads. The name ships and Forms teaches it, so the shipped public surface was 104 names on that day, and what this page documents is the source as it stands.