Skip to content

re-frame2 — API

Type: Reference Reference for the CLJS implementation's API: signatures, status, cross-references. No rationale — per-Spec docs own the why. Pattern-level contracts live in 000-Vision §The pattern and the per-Spec docs. :fx-overrides asymmetry: id-valued at the pattern level; CLJS reference also accepts fn values — see 002 §:fx-overrides.

Conventions

  • Status — exactly one base value, optionally combined with one or more parenthesised qualifiers. The closed set below is the same vocabulary the generated api-manifest.edn curates (its :status field) — the two MUST agree:
  • Base values:
    • v1 (ships in v1).
    • v1 (preserved) (exists in current re-frame; preserved unchanged).
    • v1 (preserved + extended) (exists today; v1 adds new arity or behaviour).
    • EP-NNNN (a surface introduced or reshaped by a named pre-alpha EP, shipping in v1 — e.g. reg-event (EP-0018), reg-interceptor (EP-0022); the canonical lineage is the named EP, cited in the row's Notes).
    • post-v1 lib (design spec in v1 Specs but ships in a post-v1 library).
    • post-v1 (planned, rf2-<id>) (specced normatively but not yet shipped; the impl is tracked by the named bead — per the Projection-maintenance rule below).
  • Qualifiers (parenthesised, combinable): dev-only (elided in production builds — the macro emit site or runtime body, depending on the API); changed, EP-NNNN (a preserved v1 surface a named EP changed — e.g. v1 (changed, EP-0017)); optional capability (ships only when the owning optional artefact is on the classpath, optionally narrowed optional capability, dev/test); internal lowering only (an EP surface retained as a framework-internal lowering seam, not a public authoring form — e.g. EP-0022 (internal lowering only)).
  • Examples: v1, v1 (preserved), v1 (dev-only), v1 (preserved, dev-only), v1 (changed, EP-0017), EP-0018, EP-0022 (internal lowering only), v1 (optional capability), post-v1 lib, post-v1 (planned, rf2-<id>).
  • The re-frame.alpha namespace is dissolved — no APIs in this reference live outside re-frame.core (with the documented per-namespace exceptions: re-frame.test-support and re-frame.test-helpers).
  • Macro/Fn: marked M (macro) or Fn.
  • Spec column — names exactly the canonical owning Spec (the per-Spec doc whose contract this API implements). Migration rules and other cross-references are NOT in the Spec column; they appear in the Notes column when relevant.
  • Configure keys — runtime configuration is uniformly via (rf/configure! {<key> <opts>, …}), a single nested map. Every <key> is enumerated in §Configure keys below; per-area tables call out which keys their APIs read but do not redefine the key's vocabulary.
  • Per-artefact public namespaces. The core surfaces live in re-frame.core. Per-feature artefacts ship their own public namespace; consumers :require the namespace directly (with the documented exceptions of the epoch + SSR-query surfaces, which late-bind re-exports through re-frame.core). Front-porch boundary. re-frame.core is the small app-developer front porch — registration, dispatch, subscribe, the frame basics, interceptors, lifecycle, configure, and the core trace / egress / registrar-query / app-realm surfaces. The optional-feature registration MACROS stay on the façade (reg-route, reg-flow, reg-app-schema / reg-app-schemas, reg-machine / defmachine, reg-resource / reg-mutation / reg-resource-scope, reg-error-projector, reg-head, reg-http-interceptor) because they capture call-site source-coords and have no owned-namespace macro form — registration stays central. But the optional features' non-registration query / introspection / lifecycle helpers are NOT re-exported from re-frame.core; reach them through their owning namespace: re-frame.schemas (app-schema-at / app-schema-meta-at / app-schemas / app-schemas-digest / set-schema-validator! / -explainer! / -printer! / set-schema-fns!), re-frame.machines (reg-machine* / make-machine-handler / machine-transition / machines / machine-meta / machine-by-system-id), re-frame.routing (match-url / route-url / current-url / clear-route), re-frame.flows (clear-flow). The epoch + SSR-query re-exports remain on the façade as documented late-bind exceptions (rows below):
Namespace Artefact Surfaces
re-frame.core core the registration / dispatch / subscribe / interceptor / lifecycle / configure surfaces; late-binds re-exports for two artefacts: (a) the re-frame.epoch surface (epoch-history, restore-epoch!, replace-frame-state!, projected-record, projected-history) — the epoch listener stream is reached through the core-native (register-listener! :epoch …) verb, not a per-channel re-export; (b) the re-frame.ssr query surface (render-to-string, render-tree-hash, project-error, render-head, active-head, head-model->html, head-snapshot). The streaming-render-shell / streaming-render-continuation / streaming-build-final-payload triple is not re-exported — the streaming surface is host-adapter territory and the SSR-aware host (re-frame.ssr.ring / equivalents) requires [re-frame.ssr :as ssr] directly. Re-exports activate when the named artefact is on the classpath; absent artefacts surface :rf.error/<feature>-artefact-missing errors.
re-frame.test-support core assert-path-equals, poll-until, fixture machinery (per §Testing). Runtime-state axis — registrar, frames, app-db, drain. assert-path-equals mirrors the :rf.assert/path-equals Story event. View-tree assertions live in the sibling re-frame.test-helpers.
re-frame.test-helpers core View-assertion helpers — hiccup-walk (find-by-testid / find-by-attr family, text-content, extract-handler, invoke-handler), the testid authoring helper, and the expand-tree walker (per §Testing — View-assertion helpers). View-tree axis — hiccup data, testids, attached handlers. Runtime-state assertions live in the sibling re-frame.test-support.
re-frame.ssr day8/re-frame2-ssr render-to-string, emit-ui-tree, render-tree-hash, streaming-render-*, render-head, active-head, head-model->html, head-snapshot, project-error (per §SSR).
re-frame.ssr.ring day8/re-frame2-ssr-ring the Ring host-adapter (default-html-shell, streaming-prefix/suffix, trusted-shell hooks per Spec 011).
re-frame.schemas day8/re-frame2-schemas app-schemas, app-schema-at, app-schema-meta-at, app-schemas-digest, set-schema-validator!/-explainer!/-printer!/set-schema-fns!, validate-at-boundary-interceptor (per §Schemas).
re-frame.http day8/re-frame2-http the verb helpers get / post / put / delete / patch / head / options (per §HTTP requests).
re-frame.machines day8/re-frame2-machines (post-v1 scaffolding) reg-machine, make-machine-handler, machine-transition, machines, machine-meta, the :rf.machine/spawn / :rf.machine/destroy fx (per §Machines).
re-frame.epoch day8/re-frame2-epoch epoch-history, restore-epoch!, replace-frame-state!, (rf/configure! {:epoch-history ...}), and the epoch listener stream reached via (rf/register-listener! :epoch id f) / (rf/unregister-listener! :epoch id). Re-exported / late-bound through re-frame.core via late-bind hooks — (:require [re-frame.epoch]) at boot before consuming the surfaces through re-frame.core (per Tool-Pair §Time-travel — Artefact home).
re-frame.adapter.uix day8/re-frame2-uix UIx-specific surfaces (per §UIx adapter).
  • Projection-maintenance rule. This doc is a non-canonical projection — the canonical contract lives in the per-Spec docs cited in each row's Spec column. The projection MUST stay in sync with shipped artefacts. Every row carries: owner (Spec column) — the canonical spec doc; artefact / namespace — where the public-var lives (table above); public-var statusv1 / v1 (preserved) / post-v1 lib / post-v1 (planned, rf2-<id>) for surfaces specced normatively but not yet shipped (the spec contract holds; the impl is tracked by the named bead); verification pointer — the conformance fixture, the per-artefact test, or the AI-Audit row that asserts the row holds. Rows that document a surface neither shipped nor on a tracking bead MUST be cut from this projection — the design's normative claim then lives only in the owner spec.

Tier taxonomy

Every public-surface row in this document is either supported — a documented API that downstream apps and tools may rely on, in the status the row's Status column gives — or, for the one implementation tier, public-for-technical-reasons only (exported but explicitly not a surface to depend on; see below). Supported is not the same as front-porch: a system that ships restore-epoch!, projected-history, adapter hooks, and replace-frame-state! has a large public surface, but only a small slice of it is what a new app developer should ever reach for. The Tier column on every table below records that slice, using a closed vocabulary of eight values:

Tier Meaning Who reaches for it
front-porch The tight set a new app developer needs to build a working app — register / dispatch / subscribe, the frame basics, the everyday reads. Loaded by default by the Guide and the skills. Every app author, day one.
advanced Power-user surfaces that solve real problems but are not first-reach — epoch-listener registration, trace projection, flows internals, the *-twin fn forms, the lower-level lifecycle (install-adapter!, init-platform). Opt-in. Experienced authors, library authors, niche cases.
tooling Dev / inspection surfaces consumed by Story, Xray, the pair-MCP servers, and other Spec 009 / Tool-Pair tools — trace listeners, the registrar query API, off-box-egress projections, the epoch query surface, the Story run-result read accessors. Not for application logic. Tool and instrumentation authors.
adapter The substrate-adapter hooks — the per-substrate adapter Var, the hooks and set-*! seams a substrate adapter implements or installs. Adapter authors (Reagent / UIx / custom).
testing Test-only helpers — fixture machinery, assertion helpers, view-tree walkers, the HTTP-stub surfaces. Elided or simply absent from production paths. Test authors.
internal-public A supported host/tool embedding point: stable, exported, and safe to call, but not an application surface — provided for a specific tool/host integration, not for app logic. The Xray mount-<panel>! / mount-shell! family is the canonical (and now sole) instance — a host that builds its own Xray chrome may call them; an app never should. Narrower than it looks: it is not the bucket for every exported helper (those are implementation); it is the small set a host may legitimately embed against. Specific host/tool integrations only.
implementation Public-for-technical-reasons only — exported because a sibling namespace, the tool chrome, or a test must reach it across a namespace boundary (ClojureScript has no cheap cross-namespace-private seam), but it is NOT a supported surface: an app or tool MUST NOT depend on it, and it may change or vanish without notice. The Story run-variant / migration vocabulary, the per-feature artefact lifecycle/validation/cache helpers (re-frame.schemas, re-frame.ssr, re-frame.routing, re-frame.machines, re-frame.flows), the Ring host-adapter internals, and the Xray panel-leaf Panel reg-views all live here. Nobody downstream — internal plumbing that merely happens to be public.
deprecated On the way out; a replacement exists. Retained only long enough for callers to migrate. (Pre-alpha currently carries none — removed surfaces live in §Removed / not shipped, not here.) Nobody new — migrate off.

The Guide and the skills load FRONT-PORCH only by default. A new app developer reading the Guide, or an AI agent operating through the skills, sees the front-porch tier and nothing else unless they opt in. Advanced, tooling, adapter, testing, and internal-public surfaces are opt-in — reached by explicitly pulling in the relevant chapter, the relevant artefact, or the relevant tool. The implementation tier is never reached for: it is not opt-in, it is off-limits — exported only for technical reasons (see the table) and excluded from the supported surface entirely. The acceptance bar for this taxonomy: a new app developer can read the one-page front-porch list (the union of all front-porch rows below) and never trip over trace projection, epoch-listener registration, adapter hooks, or Xray internals.

Tier vs Status. Tier and Status are orthogonal axes. Status records shipping lineage (v1 / v1 (preserved) / post-v1 lib / …). Tier records who-reaches-for-it. A surface can be v1 and tooling (register-listener!), or post-v1 lib and front-porch (the Story run verb is post-v1 lib/tooling; a hypothetical post-v1 ergonomic core helper would be post-v1 lib/front-porch). Read the two columns together.

The front-porch / back-room split is the first instance, generalised. The multi-frame surface (§View ergonomics, per 002 §The multi-frame surface) was already organised as a front-porch / back-room split — dispatch / subscribe / with-frame on the porch, the {:frame …} override in the back room (and capture-frame, the ONE public HOLD primitive, alongside it). The Tier column generalises that split across the whole API: front-porch is the porch; advanced / tooling / adapter / testing / internal-public are the back rooms (all still supported); implementation is below the floorboards — exported but off the supported surface entirely.

Closed vocabulary, restrictive-by-default. The eight values above are the complete set — no ninth tier is added without an explicit governance decision. When a surface is genuinely ambiguous between two tiers, pick the more restrictive one (advanced over front-porch; internal-public over advanced; implementation over internal-public when the var is not actually a host/tool integration point but merely an exported helper) and note the call in the row's Notes. The downstream manifest consumes Tier as a first-class field, so the value must come from this closed set. The internal-public / implementation boundary: ask "is this a surface a host or tool legitimately embeds against?" — if yes, internal-public (the Xray mount family); if it is exported only so the framework's own namespaces / tests can reach it across a boundary, implementation. The split keeps the supported surface honest.

What the Tier column covers. Tier is a property of a public var (a fn / macro / Var). The tables that enumerate keyword-addressed registrations — standard events (:rf.route/navigate, :rf/hydrate, …), standard subs (:rf/route, :rf/response, …), standard fx (:rf.nav/push-url, :rf.http/managed, …), standard cofx, reserved fx-ids, the :fx-entry catalogue, the spec-internal schemas, the configure keys, and the error/trace-event catalogues — are not vars and carry no Tier column. They are part of the contract of whichever artefact owns them; their availability follows that artefact's tier (e.g. the :rf.route/* events ship with the advanced routing artefact). The front-porch one-page list is the union of front-porch-tiered var rows.

Tiering of cross-tool surfaces (Story, Xray, pair-MCP)

Story, Xray, and the MCP support namespaces ship their own public-var rows in their own specs (007-Stories.md, tools/xray/spec, Tool-Pair.md); this projection rows only the slices that surface through re-frame.core or the per-feature artefacts. The Tier taxonomy is nonetheless repo-wide and authoritative over those surfaces — the per-tool specs classify against this closed vocabulary rather than inventing local terms. The standing classifications (resolved here, so the per-tool specs reference rather than re-litigate):

  • Story facade — the public execution verbs run / is / explain and the registration macros (reg-story / reg-variant / reg-workspace / reg-tag / reg-decorator / reg-story-panel) are tooling (a Storybook-shaped dev surface, not application logic). The run-result read accessorsrun-result, result-status, result-passed?, run-result-schema, valid-run-result?, explain-run-result — are tooling too: the statement-of-record for reading what a run produced (per story spec 017 §Run result). The run-variant / is-variant / run-plan / is-plan / watch-variant / reset-variant implementation/migration vocabulary is implementation — public-for-technical-reasons only (the chrome and tests reach it across namespaces), explicitly NOT the supported execution surface. The implementation tier is its home — public as a var, off the supported surface. (Absorbs story F-8.)
  • Xray mount-<panel>! family (mount-epoch-panel!, mount-app-db-diff!, mount-trace!, mount-machine-inspector!, mount-routing!, mount-segment-inspector!, the master mount-shell!, …) — internal-public, the canonical (and now sole) instance of that tier. They are exported and stable, but they are a host-embed surface, not an application or even general-tool surface: a host that builds its own Xray chrome may call a mount-<panel>!; an app never should. This resolves the prior "public vs internal-but-stable" question against the closed vocabulary — the answer is internal-public. (Resolves xray M2.)
  • Xray panel-leaf Panel reg-views (day8.re-frame2-xray.panels.<area>/Panel, the Static-mode panel) — implementation. They are exported only so the shell can compose them across namespaces; they are NOT a host-facing single-panel embed surface — a host embeds the full shell via mount-shell! (per 008-Embedding-Contract), never a bare Panel. Demoted off internal-public (now reserved for the supported mount-embed surface) so the leaves do not read as a supported embed API.
  • Xray panel-helper functions (the per-panel render/projection helpers beneath the mount-fns) — tooling where they are a documented panel-author surface, otherwise unrowed-internal. (Absorbs xray H6.)
  • pair-MCP support namespaces — the trace/egress surfaces they consume (elide-wire-value, sensitive?, the registrar query API, the epoch query surface) are tooling, tiered at their re-frame.core / artefact home rows below.

Not-rowed internal carve-outs

The only internal (unrowed, MUST-NOT-depend-on) carve-outs in re-frame.core are two JVM-only macro-helpers re-exposed purely so pre-split tests can reach them:

  • re-frame.core/expand-reg-view^:no-doc; the canonical home is re-frame.core-reg-view-macro/expand-reg-view.
  • re-frame.core/parse-reg-view-args^:no-doc; the canonical home is re-frame.core-reg-view-macro/parse-reg-view-args.

Neither is rowed in this projection. Applications and tools MUST NOT depend on these re-exports; reach the canonical homes directly. These are not a tier — they are below the public surface entirely.


Registration

Return value. Every reg-* row below returns its primary id — the keyword (or path, for reg-app-schema) the caller registered with (its first positional argument). Per Conventions §reg-* return-value convention.

API M/Fn Signature Status Tier Spec Notes
reg-event M (reg-event id ?metadata handler) EP-0018 front-porch 002 The ONE public event form — a two-arg (fn [coeffects event-vec] effect-map) handler, coeffects in, a closed #{:db :rf.db/runtime :fx} effects map out (or nil no-op). The db write is an explicit {:db …} effect; there is no db-only return shape. Coeffects declared uniformly via :rf.cofx/requires. Full-context work is expressed with a registered interceptor (reg-interceptor, referenced by id). Metadata-map superset middle slot carries the reserved :interceptors key (a vector of interceptor refs). There is no reg-event-db/reg-event-fx, and reg-event-ctx is a framework-internal primitive — calling any of the retired names is a hard error (see §The retired event-registration names + the Removed §).
reg-sub M (reg-sub id ?metadata input-fn? computation-fn) v1 (preserved + extended) front-porch 002 The only sub-registration form in v2. Three input-production modes (app-db reader / static :<- / parametric input-fn) — see §reg-sub input-production modes. The optional first fn is a v2 input-fn (query-v → vector-of-query-vectors), NOT a v1 reaction-returning signal fn. :<- sugar preserved.
reg-fx M (reg-fx id ?metadata handler) v1 (preserved + extended) front-porch 002 The handler is binary, context-first: (fn [ctx args]) per 002 §The binary fx-handler signature. ctx is a small map carrying :frame (the active frame id), :event (the originating event vector), and a runtime-internal :envelope; args is the value the event handler placed beside the fx-id in its :fx vector. A unary (fn [args]) handler survives on CLJS only via JS argument-dropping — it is not the blessed contract. :platforms metadata (a set of :server / :client) gates execution by active platform (default universal).
reg-cofx M (reg-cofx id ?metadata supplier) v1 (changed, EP-0017) front-porch 001, 002 Register a coeffect id with a value-returning supplier ((fn [] v) / (fn [arg] v)) and a registration grade — ambient (default) or recordable (:recordable? true, optionally :provided? true). A handler takes delivery by declaring :rf.cofx/requires (the value arrives FLAT under the id; 001 §:rf.cofx/requires). :rf/time-ms is the framework's one provided recordable registration. The ctx→ctx handler shape and inject-cofx are retired (no alias). See §Coeffects.
reg-interceptor M (reg-interceptor id ?metadata descriptor) EP-0022 front-porch 001, 002 The public application-authoring form for an interceptor — a first-class registered program member (registrar kind :interceptor). descriptor is one of {:before f} / {:after f} / {:before f :after f} (static) or {:factory f} (a parameterized family; the factory takes ONE arg and is the mechanism the standard [:rf.interceptor/path …] rides). Event/frame :interceptors chains reference registered interceptors by id (bare keyword) or [id arg], never inline values. Captures source coords; surfaces via handler-meta :interceptor. A migration value carrying an :id is accepted at this boundary only (the id must match). Replaces ->interceptor as the public authoring surface. See 001 §Interceptors + 002 §Registered interceptors and the chain grammar.
make-frame Fn (make-frame opts) / (make-frame opts descriptors) → frame value EP-0024 advanced 002 The ONE frame constructor: builds a live frame and returns the frame value (the lifecycle token — the routing ops dispatch / subscribe / frame-provider accept the value directly OR its id, normalizing a value to its id; destroy-frame! accepts either too but is the one lifecycle exception — the value is an exact-incarnation token that tears down only the incarnation it names (a stale value no-ops against a same-id successor), while the id is address-directed, per §Destroy. API-shrink #1, rf2-csbbwu removed the frame-value->id accessor; there is no need to unwrap the value). opts is a map (required — a non-map opts, including nil, fails loud with :rf.error/make-frame-bad-opts; the all-defaults frame is (make-frame {})). Accepts BOTH image-selection opts AND record-config opts in one call: :images (a non-empty vector; present ⇒ the selected generation; [] is an error:rf.error/make-frame-bad-images; absent ⇒ the default image generation over the whole source store + framework standards, failing loud on a cross-namespace same-[kind id] collision — :rf.error/image-duplicate-id; app isolation is named via :select-ns, not absence) / :id (registers in the one frame registry; a duplicate id is idempotent replacement — config + generation refresh, durable state preserved) / :adapter, plus any record-config key (:initial-events — seed app-db via a leading [:rf/set-db {…}] step — :fx-overrides, :platform, :ssr, :doc, :preset, :tags, …) honoured in the same call. (There is no :capabilities image-selection key — image-declared host capabilities are removed; :capabilities flows through as ordinary record-config.) Re-calling make-frame against the SAME :id with a NEW :images vector IS image hot-reload — the generation swaps while durable state is preserved (rf2-lxwpob folded the dedicated reload-images! verb into this; read the diff via generation-diff). make-frame is the ONE programmatic constructor (tools / tests / SSR / dynamic / image-loaded frames — rf2-h1vqa4 deleted the reg-frame spelling, no alias); the day-1 mount recipe is frame-root (ENSURE). (re-frame.frame/make-anon-frame-record! is the internal no-:id record helper.)
image M (image spec) → normalized, INERT image value — spec carries :id (optional), :select-ns ({:include [globs] :exclude [globs]} provenance selection), :registrations (inline registrar-keyed sections). Pure data — no registrar, no side effect; supplied to make-frame via :images (later image wins; shadows reported on :rf.gen/shadows) EP-0023 advanced 002 The one public image constructor.
generation-diff Fn (generation-diff before after){:added #{[kind id] …} :changed #{…} :removed #{…} :retained #{…}} — a PURE diff between two sealed image generations (read via frame-generation before/after a re-make-frame reload). EP-0023 tooling 002 Replaces the removed reload-images! verb's report — a read over two generation values, not a bespoke verb (rf2-lxwpob).
reg-view M (reg-view sym [args] body+) / (reg-view sym docstring [args] body+) / (reg-view ^{:rf/id :explicit/id} sym [args] body+) v1 front-porch 001, 002 Defn-shape; auto-defs the symbol; auto-derives id from (keyword *ns* sym); auto-injects dispatch / subscribe as lexical bindings; rejects non-defn-shape bodies at macroexpand.
reg-view* Fn (reg-view* id render-fn) / (reg-view* id metadata render-fn) v1 advanced 001, 002 Plain-fn surface beneath reg-view. No auto-def, no auto-inject, no compile check. Use for computed ids, library-generated views, Reagent Form-3 (create-class), or registration without a Var. The * follows Clojure's let/let*, fn/fn* idiom (per Conventions).
reg-machine M (reg-machine machine-id machine-spec) / (reg-machine machine-id opts machine-spec) v1 advanced 005 Optional re-frame.machines artefact. Walks the literal spec form at expansion time; co-locates per-element source on each :guards / :actions entry + a reference-site :source-coords on each :states-tree map node. Top-level call-site coords land on handler-meta. The optional opts metadata map is the canonical Spec 001 MIDDLE slot; it carries an event-vector :schema (the :where :event boundary on the dispatched outer vector) — the machine + event-vector-schema shape.
defmachine M (defmachine name spec) / (defmachine name docstring spec) v1 advanced 005 def-shape for the def-then-register pattern. Walks the literal spec at the definition site, stamping per-element source onto the def'd value so it travels into a later (reg-machine id name). Does not register.
reg-app-schema M (reg-app-schema path schema) / (reg-app-schema path metadata schema) — the schema is the positional value slot; the optional middle metadata map carries the :frame target (e.g. (reg-app-schema [:user] {:frame :session} UserSchema)) (rf2-qm7k83 Part A) v1 advanced 010 Optional re-frame.schemas artefact. Path is the registration id. App-db schemas are path-keyed and live in the schemas artefact's per-frame side-table (app-db schemas are NOT a registrar kind). Every other reg-* is keyword-id-keyed; here the first arg is the path vector (e.g. [:user]) and (app-schema-at [:user]) / (app-schema-meta-at [:user]) look up by the same vector. The path-as-id asymmetry is principled (paths are first-class in get-in / assoc-in grain — schemas-at-paths matches the dataflow grain), not accidental; otherwise reg-app-schema is an ordinary family member — the schema is the positional value slot uniform with the rest of the reg-* family. A non-map middle metadata arg (the 3-slot form) is a loud :rf.error/app-schema-bad-metadata. Per Conventions §reg-* return-value rule.
reg-app-schemas M (reg-app-schemas {path-1 schema-1, path-2 schema-2, ...}) / (reg-app-schemas {…} opts) — bulk plural form for feature-modular apps that register 5–20 paths against the same prefix (per Conventions §Feature-modularity prefix convention). The bulk {path -> schema} shape is retained (the map value IS the schema — no positional ambiguity in a bulk map); the plural form is KEPT for its all-or-nothing atomic-batch contract (every path validated before any mutation — a naive doseq does not preserve it). Each entry routes through the singular reg-app-schema positionally and is stamped with this call's source-coords. Returns the vector of paths registered v1 advanced 010
reg-flow Fn (reg-flow flow-id metadata derive-fn) v1 advanced 013 Optional flows artefact. Per the canonical Spec 001 3-slot grammar (rf2-bqstzr): the pure :derive fn is the third VALUE slot; metadata carries :inputs / :output-path (both REQUIRED) plus optional :doc / :schema / the EP-0025 classification keys and the :frame mounting key. A :derive left inside the metadata map is rejected loudly (:rf.error/invalid-flow-metadata). Returns flow-id (per Conventions §reg-* return-value convention). Flows are frame-scoped and single-store: the :flow registrar kind is RESERVED-but-empty; introspection is via the frame-scoped re-frame.flows/flow-meta-at / flows-snapshot (not handler-meta :flow). Per 013-Flows.md §The registration shape / §Frame-scoping.
reg-route M (reg-route id metadata path) — canonical 3-slot grammar: the URL :path pattern is the third VALUE slot, metadata the pure reflection map v1 advanced 012 Optional routing artefact. A :path left inside the metadata map is a loud :rf.error/route-bad-metadata.
reg-head M (reg-head id ?metadata head-fn) v1 advanced 011 Optional SSR artefact. New registry kind :head; routes name a registered head via :head route metadata. Captures source-coords; under the optional-artefact wrapper convention the surface routes through the :ssr/reg-head late-bind hook.
reg-error-projector M (reg-error-projector id ?metadata projector-fn) v1 advanced 011 Optional SSR artefact. New registry kind :error-projector; named per-frame via the frame's :ssr {:public-error-id ...} config (per make-frame / frame-root).

reg-sub input-production modes

reg-sub supports three input-production modes. Every subscription has an input query-vector producer: layer-1 has no producer; :<- is the literal producer; input-fn is the query-parametric producer.

Mode Form Meaning
App-db reader (reg-sub id computation-fn) No upstream subscriptions. The computation fn receives app-db and the outer query-v.
Static inputs (reg-sub id :<- q1 :<- q2 computation-fn) Inputs are literal query vectors known at registration.
Parametric inputs (reg-sub id input-fn computation-fn) Inputs are computed from the outer query-v when a concrete cache entry is materialized.

The two-function form's first fn is a v2 input-fn — a pure function from the outer query-v to a vector of input query vectors. It is not a v1 signal function: it must not call subscribe, deref app-db, dispatch, mutate, or perform IO; it receives only the outer query-v; and it must not return live reactions. The computation-fn receives the vector of resolved input values (in the same order) and the outer query-v.

(rf/reg-sub
  :article/page
  (fn input-fn [[_ article-id]]
    [[:article/by-id article-id]
     [:comments/for-article article-id]
     [:viewer/current]])
  (fn computation-fn [[article comments viewer] [_ article-id]]
    {:id article-id :article article :comments comments
     :can-edit? (:edit? viewer)}))

Input grammar. An input-fn MUST return a vector, and every element of that vector MUST be a query vector (a vector whose first element is a keyword):

input-return := [query-vector*]      ;; query-vector := vector with a keyword head
;; Accepted
[[:article/by-id id] [:viewer/current]]   ;; multiple inputs
[[:item/by-id id]]                        ;; single input — still a vector OF query vectors
[]                                        ;; no inputs (unusual but valid)

;; Rejected — see :rf.error/sub-input-fn-bad-return
:viewer/current                           ;; bare keyword
[:article/by-id id]                       ;; scalar query vector (ambiguous: arg vs two inputs)
[[:article/by-id id] :viewer]             ;; mixed vector + bare keyword
{:article [:article/by-id id]}            ;; map return

The scalar query-vector rejection is deliberate: [:x :y] is ambiguous at this boundary (one query with argument :y, vs two inputs). The only accepted single-query spelling is [[:x :y]]. No bare keyword shorthand, no map return, no reaction/derefable. Use :<- for static inputs; reach for input-fn only when the upstream query vectors need values from the outer query-v. The static :<- form is exactly a constant input-fn ((fn [_] [[:items] [:filter]])). Per 006 §Subscription input producers, 008 §compute-sub algorithm, and Conventions §reg-sub input grammar. Registration-shape and input-return errors signal loudly via :rf.error/reg-sub-bad-args, :rf.error/sub-input-fn-exception, and :rf.error/sub-input-fn-bad-return (catalogued in 009 §Error event catalogue).

Clearing registrations

API M/Fn Signature Status Tier
clear-event Fn (clear-event) / (clear-event id) v1 (preserved) advanced
clear-sub Fn (clear-sub) / (clear-sub id) v1 (preserved) advanced
clear-fx Fn (clear-fx) / (clear-fx id) v1 (preserved) advanced
destroy-frame! Fn (destroy-frame! frame-id) — the normative teardown boundary. A frame value target carries exact-incarnation authority (tears down only the incarnation it names — a stale value no-ops against a same-id successor), while a frame-id keyword is address-directed (tears down whatever incarnation is currently live), per 002 §Destroy (rf2-moftbs). Per-feature artefacts (flows, machines, schemas, SSR, epoch) hang their frame-scoped cleanup off this call; flows release per 013 §Frame-destroy teardown. v1 front-porch
clear-sub-cache! Fn (clear-sub-cache! frame-id?) v1 (preserved) advanced

Dispatch and subscribe

API M/Fn Signature Status Tier Spec
dispatch M/Fn (CLJS) (dispatch event) / (dispatch event opts) v1 (preserved + extended); macro captures call-site for :rf.trace/call-site; on CLJS the same name is ALSO a plain-fn value-alias (Convention A, rf2-m90brg) for HoF / programmatic dispatch with no call-site capture front-porch 002
dispatch-sync M/Fn (CLJS) (dispatch-sync event) / (dispatch-sync event opts) v1 (preserved + extended); macro captures call-site for :rf.trace/call-site; on CLJS the same name is ALSO a plain-fn value-alias (Convention A, rf2-m90brg) for HoF / programmatic sync dispatch with no call-site capture front-porch 002
subscribe M/Fn (CLJS) (subscribe query-v) / (subscribe query-v opts) v1 (preserved + extended); opts may carry :frame (mirrors dispatch); macro captures call-site for :rf.trace/call-site; on CLJS the same name is ALSO a plain-fn value-alias (Convention A, rf2-m90brg) for HoF / programmatic subscribe with no call-site capture front-porch 002
subscribe-once Fn (subscribe-once query-v) / (subscribe-once query-v opts) → value (subscribe + deref + immediate unsubscribe; one-shot, non-reactive read for handler bodies, REPL — never inside a machine callback: an in-callback ambient read is unrecorded and breaks 005's replay contract; a machine reads facts via payload or a declared recordable cofx) v1 (preserved + extended); opts may carry :frame (mirrors subscribe, rf2-bfadc6) — closes the misbinding footgun for an author carrying the opts form over from subscribe advanced 006
unsubscribe Fn (unsubscribe query-v) / (unsubscribe frame-id query-v) → nil (decrement the cache ref-count; on the 1 → 0 transition the cache slot is disposed synchronously in-tick — no grace-period timer, per 006 §Reference counting and disposal). Carved out from the Conventions §Tear-down verb axisclear-sub is already taken by the symmetric inverse of reg-sub (the registrar decrement), so un- is reserved as the singular form for the sub-cache ref-count decrement. v1 advanced 006

To read a machine's snapshot, subscribe to the canonical [:rf/machine machine-id] vector (see §Standard registered subs (machines)).

opts map keys: :frame, :fx-overrides, :interceptor-overrides, :trace-id, :source. Envelope shape and semantics: see 002 §Routing: the dispatch envelope.

Canonical event / query-v shape (best practice). [<id>] (trivial), [<id> <single-scalar>] (single-arg), [<id> {<k> <v>}] (multi-arg → single map payload). Variadic [<id> a b c] is tolerated by the runtime for v1-migration and caller convenience; the linter nudges new code toward the map form. Full rationale and cross-refs: Conventions §Canonical event-vector shape.

dispatch-* family taxonomy

Per audit-of-audits state-machines #10, the dispatch-* family has two sub-shapes that look alike on first read but answer different questions. Both are dispatch operations — the family-prefix is honest — but they sit in different sub-families.

Stamping-pair sub-family (dispatch / dispatch-sync / subscribe). The pair-shape question is "do you want call-site stamping or not?" In call position the macro form captures :rf.trace/call-site from the surrounding source position so tooling can navigate from a trace event back to the originating expression. On CLJS the same name is ALSO a plain-fn value-alias (Convention A, rf2-m90brg — mirrors reg-event / reg-sub / etc.) for HoF composition ((map dispatch events)) where a macro can't sit inside the higher-order call — no call-site capture on that path. There is no *-suffixed twin (rf2-m90brg retired dispatch* / dispatch-sync* / subscribe* from the facade); a JVM programmatic caller reaches the owning ns fn directly (re-frame.router/dispatch! / -dispatch-sync!, re-frame.subs/subscribe). Both the macro path and the value-alias route through the same underlying dispatcher; only the trace stamping differs.

Named-target sub-family (the reserved [:rf.machine/dispatch-to-system [system-id event]] fx tuple, per 005 §Cross-machine messaging by name). This question is "do you have a :system-id instead of a target machine-id?" Named addressing resolves the target through the per-frame [:rf.runtime/machines :system-ids] reverse index first, then dispatches just like the stamping pair. The only surface is the action-side fx tuple; the redundant dispatch-to-system direct-call FN twin was deleted (pre-alpha, zero in-repo consumers — a machine IS an event handler, so the everyday send is plain dispatch to the id you hold). The fx is a parked named-addressing escape (advanced/parity tier), retained for XState v6 actor-system parity (systemId addressing); the facade audit at API-freeze rules on its deletion. The naming reads as "dispatch with extra routing logic on top," not "different kind of dispatch."


View ergonomics

The multi-frame surface is organised by intent, not mechanism (a front-porch / back-room split — per 002 §The multi-frame surface):

  • Single-frame (no frames in play): dispatch, dispatch-sync, subscribe.
  • Scope: with-frame, with-new-frame, frame-provider {:frame …} (SCOPE-only — scope an existing frame into a React subtree; fails loud if absent). (ENSURE is its sibling component frame-root {:id …} — see below.)
  • Hold (carry a frame's ops as a value, across async): capture-frame — the ONE public carry primitive (API-shrink #1, rf2-csbbwu removed frame-bound-fn / frame-bound-fn* from the facade entirely — capture-frame or an explicit {:frame …} opt expresses the real use cases).
  • Override: the {:frame …} opt — first-class explicit routing for tools / tests / SSR / fx handlers.
  • Reads / lifecycle: app-db-value, frame-state-value, current-frame-id, destroy-frame!, make-frame, frame-ids, frame-meta (see §Public registrar query API).
API M/Fn Signature Status Tier Spec
frame-provider Component (Reagent) SCOPE-only (rf2-nyea0r split — roots ensure; providers scope). [rf/frame-provider {:frame :todo} & children]: provides an ALREADY-CREATED frame id through React context; :frame accepts a frame-id keyword OR a live frame value (API-shrink #1, rf2-csbbwu); creates / refreshes / destroys nothing; FAILS LOUD when the frame is absent (:rf.error/frame-provider-frame-absent; a value/keyword that is neither raises :rf.error/bad-frame-provider-arg; a nil :frame:rf.error/no-frame-context). Given an :id (the ENSURE key), FAILS LOUD naming frame-root (:rf.error/frame-provider-given-id). For create-if-absent, use frame-root. v1 front-porch 002
frame-root Component (Reagent) ENSURE — a COMMIT-OWNED TWO-PASS boundary (rf2-nyea0r split). [rf/frame-root {:id :todo :images […] :initial-events [[:rf/set-db {}]]} & children]: creates the frame if absent (via make-frame) in a client useLayoutEffect (NOT during render — first render emits no descendant subtree; a Suspense-aborted render creates + seeds nothing, no ghost frame), REUSES it WITHOUT re-seeding if present (idempotent re-mount / keyed remount preserves durable state and does NOT replay :initial-events; StrictMode-safe), provides its id to descendants; NO destroy-on-unmount. :id required (a missing/non-keyword :id:rf.error/frame-root-missing-id); a mounted :id/opts change → :rf.error/frame-root-reconfigured; a stray :frame:rf.error/frame-root-given-frame. True ownership stays make-frame + destroy-frame! in a create-class. v1 front-porch 002
with-frame M (with-frame :keyword body) — pin to an existing frame-id. Vector arg is a compile-time error (use with-new-frame) v1 front-porch 002
with-new-frame M (with-new-frame [sym expr] body) — eval expr, bind sym, run body, destroy frame on exit. Keyword arg is a compile-time error (use with-frame) v1 front-porch 002
capture-frame Fn (capture-frame) or (capture-frame frame-id){:frame :dispatch :dispatch-sync :subscribe} — the keystone OPERATION BUNDLE. Captures the frame at CREATION; its ops always target the captured frame and survive async. Read app-db via (app-db-value (:frame h)), not the handle v1 front-porch 002
view Fn (view view-id)render-fn (runtime-lookup handle; returns the registered render-fn, not hiccup). Use in hiccup as [(rf/view :id) args...] — the lookup form for late-binding a registered view by id. v1 advanced 001

with-frame (pin) and with-new-frame (eval-bind-run-destroy) are documented in 002 §with-frame and with-new-frame. The macros are non-overlapping: each rejects the other's argument shape at compile time, with :recovery pointing the caller at the right sibling.

capture-frame is the keystone affordance — it replaces the removed dispatcher / subscriber nouns and is the single answer to "carry a frame's dispatch/subscribe ops across an async boundary." The handle is locked: a per-call :frame opt MUST NOT override the frame captured at handle creation — the captured frame always wins (per 002 §capture-frame). frame-bound-fn / frame-bound-fn* are REMOVED from the facade (API-shrink #1, rf2-csbbwu) — capture-frame (or an explicit {:frame …} opt) expresses the real use cases and is the ONE public carry primitive; the frame-rebinding closure semantics survive internally as re-frame.frame/bind-fn for the framework's own reach.


Reagent adapter (Spec 006)

Reagent-specific surfaces live in re-frame.adapter.reagent (artefact day8/re-frame2-reagent, the browser default). Reagent is the default substrate; its frame-provider Component is rowed in §View ergonomics and the reg-view macro in §Registration, not here. Apps targeting Reagent :require [re-frame.adapter.reagent :as reagent-adapter] and pass reagent-adapter/adapter to (rf/init! …).

API M/Fn Signature Status Tier Spec
reagent-adapter/adapter Var (map) the 11-key adapter spec map {:kind :make-state-container :read-container :replace-container! :subscribe-container :make-derived-value :render :render-to-string :register-context-provider :flush-render! :dispose-adapter!} (ratom-backed; per Spec 006 §Adapter contract) v1 adapter 006
reagent-adapter/flush-views! Fn (flush-views!) / (flush-views! f) — flush pending Reagent renders synchronously (wraps React's act() for tests); the canonical cross-substrate test-flush hook v1 adapter 006, 008
reagent-adapter/set-hiccup-emitter! Fn (set-hiccup-emitter! f) — install the render-tree → HTML fn for render-to-string (the SSR late-bind seam, published through the :reagent/set-hiccup-emitter! hook) v1 adapter 006, 011

UIx adapter (Spec 006)

UIx-specific surfaces live in re-frame.adapter.uix (artefact day8/re-frame2-uix) — they are NOT re-exported from re-frame.core because core has no static dependency on the adapter (the dependency direction is adapter → core per Conventions §Adapter shipping convention). Apps targeting UIx :require [re-frame.adapter.uix :as uix-adapter] and call the surfaces directly.

API M/Fn Signature Status Tier Spec
uix-adapter/adapter Var (map) the 11-key adapter spec map {:kind :make-state-container :read-container :replace-container! :subscribe-container :make-derived-value :render :render-to-string :register-context-provider :flush-render! :dispose-adapter!} (6 required + 3 optional + :dispose-adapter! + the :kind discriminator, per Spec 006 §Adapter contract) v1 adapter 006
uix-adapter/use-subscribe Fn (UIx hook) (use-subscribe query-v) / (use-subscribe frame-kw query-v) → current sub value v1 adapter 006
uix-adapter/use-frame Fn (UIx hook) (use-frame) → the frame api for the ambient provider frame — EXACTLY what (rf/capture-frame) returns ({:frame :dispatch :dispatch-sync :subscribe}), capture-frame in hook position; resolves through the same carried-invariant chain as the ambient use-subscribe (no scope → :rf.error/no-frame-context); reference-stable across re-renders for the same resolved frame incarnation — a same-id destroy-and-recreate retargets it, because the bundle is pinned to the incarnation capture-frame ran against and not to the address. No opts, no variants — explicit frames use (rf/capture-frame frame-id). v1 adapter 002, 006
uix-adapter/use-current-frame Fn (UIx hook) (use-current-frame) → frame-kw, or the :rf.frame/no-provider sentinel when neither frame-provider nor frame-root sits above (the narrow raw useContext read of the one shared context both boundaries install — explicitly not :rf/default; use (rf/current-frame-id) for the full dynamic-var → context → nil resolution chain) v1 adapter 006
uix-adapter/frame-provider Fn (UIx component) SCOPE-only (rf2-nyea0r split — roots ensure; providers scope) — ($ uix-adapter/frame-provider {:frame :session} child-1 child-2) provides an existing frame id; creates nothing; fails loud if absent (:rf.error/frame-provider-frame-absent); given an :id (the ENSURE key) fails loud naming frame-root (:rf.error/frame-provider-given-id); idiomatic $ trailing children v1 adapter 002, 006
uix-adapter/frame-root Fn (UIx component) ENSURE — a commit-owned two-pass boundary (rf2-nyea0r split) — ($ uix-adapter/frame-root {:id :session :images […]} child-1 child-2) creates the frame if absent in a client useLayoutEffect (not during render — a discarded render creates nothing), reuses it without re-seeding if present, provides its id to descendants; no destroy-on-unmount; takes make-frame opts; a missing/non-keyword :id:rf.error/frame-root-missing-id, a stray :frame:rf.error/frame-root-given-frame; idiomatic $ trailing children v1 adapter 002, 006
uix-adapter/wrap-view Fn (wrap-view id metadata user-fn) → wrapped fn (source-coord injection per Spec 006 §Source-coord annotation) v1 adapter 006
uix-adapter/flush-views! Fn (flush-views!) / (flush-views! f) — wraps React's act() for tests v1 adapter 006, 008
uix-adapter/set-hiccup-emitter! Fn (set-hiccup-emitter! f) — install render-tree → HTML fn (parity with the Reagent adapter's late-bind seam) v1 adapter 006, 011

Per Decision 1 the hook is named use-subscribe (matching the React/UIx idiom). Per Decision 3 there is no auto-injection — UIx components read via the hook and hold frame ops via use-frame (capture-frame in hook position: (let [{:keys [dispatch]} (use-frame)] …)); capture-frame is THE hold primitive, and reg-view injection / use-frame are its two ergonomic spellings. Per Decision 4 reg-view (the Reagent macro) does NOT cover UIx; UIx users register with rf/reg-view* if they need registry-keyed view addressing.

The shared React Context that backs frame-provider / frame-root lives in re-frame.adapter.context (CLJS-only file in core, factored out per Decision 2) — the Reagent and UIx adapters both consume the same createContext object so a mixed-substrate app's frame-provider chain composes across substrates.


Routing (Spec 012)

reg-route is rowed canonically in §Registration.

API M/Fn Signature Status Tier Spec
match-url Fn (match-url url){:route-id :params :query :fragment :validation-failed? ?:validation-error} or nil (:validation-error present only on a validation failure) v1 advanced 012
route-url Fn (route-url {:to route-id :params path-params :query query-params :fragment fragment}) → URL string (single address-map arity; strictly address-only — :url / :query-merge / policy / unknown keys reject loud) v1 advanced 012
route-link Fn (registered view at :route/link) [rf/route-link {:to :route-id :params {...} :query {...} :fragment "..." :prefetch :intent & html-attrs} & children] v1 advanced 012
history-url-strategy Var (map) The DEFAULT :url-strategy — HTML5 History, path-form. re-frame.routing/history-url-strategy, NOT on the re-frame.core façade (routing bundle isolation) v1 advanced 012
hash-url-strategy Var (map) #-prefixed :url-strategy for no-server-rewrite static hosting / secretary-era v1 migrations. re-frame.routing/hash-url-strategy v1 advanced 012
with-base-path Fn (with-base-path strategy base) — STRATEGY COMBINATOR (rf2-g8pbwg): wraps strategy (either shipped strategy, or a custom one) so a deployment sub-path (e.g. an app served from /realworld/) is stripped off every inbound URL and re-added to every outbound one, at all four egress/ingress consult points. A blank/nil base returns strategy unchanged. re-frame.routing/with-base-path v1 advanced 012

A :url-bound? true frame's browser URL-change listener is installed / removed by the FRAME LIFECYCLE, automatically — creation installs (strategy-aware: popstate for history, hashchange for hash, per :url-strategy), destroy removes (rf2-g8pbwg). There is no imperative install/remove pair to call; the retired install-url-listener! / remove-url-listener! / install-history-listener! / remove-history-listener! exports are GONE (pre-alpha, no back-compat shim).

reg-route's routing-owned metadata reserved keys: :doc, :params, :query, :query-defaults, :tags, :parent, :on-match, :can-leave, :can-enter, :scroll, :sensitive, :large. Two cross-feature bare keys are accepted beside them: :head, owned by SSR (011) and always accepted; and :resources, owned by the Resources artefact (016) and accepted only when that artefact publishes its :routing/extra-route-keys hook. The URL :path pattern is the third VALUE slot, not a metadata key. Canonical detail in 012-Routing.md §Reserved route-metadata keys; shape in Spec-Schemas §:rf/route-metadata.

route-link click rules: a plain primary-button click (no modifier keys, no defaultPrevented) calls .preventDefault and dispatches [:rf.route/url-requested {:url <synthesised> :to <route-id> :params {...} :query {...} :fragment "..."}]. Modifier-key clicks (cmd / ctrl / shift / alt) and auxiliary-button clicks (middle-click) defer to the browser so the native href opens in a new tab. A caller-supplied :on-click runs first; if it calls .preventDefault (or otherwise leaves defaultPrevented true) the framework's interception is skipped. Keys other than :to / :params / :query / :fragment / :prefetch / :on-click pass through to the underlying <a> element. Detailed semantics in 012-Routing.md §Linking from views.

Standard route-related events:

Event Notes Spec
:rf.route/navigate Navigate to a registered route. 012
:rf.route/handle-url-change URL-change handler for popstate / initial load / SSR (default scroll :restore). Co-equal sibling of :rf.route/transitioned, not a delegate. 012
:rf.route/transitioned URL-change handler for forward navigation (link click / programmatic push; default scroll :top). 012
:rf.route/url-requested The user clicked a framework-owned link. 012
:rf.route/navigation-blocked A :can-leave guard rejected a navigation. 012
:rf.route/entry-denied A :can-enter guard rejected navigation into a route. TERMINAL — nothing commits and no pending value is created; dispatched exactly once per attempt, carrying {:destination :target :cause :requested-url :guard}. A framework no-op default handler ships, so denial is safe with no application handler. 012
:rf.route/continue User-dispatched event proceeding a blocked navigation. 012
:rf.route/cancel User-dispatched event abandoning a blocked navigation. 012

Standard route-related subs:

Sub Returns Spec
:rf/route The full :rf/route slice {:route-id :params :query :fragment :transition :error :nav-token} 012
:rf.route/id Current route id 012
:rf.route/params Current path params 012
:rf.route/query Current query params 012
:rf.route/transition :idle / :loading / :error 012
:rf.route/error Current error map (when :transition = :error) 012
:rf.route/fragment Current URL fragment (string or nil) 012
:rf.route/chain Vector of route ids from parent-most to current (per :parent links) 012
:rf/pending-navigation The pending-nav slot (per :rf/pending-navigation schema) when a navigation is blocked; nil otherwise 012

The route and the pending-nav slot are read as ordinary subscription vectors — @(rf/subscribe [:rf/route]) and @(rf/subscribe [:rf/pending-navigation]); the :rf.route/* granular subs above chain off [:rf/route]. There is no named-read-sugar fn: a runtime-db framework read is a subscription vector, one grammar (per Conventions §Reserved sub-ids).

Standard route-related fx (canonical detail in 012-Routing.md):

Fx Args Platforms
:rf.nav/push-url URL string :client
:rf.nav/replace-url URL string :client
:rf.nav/scroll scroll-spec map :client
:rf.nav/capture-scroll {:url <leaving-route-url>} :client
:rf.route/with-nav-token {:rf/reply-to <reply-target> :value <v> :nav-token <token> :route-id <route-id>} (per 012 §Threading the nav-token) universal

Standard route-related cofx (canonical detail in 012-Routing.md):

Cofx Delivers Spec
:rf.route/nav-token The active navigation epoch token (read from [:rf.runtime/routing :current :nav-token]), delivered flat under the coeffect key :rf.route/nav-token — declare via {:rf.cofx/requires [:rf.route/nav-token]} in an :on-match-reached handler to capture the epoch live at scheduling time for stale-result suppression. Per 012 §Navigation tokens. 012

SSR (Spec 011)

Namespace: the surfaces below live in re-frame.ssr (artefact day8/re-frame2-ssr); consumers (:require [re-frame.ssr :as ssr]). The Ring host-adapter lives in re-frame.ssr.ring (artefact day8/re-frame2-ssr-ring). The SSR query surface (render-to-string, render-tree-hash, project-error, render-head, active-head, head-model->html, head-snapshot) is also late-bind re-exported through re-frame.core — one of the two documented façade exceptions (the other is epoch), per the §Conventions per-artefact namespace table. The streaming surface (streaming-render-*) and the Ring host-adapter (re-frame.ssr.ring) are NOT re-exported — they are host-adapter territory; an SSR-aware host requires the namespace directly. Apps targeting SSR add the artefacts to their deps regardless.

reg-head and reg-error-projector are rowed canonically in §Registration. The head-fn signature is (fn [db route] head-model); the projector-fn signature is (fn [trace-event] :rf/public-error).

API M/Fn Signature Status Tier Spec
render-to-string Fn (render-to-string view-or-hiccup opts) → HTML string v1 advanced 011
emit-ui-tree Fn (emit-ui-tree tree) / (emit-ui-tree tree {:doctype? bool}) → HTML string. Folds an already-rendered version-1 structural tree to HTML — pure, deterministic to the byte, JVM-runnable; it calls no view and resolves no subscription. Validates the root :rf.ui/tree-version first, before any emission: a missing / non-integer / unsupported version throws :rf.error/ssr-ui-tree-version-unsupported ({:got … :supported #{1}}); a malformed node past the gate throws the shared :rf.error/ui-tree-malformed. :doctype? is the only current option — it prefixes <!DOCTYPE html>. Per 004B §The SSR consumption boundary. v1 advanced 004B
render-tree-hash Fn (render-tree-hash render-tree) → 32-bit FNV-1a structural hash (lowercase hex). Identical output on JVM and CLJS for the same canonical-EDN representation. Per 011 §Hydration-mismatch detection. v1 advanced 011
project-error Fn (project-error frame-id trace-event):rf/public-error. Applies the active error-projector (selected by the frame's :ssr {:public-error-id ...} metadata) for the named frame. Per 011 §Server error projection. v1 advanced 011
render-head Fn (render-head head-id opts):rf/head-model v1 advanced 011
active-head Fn (active-head frame-id):rf/head-model. Head rendering is a frame-scoped read, so the frame is carried, not ambient: the no-arg form was removed (EP-0002) and a nil frame-id raises :rf.error/no-frame-context rather than resolving against a synthesised default frame. v1 advanced 011
head-model->html Fn (head-model->html head-model) / (head-model->html head-model {:wrap? bool}) → inner-head HTML string v1 advanced 011
head-snapshot Fn (head-snapshot frame-id){head-id → :rf/head-model}. Read the per-frame snapshot of last-produced head-models. Returns {} for a frame that has never seen a render-head call (or whose snapshot has been cleared via per-request frame teardown). Useful for tests, introspection, and tools. Re-exported as rf/head-snapshot. Per 011 §Head/meta contract. v1 advanced 011
streaming-render-shell Fn (streaming-render-shell root-hiccup){:shell-html "…" :continuations [{:id <id> :subtree <hiccup>} …]}. Walks the tree once; at each :rf/suspense-boundary emits a <template …suspense-fallback> placeholder + records a continuation. Per 011 §Streaming SSR — (a). v1 advanced 011
streaming-render-continuation Fn (streaming-render-continuation frame-id entry){:id … :html "…" :delta {…} :failed? bool :continuations [{:id <id> :subtree <hiccup>} …]} (:continuations are nested boundaries discovered while draining — [] in the common case). Drains one continuation against frame-id's app-db; snapshots before-db / after-db and computes the per-subtree delta. Catches throws and surfaces the original fallback HTML inline (per 011 §Failure semantics — inline fallback). v1 advanced 011
streaming-build-final-payload Fn (streaming-build-final-payload frame-id render-hash opts) → canonical :rf/hydration-payload. Called after all continuations drain to populate the __rf_payload final chunk. v1 advanced 011

Standard SSR-related events:

Event What it does Spec
:rf/server-init Per-request server-side initialisation. Reads request cofx; dispatches setup events. :platforms #{:server}. 011
:rf/hydrate Seed the client-side app-db from the server-supplied payload. Runs once on client bootstrap. 011

Standard SSR-related fx (server-only; :platforms #{:server}):

Fx Args Spec
:rf.server/set-status :int (per :rf.fx.server/set-status-args) 011
:rf.server/set-header {:name :value} (per :rf.fx.server/set-header-args) 011
:rf.server/append-header {:name :value} (per :rf.fx.server/append-header-args) 011
:rf.server/set-cookie :rf.server/cookie map 011
:rf.server/delete-cookie {:name ?:path ?:domain} 011
:rf.server/redirect {:location ?:status} (default :status 302); truncates HTML. Caller-trusted :location 011
:rf.server/safe-redirect {:location ?:relative-only? ?:allow ?:status} — the caller-untrusted variant; parses :location, rejects javascript: / data: / vbscript: schemes, and enforces :relative-only? / :allow allowlist before setting :redirect. Open-redirect mitigation for attacker-controlled ?next= strings 011

Standard SSR-related fx (client-side hydration compatibility checks; :platforms #{:client}):

Fx What it does Spec
:rf.ssr/check-version Hydration-side framework version-compatibility check (emits :rf.ssr/version-mismatch on drift). 011
:rf.ssr/check-schema-digest Hydration-side schema-digest compatibility check (emits :rf.ssr/schema-digest-mismatch on drift). 011

Implementation-tier SSR internals (not rowed). The client hydration / streaming lifecycle — hydrate!, verify-hydration!, read-server-payload, streaming-install!, drain-blocking-resources!, and the per-request response / request accessors (get-response / peek-response / flush-response! / get-request / set-request! / clear-request!) — live in re-frame.ssr but are :implementation-tier host-adapter plumbing (the supported client path is the :rf/hydrate event plus the re-frame.ssr.ring host adapter). Per the projection's implementation-tier policy (§Tier taxonomy) they are carried by the manifest but not rowed here — the per-request response accumulator (:rf/response) is read through these accessors, not a subscription.

Standard SSR-related subs:

Sub Returns Spec
:rf/head The head model for the active route (resolved via (active-head frame-id)) 011
:rf/public-error The sanitised public-error projection when an error page is being rendered; nil otherwise 011

Standard cofx (server-only):

Cofx Returns Spec
:rf.server/request The active HTTP request map 011

reg-fx's :platforms metadata key (a set containing :server and/or :client) gates fx execution by active platform; default #{:server :client} (universal) when the key is absent. Skipped fx emit a :rf.fx/skipped-on-platform trace event. Detail in 011 §:platforms metadata on reg-fx.

SSR error-projection policy is per-frame metadata (see Conventions §Configuration surfaces bucket 3): a frame opts in via the :ssr {:public-error-id ... :dev-error-detail? ...} map on its make-frame / frame-root config. See 011 §Server error projection for the keys.


HTTP requests (Spec 014)

:rf.http/managed is the canonical, optional HTTP-request fx — v1 (optional capability). CLJS reference ships it on Fetch (browser) and java.net.http.HttpClient (JVM). Args, behaviours, decode pipeline, retry semantics, abort surface, failure taxonomy, and reply addressing are normatively defined in 014-HTTPRequests.md; the surface below is the API-level summary.

API Kind Signature / shape Status Tier Spec
:rf.http/managed fx [:rf.http/managed args-map] — args per 014 §The args map and :rf.fx/managed-args v1 (optional capability) — (fx-id; follows the advanced HTTP artefact) 014
:rf.http/managed-abort fx [:rf.http/managed-abort request-id] — abort the in-flight request with the given :request-id v1 (optional capability) — (fx-id) 014
:rf.http/managed-canned-success fx [:rf.http/managed-canned-success {:value v}] — synthesises the canonical success reply (per 014 §Testing). Registered at load of re-frame.http.test-support (NOT re-frame.http.managed); the stubbing macros (rows below) ship in the same namespace per (audit-of-audits #15). v1 (optional capability, dev/test) — (fx-id; test) 014
:rf.http/managed-canned-failure fx [:rf.http/managed-canned-failure {:kind <:rf.http/*> :tags {...}}] — synthesises the canonical failure reply. Same registration gate (re-frame.http.test-support) and same co-location with the stubbing macros. v1 (optional capability, dev/test) — (fx-id; test) 014
with-managed-request-stubs M (with-managed-request-stubs route-map body+) — route-map {[<method> <url>] {:reply ...}} per 014 §Testing. Lives in re-frame.http.test-support (the single home for HTTP test surfaces — audit-of-audits #15 closed the prior split that placed the macros in re-frame.http.managed); see Spec 008 §HTTP test surfaces. v1 (optional capability, dev/test) testing 014
with-managed-request-stubs* Fn (with-managed-request-stubs* route-map body-fn) — plain-fn surface beneath with-managed-request-stubs; the * follows the Clojure let/let*, fn/fn* idiom (per Conventions). Ships in re-frame.http.test-support. Use for computed route-maps or non-literal bodies. Per 014 §Testing. v1 (optional capability, dev/test) testing 014
reg-http-interceptor M (reg-http-interceptor id interceptor-map) — register an HTTP interceptor on a frame's :rf.http/managed middleware chain (per 014 §Middleware). A façade macro (re-frame.core, capturing call-site source-coords; the fn form is re-frame.http.middleware/reg-http-interceptor). id is a keyword; interceptor-map carries at least one of :before (fn [ctx] ctx') and :after (fn [ctx response] response'), plus optional :frame (the EP-0002 override; absent, the carried scope it registers under resolves it — registering under no scope raises :rf.error/no-frame-context, never :rf/default) and any :rf/registration-metadata (:doc / :tags / :schema / :sensitive?). The surface mirrors the event-interceptor {:id :before :after} shape — symmetric request/response sides; :before chain in registration order, :after chain in reverse. v1 (optional capability) advanced 014
clear-http-interceptor Fn (clear-http-interceptor id) / (clear-http-interceptor id {:frame target}) — unregister an interceptor by id (per 014 §Middleware). The single-arity resolves the frame from the carried scope; under no scope it raises :rf.error/no-frame-context — no :rf/default fallback (EP-0002). The two-arity names the frame via an id-first {:frame target} opts map (the framework-wide (id {:frame …}) shape) and is exact and fail-closed: anything but exactly {:frame target} with a present, non-nil target — {}, {:frame nil}, a typo'd or extra key, a non-map second arg — raises :rf.error/http-bad-interceptor BEFORE any ambient frame is resolved or touched (rf2-s32bf). A valid explicit target need not name a currently live frame: clearing an absent frame's chain is an idempotent no-op, not an error. The frame-first (frame id) spelling is the artefact-internal clear-http-interceptor* seam (used by the :rf.fx/clear-http-interceptor fx), not a public arity. v1 (optional capability) advanced 014
:rf.fx/reg-http-interceptor fx [:rf.fx/reg-http-interceptor {:id <id> :before … :after … ?:frame …}] — register an HTTP interceptor at runtime via :fx (the data-shaped counterpart of the reg-http-interceptor macro; the dispatching frame threads through when :frame is omitted). Registered by re-frame.http.managed; :platforms #{:client :server}. v1 (optional capability) — (fx-id) 014
:rf.fx/clear-http-interceptor fx [:rf.fx/clear-http-interceptor {:id <id> ?:frame …}] — unregister an HTTP interceptor at runtime via :fx (args :frame wins, else the fx-context frame). v1 (optional capability) — (fx-id) 014
re-frame.http/get Fn (rf.http/get url) / (rf.http/get url args) — build a [:rf.http/managed {:request {:method :get :url url} ...}] fx vector (per 014 §Call-site helpers). v1 (optional capability) advanced 014
re-frame.http/post Fn (rf.http/post url) / (rf.http/post url args) — POST helper; same shape as get. v1 (optional capability) advanced 014
re-frame.http/put Fn (rf.http/put url) / (rf.http/put url args) — PUT helper. v1 (optional capability) advanced 014
re-frame.http/delete Fn (rf.http/delete url) / (rf.http/delete url args) — DELETE helper. v1 (optional capability) advanced 014
re-frame.http/patch Fn (rf.http/patch url) / (rf.http/patch url args) — PATCH helper. v1 (optional capability) advanced 014
re-frame.http/head Fn (rf.http/head url) / (rf.http/head url args) — HEAD helper. v1 (optional capability) advanced 014
re-frame.http/options Fn (rf.http/options url) / (rf.http/options url args) — OPTIONS helper. v1 (optional capability) advanced 014

Public API surface in re-frame.core for ports that ship Spec 014. Ports that omit it MUST NOT register :rf.http/* for any other purpose (per Conventions §Reserved namespaces).

The raw install-managed-request-stubs! / uninstall-managed-request-stubs! pair is NOT a re-frame.core façade export — it is test-support infrastructure reached through its home namespace re-frame.http.test-support ((:require [re-frame.http.test-support :as http-test-support])). The ergonomic with-managed-request-stubs macro is the façade surface; use the raw pair only when stubs must span multiple deftests.

The verb helpers (get / post / put / delete / patch / head / options) live in re-frame.http — users (:require [re-frame.http :as rf.http]) alongside re-frame.core. They're pure synthesis fns that produce the canonical [:rf.http/managed args-map] fx vector; the namespace ships in day8/re-frame2-http (same artefact as the fx they reference) so loading the helpers and the fx are a single dep decision.

Reply-payload shape

Every reply lands as the canonical uniform reply envelope (rf2-ibksxg — one dialect, no {:kind :success/:failure} reshape): {:status :ok :value v …} on success, {:status :error :error {:kind <:rf.http/*> …} …} on failure, {:status :cancelled :error {:kind :rf.http/aborted …} …} on abort. Reply addressing is explicit: :reply-to is the unified app-facing spelling — one event vector for both the success and the failure reply, the envelope appended as the last event-vector arg (the app branches on (:status reply)); the same :reply-to key resources / mutations use. :on-success / :on-failure are the split routing sugar over it — a named target per branch, both receiving the identical envelope. All lower to the one internal / normalized :rf/reply-to descriptor (a conformance surface, no longer an everyday spelling). Omitting all reply targets raises :rf.error/http-no-reply-target at dispatch — the co-located default (reply merged under :rf/reply back to the originating event) was retired pre-alpha (rf2-et4c1s). Detailed in 014 §Reply addressing and §Reply payload shape.

Failure categories (closed set)

The eight :kind values inside a failure reply, all reserved under :rf.http/* (per Conventions §Reserved namespaces). See 014 §Failure categories for tags-by-kind:

:kind Meaning
:rf.http/transport Network / DNS / connection error pre-HTTP
:rf.http/cors CORS preflight rejected (CLJS-only)
:rf.http/timeout Per-attempt timeout fired
:rf.http/http-4xx Non-2xx 4xx response
:rf.http/http-5xx Non-2xx 5xx response
:rf.http/decode-failure 2xx response but decode rejected the body
:rf.http/accept-failure :accept returned {:failure user-map}
:rf.http/aborted Request aborted via :request-id or :abort-signal

Trace events emitted by :rf.http/managed

:operation :op-type When
:rf.http/retry-attempt :info Per intermediate attempt that matched :retry :on, plus a terminal retry-sequence stop marker once the sequence ends (budget spent, or a later attempt failed outside :retry :on); carries :request-id, :url, :attempt, :max-attempts, :failure, :next-backoff-ms (nil on the terminal stop marker)
:rf.http.interceptor/registered :info A reg-http-interceptor succeeded; carries :frame, :id (per 014 §Middleware)
:rf.http.interceptor/cleared :info A clear-http-interceptor removed an existing slot; carries :frame, :id
:rf.error/http-interceptor-failed :error An interceptor :before or :after threw; carries :frame, :interceptor-id, :url, :cause (plus :phase :after on the response side). Request side: the request is NOT dispatched; response side: the reply is suppressed (per 014 §Middleware §Failure mode)

Schema-reflection metadata

Handlers may declare :rf.http/decode-schemas [<schema> ...] in their reg-event metadata-map; pair tools and generators read it via (rf/handler-meta :event id). Optional, never enforced — see 014 §Schema reflection.


Resources (Spec 016)

The Resources artefact (day8/re-frame2-resources, post-v1 optional) ships declarative cached server-state. Its full registration / event / sub / accessor surface is normatively defined in 016-Resources.md; this projection rows the facade exports classified by the standing diff-time rule (every new re-frame.core facade export is classified + justified when it lands). The resource/mutation registrars and the :rf.resource/* / :rf.mutation/* keyword-addressed events, subs, and trace ops follow the artefact's tier (optional capability) and carry no Tier column (they are not vars).

API M/Fn Signature Status Tier Spec Notes
reg-resource Fn (reg-resource resource-id metadata request-fn) — canonical 3-slot grammar: the :request fetch fn is the third VALUE slot, metadata the reflection + config map (:scope, :params-schema, :data-schema, …) post-v1 lib (optional capability) advanced 016 The :resource registrar kind. Late-bound by the Resources artefact. A :request left inside the metadata map is a loud :rf.error/resource-bad-spec. Front-room of the resources surface but advanced (not front-porch): an optional post-v1 capability a new app does not reach for on day one.
clear-resource Fn (clear-resource resource-id) post-v1 lib (optional capability) advanced 016 Registration-lifecycle removal (the clear- registrar decrement per Conventions §Tear-down verb axis); disposes resource-runtime state per 016 §Registration.
reg-mutation Fn (reg-mutation mutation-id metadata request-fn) — canonical 3-slot grammar: the :request write fn is the third VALUE slot, metadata the reflection + config map (:params-schema, :invalidates, …) post-v1 lib (optional capability) advanced 016 The :mutation registrar kind — a named causal write. A :request left inside the metadata map is a loud :rf.error/mutation-bad-spec. :rf.mutation/execute carries the call-site :reply-to continuation target.
clear-mutation Fn (clear-mutation mutation-id) post-v1 lib (optional capability) advanced 016 Registration-lifecycle removal of a mutation.
reg-resource-scope Fn (reg-resource-scope scope-id metadata resolve-fn) — register a pure named db-derived scope resolver in the canonical 3-slot grammar (rf2-bqstzr): the :resolve fn is the value slot, and metadata carries the declared :inputs {name [:db <rf-path>]} (plus optional :doc). Omitting :inputs (the 2-arg (reg-resource-scope scope-id resolve-fn) sugar) selects the whole-db form — the resolver reads the db, a tooling-marked explicit-cost dependency. A :resolve inside the metadata map is rejected loudly. The :resource-scope registrar kind. post-v1 lib (optional capability) advanced 016 Facade classification (EP-0016 D3): a re-frame.core export of the optional Resources artefact, justified as the single scope-resolution currency reused by resource registration, route resources, event ensure, subscriptions, invalidation descriptors, exact targets, and clear-scope. advanced — an optional-artefact authoring surface, not front-porch. Per 016 §Named resource-scope resolvers.
clear-resource-scope Fn (clear-resource-scope scope-id) post-v1 lib (optional capability) advanced 016 The clear- registrar decrement counterpart of reg-resource-scope (per Conventions §Tear-down verb axis).
resolve-resource-scope Fn (resolve-resource-scope db scope-id) — resolve a named scope resolver against a supplied db value; returns the canonical scope or nil. A pure resolver helper, not an effect (no app-state / dispatch side effects) and no observability side effect — it routes through the trace-free pure evaluator, so unlike the causal resolution sites it does not emit :rf.resource/scope-resolved. post-v1 lib (optional capability) advanced 016 Facade classification (EP-0016 D3 / issue 7): a re-frame.core export justified as the ergonomic helper for the logout/account-switch idiom — resolve the concrete old scope from the handler's coeffect db (no :snapshot-db payload, which would be an egress-bearing record under EP-0015). A plain function over the resolver registry; no new effect-API surface, no resolution-timing ambiguity. It routes through the trace-free pure evaluator, so — unlike the causal {:from-db …} / route-entry / mutation-settle resolution sites — it does not emit :rf.resource/scope-resolved (rf2-ru73k6 F3: a passive read advertised as pure has no observability side effect). Per 016 §clear-scope resolves the concrete scope from the coeffect db.
resources Fn (resources {:frame f}) — frame-targeted introspection: the registered resources plus the live per-frame resource-instance table v1 (optional capability) advanced 016 Façade re-export of the Resources artefact (late-bound).
mutations Fn (mutations {:frame f}) — frame-targeted introspection: the registered mutation ids plus the live per-frame mutation-instance table (keyed by instance id) v1 (optional capability) advanced 016
resource-meta Fn (resource-meta resource-id) — the registered resource's spec map (:params-schema, :data-schema, :request, :scope, …), or nil v1 (optional capability) advanced 016
mutation-meta Fn (mutation-meta mutation-id) — the registered mutation's spec map (:request, :params-schema, :invalidates, …), or nil v1 (optional capability) advanced 016
resource-state Fn (resource-state {:resource … :scope … :params … :frame …}) — a resource instance's live runtime state (explicit frame target) v1 (optional capability) advanced 016
mutation-state Fn (mutation-state {:instance … :frame …}) — a mutation instance's durable runtime row ({:status :result :error …}), or nil (explicit frame target) v1 (optional capability) advanced 016
install-revalidation-listeners! Fn (install-revalidation-listeners! frame-id) — window focus / network-reconnect listeners driving active-stale revalidation for the frame; idempotent; cancelled on frame destroy; CLJS-only (JVM no-op) v1 (optional capability) advanced 016
remove-revalidation-listeners! Fn (remove-revalidation-listeners! frame-id) — tear down the revalidation listeners; no-op when none installed (and on the JVM) v1 (optional capability) advanced 016

Request decoration (EP-0016 Rider 3) reuses the existing HTTP facade. Auth/tracing/base-URL/retry decoration for resources and mutations is not a new resources surface — it is the existing reg-http-interceptor / clear-http-interceptor (§HTTP requests), registered once per frame and applied to every :rf.http/managed request (resource reads, mutations, plain managed calls). No new facade export is introduced for decoration; the doctrine is ownership (transport decoration lives in the managed-HTTP seam), per 016 §Request decoration belongs to the managed-HTTP seam.


Effect-map shape

Closed: #{:db :rf.db/runtime :fx}. Ordinary app handlers return only :db + :fx; :rf.db/runtime is reserved by convention for framework / runtime-extension authority (it writes the runtime-db partition — non-framework handlers emitting it are surfaced by dev diagnostics, not silently dropped). See Spec-Schemas §:rf/effect-map. Top-level :dispatch / :dispatch-later / :dispatch-n from v1 migrate via MIGRATION.md §M-8.

Key Notes
:db New app-db partition (replaces). The app-facing state key.
:rf.db/runtime New runtime-db partition (replaces). Reserved by convention for framework / runtime-extension authority — ordinary app handlers do not emit it.
:fx Vector of [fx-id args] pairs.

Standard :fx entries:

[fx-id args] Args Status Spec Notes
[:dispatch [event-id ...]] event vector v1 002
[:dispatch-later {:ms ms :event event-vec}] options map v1 002
[:http args] impl-specific user-registered via reg-fx.
[:rf.http/managed args-map] args per 014 §The args map v1 (optional capability) 014 Framework-provided when the implementation ships Spec 014. CLJS reference: ships on Fetch + JVM HttpClient. See also :rf.http/managed-abort, :rf.http/managed-canned-success, :rf.http/managed-canned-failure.
[:rf.nav/push-url url-string] URL string v1 012
[:raise event-vec] event vector v1 005 machine-only: reserved fx-id recognised by the machine handler; routes the event back into the same machine, atomic and pre-commit. Outside a machine action's :fx, this fx-id is unbound.
[:rf.machine/spawn spawn-spec] spawn-spec map (per :rf.fx/spawn-args: :machine-id/:definition, :id-prefix, :data, :on-spawn, :start) v1 005 Canonical actor-lifecycle fx (registered globally by re-frame.machines); installs a new dynamic actor (whose snapshot lives at [:rf.runtime/machines :snapshots <gensym'd-id>]). On the declarative :spawn / :spawn-all path the reducer binds the assigned id into the spawning machine's own :data under :rf/spawned (XState-context parity, per 005 §Recording the spawned id user-side); :on-spawn is advisory-only and cannot record it (its return is dropped). Emitted from any event handler's :fx (including machine actions and the :spawn desugar).
[:rf.machine/destroy actor-id] actor id (keyword) v1 005 Canonical actor-destroy fx (registered globally by re-frame.machines); runs the actor's :exit action, dissociates [:rf.runtime/machines :snapshots <actor-id>], and clears the actor's event-handler registration. Symmetric counterpart to :rf.machine/spawn.

Public registrar query API

For tooling, agents, story tools, 10x.

Two read shapes, no realm coordinate. The registrar query grammar has exactly two shapes per member (registrations / handler-meta): the positional-keyword arity reads the default source store ((registrations :event) etc.), and a map is ALWAYS a frame-targeted read (next note). There is no :realm map arity(registrations {:realm r :kind k}) and friends do not exist; there is no realm coordinate in the public read grammar. A registrar-query map without :frame is an error (:rf.error/registrar-query-needs-frame) — a map is never a "default realm" read. There is no re-frame.realm namespace and no realm-scoped reader to read; frame resolution routes directly through the process registrar (re-frame.registrar / re-frame.frame / re-frame.image). Tooling that needs the host app's registrations without rebinding to its own image generation (e.g. Xray's host-registry generation-bypass) reads the process source store directly via re-frame.registrar, not a realm coordinate.

Frame-targeted query. The map-shaped form — (registrations {:frame f :kind k}), (handler-meta {:frame f :kind k :id id}) — resolves the (kind, id) set through live frame f's own sealed image generation ("target frame → resolved image generation → registration resolution"), surfacing the :rf.provenance/ns + inline/image facts the resolved descriptors carry (cross-image overrides are reported on the generation's :rf.gen/shadows shadow report). :frame is a registered frame id (keyword) OR a frame value (rf/make-frame's return token) — the same target shape rf/frame-generation accepts; the public routing address is the frame id. This is the READ of the image→frame model: the public tooling surface a tool reaches instead of the internal re-frame.live-frame / re-frame.image-assembly namespaces. Fail-loud: a :frame that does not resolve to a live frame carrying a generation throws :rf.error/frame-no-generation (no fallback to the default registrar — the read needs a live frame). The dedicated raw read frame-generation (below) returns the whole sealed generation. The positional-keyword arity (default source store) is byte-identical — only a caller passing a {:frame …} map reaches the generation path.

API M/Fn Signature Status Tier JVM-runnable? Spec
registrations Fn (registrations kind) / (registrations kind pred-fn){id metadata-map}. Use when you want metadata — registry walks that read source-coords, :rf/sensitive, :rf/machine?, :platforms, etc. The positional-keyword arity reads the default source store. Frame-targeted: (registrations {:frame f :kind k}) → the {id metadata} for :kind resolved through live frame f's own sealed image generation (only the ids that frame's image carries, with :rf.provenance/ns provenance facts); :frame is a frame id or a direct frame object; an optional :pred filters. A map argument is ALWAYS a frame-targeted read — a map without :frame is an error (:rf.error/registrar-query-needs-frame); there is no :realm map arity. Fail-loud on an unresolvable :frame (see note above). v1 tooling 002
handler-meta Fn (handler-meta kind id) → registration-metadata map. View registrations include source-coord keys (:ns / :line / :column / :file) per :rf/source-coord-meta (Spec-Schemas); pair tools resolve data-rf2-source-coord DOM annotations to :file via this lookup. Frame-targeted: (handler-meta {:frame f :kind k :id id}) → the metadata for [k id] resolved through live frame f's sealed image generation (with :rf.provenance/ns provenance facts), or nil when that frame's image carries no such [k id]. A map argument is ALWAYS a frame-targeted read — a map without :frame is an error (:rf.error/registrar-query-needs-frame); there is no :realm map arity. Fail-loud on an unresolvable :frame (see note above). v1 tooling 002
frame-generation Fn (frame-generation f) → the sealed, resolved image generation live frame f is running — the inert image-assembly value with the four documented stable public keys :rf.gen/resolver ({[kind id] descriptor}), :rf.gen/images (the normalized images in :images order — later wins), :rf.gen/kinds, and :rf.gen/shadows (the cross-image shadow report — [{:registration [kind id] :image <defined-in> :shadowed-by <winner>} …]; [] when nothing was overridden — read the report directly off this key). f is a registered frame id or a direct frame object. The dedicated raw read over the frame→generation model — for describe-image-style views that want the whole generation (selected registrations, shadow report, provenance) without per-kind round-trips; the :frame arities of the pair above give per-(kind, id) resolution with provenance. Fail-loud :rf.error/frame-no-generation when f does not resolve to a live frame carrying a generation (no nil-as-default, no realm fallback). (There is no :rf.gen/requires capability slot — image-declared host capabilities are removed.) The public tooling surface tools (Pair MCP, Xray) reach instead of re-frame.live-frame / re-frame.image-assembly internals. EP-0023 tooling 002
frame-ids Fn (frame-ids) / (frame-ids ns-prefix) v1 tooling 002
frame-meta Fn (frame-meta frame-id) v1 tooling 002
app-db-value Fn (app-db-value frame-id) → the app-db partition value (plain map) — the out-of-band value read. The front-porch read is subscribe; app-db-value is the non-reactive snapshot read for tools, tests, REPL, and fx/handler bodies. v1 advanced 002
frame-state-value Fn (frame-state-value frame-id) → the coherent frame-state projection {:rf.db/app <app-db> :rf.db/runtime <runtime-db>}. The full-frame read for SSR / epoch / time-travel / Xray (EP-0001). The runtime-db-only read (retired runtime-db-value, rf2-t3lftq — API-shrink #3) is (:rf.db/runtime (frame-state-value frame-id)). v1 tooling 002

The static subscription-topology query and the runtime sub-cache snapshot are subscription-tooling surfaces, not re-frame.core facade reads. Reach them through their owning namespace re-frame.subs.tooling(sub-topology) (static dependency graph over the registrar) and (sub-cache-snapshot frame-id) (live cache state) — with subs/sub-topology / subs/sub-cache-snapshot as the JVM legacy aliases.

Schema-introspection accessors — app-schemas, app-schema-at, app-schemas-digest — are rowed canonically in §Schemas.

compute-sub is rowed canonically in §Testing (pure sub computation against an app-db value).


App values and composition (EP-0013)

Removed from the public facade. This section is retained as a stable anchor for inbound cross-references; the surface it once documented is no longer public.

The retired composition vocabulary is not on the public facade. The app/realm/module composition surface — rf/app / rf/module (+ the rf/app-registrations / rf/app-requires / rf/app-owns inspectors), rf/install! / rf/reinstall!, rf/realm / rf/dispose-realm!, and the realm reads rf/realm-ids / rf/frame-realm / rf/installed-app — is not part of re-frame.core. The public model is image → frame → event stream: a feature namespace registers ordinary reg-* forms; an rf/image selects them by :select-ns provenance (or defines them inline via :registrations) and is supplied to make-frame (see §Registration) via :images (the later image wins; the generation's :rf.gen/shadows report — read via frame-generation — names cross-image overrides); re-calling make-frame against the SAME :id with a new :images vector hot-reloads a frame's image generation in place (preserving frame memory; the reload diff is a read — generation-diff over two frame-generation values, not a bespoke verb); and a frame is addressed by its process-local frame id (or a frame value — the routing ops normalize a value to its id, so it is accepted directly; API-shrink #1 rf2-csbbwu), with no realm coordinate. There is no re-frame.realm namespace, no installed-app value, and no realm coordinate on any wire record (see Spec-Schemas §:rf/realm): frame resolution routes directly through the process registrar (re-frame.registrar / re-frame.frame / re-frame.image), not a realm. The app/realm/module construction model — and the EP-0013→EP-0023 migration mapping — is documented in EP-0023 §Backwards Compatibility and the EP history; it is not a live data surface (there are no rf/migration-map / rf/migration-explain facade reads). For tooling that reads a frame's resolved registrations, use the frame-targeted {:frame f …} registrar queries + frame-generation (§Public registrar query API).


Schemas

Namespace: the introspection surfaces below live in re-frame.schemas (artefact day8/re-frame2-schemas); consumers (:require [re-frame.schemas :as schemas]). They are not re-exported from re-frame.core — apps targeting schemas add the artefact and require the namespace directly. The registration macros (reg-app-schema / reg-app-schemas) live in re-frame.core and route through the schemas artefact at registration time. Per the §Conventions per-artefact namespace table.

reg-app-schema is rowed canonically in §Registration.

The :frame opt is a frame TARGET. Every schema opts surface that names a frame — the :frame key of reg-app-schema's metadata map, the opts of reg-app-schemas, and the {:frame …} / bare-frame-id arity of app-schemas / app-schema-at / app-schema-meta-at / app-schemas-digest — accepts either a frame-id keyword OR a frame value (rf/make-frame's return token), the same target shapes the registrar query API's :frame accepts. A frame value is normalized to its frame id (the routing address) before it keys the per-frame schema store, so a schema registered against a frame value is found by a later read-by-id (and vice versa). A bare frame value passed as the opts argument routes to its own frame, not the ambient frame. An explicit :frame that resolves to a non-keyword target (a string, a non-frame map, a vector) fails loud with :rf.error/app-schemas-bad-arg rather than silently becoming an unreachable registry key.

One preferred validator-extension path. To swap the validator / explainer / printer at boot (e.g. drop Malli for a clojure.spec or Zod-style port), use set-schema-fns! — the single bundle setter that installs all three from one map so they never drift mid-boot. The per-fn singletons set-schema-validator! / set-schema-explainer! / set-schema-printer! are retained as lower-level convenience setters for adjusting one fn in isolation; they remain advanced but are not the preferred boot path.

API M/Fn Signature Status Tier Spec
app-schemas Fn (app-schemas) / (app-schemas {:frame frame-id}) v1 tooling 010
app-schema-at Fn (app-schema-at path) / (app-schema-at path {:frame frame-id}) v1 tooling 010
app-schema-meta-at Fn (app-schema-meta-at path) / (app-schema-meta-at path opts-or-frame-id) — return the full registration-metadata map (:path, :schema, :frame, plus source-coords :ns / :line / :file and the rest of :rf/registration-metadata) for a registered app-db schema, or nil. Pair-tool and 10x consumers reach for this when they need the registration anchor (e.g. click-back-to-code); the lighter app-schema-at is the right call when only the schema value is needed. Per 010 §Schemas as a tooling/agent surface and Spec-Schemas §:rf/app-schema-meta. v1 tooling 010
app-schemas-digest Fn (app-schemas-digest) / (app-schemas-digest {:frame frame-id}) → string v1 tooling 010
set-schema-fns! Fn (set-schema-fns! {:validate validate-fn :explain explain-fn :print print-fn})the preferred schema-fns configuration path. Atomically install any subset of the validator / explainer / printer bundle from one map. Each key is optional; an absent key leaves the existing registration in place. The honest one-call substitute-Malli boot pattern (a Zod / clojure.spec port installs all three together so they never drift mid-boot — the reason this is the blessed entry point over the per-fn singletons below). :print nil coerces to the default EDN canonicaliser (parity with set-schema-printer!); :validate nil / :explain nil disable that fn. Last-write-wins per key; returns the installed bundle as a map {:validate … :explain … :print …} reflecting the live state of all three fns after the call (a bundle setter returns its bundle, not just the validator). Per 010 §Default validator and the validator-fn extension point. v1 advanced 010
set-schema-validator! Fn Lower-level single-fn setter — prefer set-schema-fns! for boot. (set-schema-validator! validate-fn) installs only the validator ((fn [schema value] truthy?), same shape as malli.core/validate) every dev-time schema-validation site routes through; the explainer/printer are left untouched. nil disables validation entirely. Default ships Malli's validate; this seam lets apps swap in their own validator to drop the Malli dep. Last-write-wins; returns the installed validator. Reach for it only when adjusting one fn in isolation (e.g. a runtime toggle); installing a port's full bundle at boot goes through set-schema-fns! so the three never drift mid-boot. Per 010 §Default validator and the validator-fn extension point. v1 advanced 010
set-schema-explainer! Fn Lower-level single-fn setter — prefer set-schema-fns! for boot. (set-schema-explainer! explain-fn) installs only the explainer used to enrich :rf.error/schema-validation-failure traces' :explain key. Companion to set-schema-validator!; subsumed by the :explain key of set-schema-fns!. Per 010 §Default validator and the validator-fn extension point. v1 advanced 010
set-schema-printer! Fn Lower-level single-fn setter — prefer set-schema-fns! for boot. (set-schema-printer! print-fn) installs only the schema-print companion the digest pipeline (per 010 §Schema digest) hashes. print-fn is (fn [schema-value] canonical-string) and MUST be pure + deterministic across runtimes. nil falls back to the default EDN canonicaliser so the digest is never undefined. Parallel to set-schema-validator! / set-schema-explainer!; subsumed by the :print key of set-schema-fns!. Non-Malli ports register their own serialiser so cross-runtime digest comparison reflects their port's contract. Per 010 §Default validator and the validator-fn extension point. v1 advanced 010
validate-at-boundary-interceptor Var (interceptor value) validate-at-boundary-interceptor — a pre-built interceptor value, not a fn (interceptor :id is :rf.schema/at-boundary); do not call it as a fn (invoking (rf/validate-at-boundary-interceptor ...) raises ArityException). Under EP-0022 a public :interceptors chain carries refs, not inline values: reference the registered :rf.schema/at-boundary interceptor by id (the value Var is the registration-boundary input, not a chain entry). v1 advanced 010

See 010 §Schemas for :schema metadata, validation timing, and dev/prod elision. (The framework does not accept a :spec metadata key on reg-* metadata; the key is :schema. Per MIGRATION §M-54.)


Event-emit (always-on, production-survivable)

Per 009 §What IS available in production (#2). A minimal always-on listener surface that survives :advanced + goog.DEBUG=false and delivers one tight record per processed event. Parallel to (not a fallback for) the dev-only trace surface; per-event only — no per-sub, per-fx, or per-:rf.event/db-changed records. Record shape {:event :event-id :frame :time :outcome :elapsed-ms} (the always-on event-emit substrate's own record fields, distinct from trace :tags keys); the :event slot is passed through rf/elide-wire-value once before fan-out, so schema-marked :sensitive? paths land as :rf/redacted and :large? paths land as :rf.size/large-elided. :outcome is one of :ok (clean settle), :error (the interceptor chain threw), :rolled-back (a candidate :db / machine-data transition was refused before install), :flow-error (a flow's :output threw), or :rejected (the :rf.schema/at-boundary interceptor refused the event's payload against the handler's :schema, so the handler never ran) — an aborted dispatch is never reported as a clean :ok. Two of those five behave differently under a release build, and the pair is worth reading together: :rolled-back has no producer in production (candidate validation is dev-only), while :rejected does, because boundary validation is ungated per 010 §Production builds. It is not the only schema check a release build keeps — C-000.35 settles that by what the check is for, not by who declared the schema it reads, and the framework's other load-bearing checks are ungated alongside it — but it is the only one this :outcome vocabulary names in its own right; the others throw on their own paths, and a throw reports :error. See 009 §What IS available in production for the full per-member contract.

Advanced corpus-wide hook — not the off-box default (EP-0015 §9). This registry fans an UNPROJECTED record across EVERY frame; it is not routed under any frame's egress policy. The NORMAL production observation surface for hosted back-ends (Datadog / Honeycomb / Sentry / …) is the frame-owned :observability :handled-events sink (Spec 015 §Frame-owned observability sink policy), declared on the frame's make-frame / frame-root config and wired with register-observability-sink!, which hands the sink an already-PROJECTED record. Reach for the corpus-wide listener only for an intentionally cross-frame hook or a record the sink routing does not carry.

Sensitive data marking is path-based per the upcoming data-classification mechanism (separate spec doc; in progress). The handler-meta :sensitive? annotation is removed.

Registration uses the stream-parameterized listener verb with the :events stream — (register-listener! :events id listener-fn) / (unregister-listener! :events id) (see §Observation listeners). The always-on event-emit registry stays addressable via re-frame.event-emit for internal consumers (which also own between-scenario clears).

Error-emit (always-on, production-survivable)

Per 009 §What IS available in production. Sibling of the event-emit surface above; runs through the always-on error-emit substrate. Survives :advanced + goog.DEBUG=false.

Advanced corpus-wide hook — not the off-box default (EP-0015 §9). This registry fans the record across EVERY frame UNPROJECTED — the :event vector is wire-elided, but the :exception rides RAW and no frame egress policy is applied, so raw owner-local data can leave a frame here. It is the documented exception to the always-on axis's 'structured data only' rule, for post-mortem shippers that need the host throwable + stack. The NORMAL off-box error-observation surface is the frame-owned :observability :errors sink (Spec 015 §Frame-owned observability sink policy), declared on the frame's make-frame / frame-root config and wired with register-observability-sink!, which PROJECTS the record under the owning frame's classification + the sink's egress profile (sensitive paths redacted, :exception dropped under :rf.egress/public-error) BEFORE the sink sees it. Off-box shippers (Sentry / Honeybadger / Rollbar) should prefer the frame sink; reach for the corpus-wide listener only for an intentionally cross-frame hook or a frameless record the sink routing does not carry.

The listener payload is a union of three record shapes: (a) the per-event error record{:error :event :event-id :frame :time :exception :elapsed-ms} — fanned out by dispatch-on-error! once per catalogued production-reachable per-event runtime :rf.error/* event; (b) the frame-teardown report{:error :rf.error/frame-teardown-failed :frame :hook-failures :reason :recovery :time} — one bounded record per frame destroy whose best-effort cleanup hooks threw (EP-0008 promotion criterion; see 009 §Observability channels and the promotion criterion); and (c) the six EP-0008-promoted SSR non-event categories (:rf.error/ssr-render-failed, :rf.error/ssr-streaming-writer-failed, :rf.error/malformed-hydration-payload — incl. a pre-frame frameless :frame nil sub-path — :rf.error/ssr-head-resolution-failed, :rf.error/sanitised-on-projection, :rf.error/ssr-ring-error-view-failed), flat union records {:error :frame :time …category keys…}. The two non-event arms (b) and (c) ride the general dispatch-error-record! helper and carry no :event / :event-id; the teardown report carries a :hook-failures vector instead of the per-event :exception / :elapsed-ms slots. Listener bodies MUST branch on (:error record) (or otherwise tolerate a record with no top-level :event / :exception) rather than assuming the per-event shape. The per-event record's :event slot is passed through rf/elide-wire-value once before fan-out, so schema-marked :sensitive? paths land as :rf/redacted and :large? paths land as :rf.size/large-elided. This is the single error-observability surface; recovery is the framework's typed per-category default, not app-steerable (the per-frame :on-error recovery policy was removed). Per-listener exceptions are isolated — a buggy listener cannot block siblings or the run.

Sensitive data marking on the error-emit substrate is path-based per the upcoming data-classification mechanism (separate spec doc; in progress). The handler-meta :sensitive? annotation is removed — the per-path elision wire-walker is the sole redaction surface on this path.

Registration uses the stream-parameterized listener verb with the :errors stream — (register-listener! :errors id listener-fn) / (unregister-listener! :errors id) (see §Observation listeners). The always-on error-emit registry itself stays addressable via re-frame.error-emit + the :error-emit/register-error-listener! late-bind hooks for internal consumers (router fan-out, the SSR error projector), which also own between-scenario clears.

Observation listeners

One stream-parameterized listener verb registers an observation callback across the four pure listener streams — the differentiator is data (which stream), so it rides in a leading required stream keyword (in place of per-channel register-(trace|event|error|epoch)-listener! pairs). The closed stream vocabulary is :trace / :events / :errors / :epoch; an unknown stream throws :rf.error/unknown-listener-stream (no bare trace default, no compatibility aliases). The frame-owned observability sink is a distinct verb, NOT a :sink stream.

Stream Axis Record
:trace dev-only (DCE'd in production) one trace event per call
:events always-on (survives :advanced + goog.DEBUG=false) one tight event-record per processed event, fanned across EVERY frame, :event wire-elided but otherwise unprojected — the ADVANCED corpus-wide hook, NOT the off-box default (the frame-owned :observability sink is)
:errors always-on one error-record per :rf.error/* event, fanned across EVERY frame, :event wire-elided, :exception RAW — the ADVANCED corpus-wide hook, NOT the off-box default
:epoch optional artefact (dev-only) one :rf/epoch-record per dequeued event; no-op returning nil when day8/re-frame2-epoch is absent
API M/Fn Signature Status Tier Spec
register-listener! Fn (register-listener! stream id listener-fn) — register listener-fn under id on stream (:trace / :events / :errors / :epoch). Re-registering the same id on the same stream replaces. Returns id (or nil on :epoch when the epoch artefact is absent). Unknown stream throws :rf.error/unknown-listener-stream. v1 tooling 009
unregister-listener! Fn (unregister-listener! stream id) → nil. No-op on :epoch when the epoch artefact is absent. v1 tooling 009

There is deliberately no facade clear-listeners! verb: dropping every listener on a stream is a test-isolation concern owned by the fixture layer, not the public facade. re-frame.test-support's reset clears the registries through the lower-level sinks directly (re-frame.trace.tooling/clear-listeners!, re-frame.event-emit/clear-event-listeners!, re-frame.error-emit/clear-error-listeners!, and the :epoch/clear-epoch-listeners! reset hook) — the former per-stream clear-listeners! façade verb was retired in API-shrink #4.

Tracing

All tracing is dev-only (elided in production). See 009 §Tracing for emit semantics and synchronous listener delivery. Trace-listener registration uses the stream-parameterized listener verb with the :trace stream.

API M/Fn Signature Status Tier Spec
emit-trace-event! Fn (emit-trace-event! op-type operation tags) → nil v1 (dev-only) tooling 009
re-frame.interop/debug-enabled? Var ^boolean. CLJS: alias of goog.DEBUG — constant-folded by Closure under :advanced, so :advanced + goog.DEBUG=false builds DCE every (when interop/debug-enabled? ...) branch. JVM: a def read ONCE at ns-load from the Java system property -Dre-frame.debug (winning on conflict) or the environment variable RE_FRAME_DEBUG; defaults true (dev parity). Accepts the conventional false-y vocabulary case-insensitively (false, 0, no, off, empty string) with whitespace trimmed; anything else leaves the flag at true. Set BEFORE re-frame.interop loads. SSR / webhook receivers / long-running JVMs facing untrusted input MUST set the gate false explicitly — per 009 §JVM builds and Security §Production gates. v1 tooling 009
re-frame.performance/enabled? Var ^boolean goog-defined (CLJS) / ^:const false (JVM). Set via :closure-defines {re-frame.performance/enabled? true} to bracket event dispatch / sub recompute / fx walk / view render, emitting a single options-bag performance.measure(name, {start, end}) per bracket (User-Timing measure entries rf:event:*, rf:sub:*, rf:fx:*, rf:render:*; no performance.mark entries are allocated). The measure is cleared by name after emit unless the companion re-frame.performance/retain-entries? goog-define (default false) is set — when set, entries persist in the host User-Timing buffer for one-shot getEntriesByType("measure") readers. Compile-time only — not a (rf/configure! ...) knob; runtime mutation has no effect. Default false; under :advanced + default the bracket DCEs and shipped binaries carry zero User-Timing instrumentation. CLJS-only — JVM is a no-op. See 009 §Performance instrumentation and Tool-Pair §Performance API consumption v1 tooling 009
trace-buffer Fn (trace-buffer frame-id) / (trace-buffer frame-id opts) → the named frame's event-keyed ring, oldest-first (event bundles by default; {:flat true} for raw trace events). [] for a destroyed / never-registered frame and in production v1 (dev-only) tooling 009
clear-trace-buffer! Fn (clear-trace-buffer! frame-id) → nil — empty the named frame's ring. No-op for an unknown frame and in production v1 (dev-only) tooling 009
(rf/configure! {:trace-buffer {:events-retained N}}) See §Configure keys. v1 (dev-only) — (configure key) 009
group-by-event Fn (group-by-event events) → vector of event bundles {:dispatch-id :event :handler :fx :effects :subs :renders :other}, sorted by emission order. Pure data; JVM-runnable. Re-exported from re-frame.trace.projection (see 009 §Event-bundle projection). v1 (dev-only) tooling 009
domino-bucket Fn (domino-bucket trace-event)#{:event :handler :fx :effect :sub :render :other}. Classifies a raw trace event into the six-domino slot used by group-by-event. Pure data. v1 (dev-only) tooling 009
group-by-event-with-events Fn (group-by-event-with-events events) — like group-by-event, but each event bundle additionally carries :trace-events (the raw events that composed it), keyed by the same frame-scoped [frame dispatch-id] grouping. Pure data; JVM-runnable v1 (dev-only) tooling 009

Trace-emission opt-out (per-handler metadata)

Event-handler registration accepts a :rf.trace/no-emit? true metadata flag. When set, the runtime suppresses every trace emission and event-emit record within the handler's scope — the handler runs invisibly to the trace surface, the event-emit substrate, and (transitively) the epoch buffer. Used by framework-internal bookkeeping handlers (Xray, Story, re-frame2-pair-mcp, story-mcp) that would otherwise saturate the trace stream. Per Conventions §Reserved namespaces the :rf.trace/* namespace is framework-owned.

Metadata key Where Value Default Effect
:rf.trace/no-emit? reg-event metadata map boolean false When true, suppresses all trace + event-emit emissions inside the handler's scope. Per 009 §Trace-emission opt-out.
:rf.trace/frame-no-emit? frame config map (make-frame / frame-root) boolean false When true, marks the frame a tool / inspector frame: the runtime suppresses every trace emission tagged with that frame, so the inspector's own reactivity does not flood the shared ring it inspects. The frame-scoped sibling of :rf.trace/no-emit?. Per 009 §Frame-level trace-emission opt-out.

Epoch history (per Tool-Pair)

Per-frame epoch snapshots, recorded once per dequeued event (at each event's run-to-completion boundary — not once per drain: a drain that processes a parent event and the :fx [[:dispatch …]] child it queued commits two records) in dev builds. Used by pair-shaped tools for time-travel and post-mortem analysis. Production builds elide entirely.

All rows below are the Tool-Pair time-travel surface — pair-shaped dev tools (Xray, the pair-MCP servers), so they tier tooling (the classification guidance also calls epoch-listener registration "advanced power-user"; either way the surface is back-room and opt-in — never front-porch). The state-injection member is the ONE partial-map mutator replace-frame-state! (rf2-t3lftq — API-shrink #3 consolidated the former replace-app-db! / reset-app-db! / replace-runtime-db! / replace-frame-state! four-mutator family into this): a present partition key replaces that partition, an absent key is preserved — a db-shaped key never silently replaces the other partition.

API M/Fn Signature Status Tier Spec
epoch-history Fn (epoch-history frame-id) → vector of epoch records. Returns [] for an unknown / destroyed frame (per Tool-Pair §Surface behaviour against destroyed frames). v1 (dev-only) tooling Tool-Pair
restore-epoch! Fn (restore-epoch! frame-id epoch-id) → boolean (true on success). Rewinds to the record's canonical :frame-state-after (BOTH partitions — reviving machines / routes / elision / SSR), not just the app-db projection (EP-0001). Returns false and emits a structured trace on any of its seven failure modes — an unknown / destroyed frame (:rf.error/no-such-handler, kind :frame), plus the six :rf.epoch/restore-* preconditions (drain-in-progress, unknown epoch, non-ok record, schema-digest mismatch, missing handler, machine version mismatch) enumerated in the epoch trace-events table below (per Tool-Pair §Surface behaviour against destroyed frames). v1 (dev-only) tooling Tool-Pair
replace-frame-state! Fn (replace-frame-state! frame-id frame-state) → boolean — atomically install a PARTIAL frame-state map (any subset of {:rf.db/app … :rf.db/runtime …}); a present key replaces that partition, an absent key is preserved unchanged. The ONE frame-state write surface: an app-only map ({:rf.db/app v}) is the former replace-app-db!; {:rf.db/app {}} is the former reset-app-db!; a runtime-only map ({:rf.db/runtime v}) is the former replace-runtime-db!; a both-key map is the full-frame atomic install for tool-driven replay / fixture install. Records a synthetic epoch. Rejects a map with no recognized partition key, or an unrecognized key, as :rf.error/replace-frame-state-bad-keys (checked before frame resolution). Emits :rf.error/no-such-handler (kind :frame) / returns false for an unknown / destroyed frame. v1 (dev-only) tooling Tool-Pair
(rf/configure! {:epoch-history {:depth N}}) See §Configure keys. v1 (dev-only) — (configure key) Tool-Pair
epoch-silence-current? Fn (epoch-silence-current? tags)true when a received :rf.epoch.cb/silenced-on-frame-destroy signal still names a CURRENT fact: the carried :observed-gen is still the generation registered under :cb-id, AND that registration is not observing :frame right now. false otherwise — including an absent/nil :observed-gen, and when the artefact is absent. THE supported receiver decision: pass the signal's :tags map straight back. ONE atomic operation, not two composable reads (rf2-uhouu) — registration identity (a same-id replacement or unregister-drop makes a different generation current) and observation continuum (a same-id successor frame re-arms by DELIVERY, which mints no generation, so :observed-gen still matches while the callback is live again) are weighed under a SINGLE ledger snapshot. Composing those two facts from separate reads is not linearizable: a replacement landing between them accepts a silence for an already-superseded registration, an answer no single point in time held — which is why the low-level halves are not published (per Tool-Pair §Surface behaviour against destroyed frames + 009 §The delayed-silence emission linearization law). v1 (dev-only) tooling Tool-Pair
app-db-value / frame-state-value (cross-ref to §Public registrar query API) Fn Partition readers — app-db, and the coherent frame-state projection (the runtime-db-only read is (:rf.db/runtime (frame-state-value frame-id))). Each returns nil for an unknown / destroyed frame (per Tool-Pair §Surface behaviour against destroyed frames). v1 advanced/tooling 002

Epoch-settled listeners are the :epoch stream of the stream-parameterized listener verb(rf/register-listener! :epoch id callback-fn) / (rf/unregister-listener! :epoch id) — not a separate facade fn (the per-channel register-epoch-listener! / unregister-epoch-listener! pair was retired in API-shrink #4, rf2-9flalp; the epoch stream registers through the one verb exactly like :trace / :events / :errors). The callback is a record-publication notification, not a once-per-event clock: an ordinary handled event publishes one record when it settles, and the SAME record re-publishes — carrying the same :epoch-id — when a post-settle render / sub-run / unmount back-fills into that already-settled epoch; synthetic records publish too (:rf.epoch/db-replaced per replace-frame-state! write and :halted-depth at the depth ceiling, both ring-retained when depth permits, plus the terminal :halted-destroy — an already-started event interrupted by frame destruction, delivered to listeners only and never retained). Because the listener is process-global while :epoch-id is unique only within one frame, reconcile on the pair [(:frame record) (:epoch-id record)] — cache under that key and replace on re-publication rather than counting callbacks; :outcome is record state (:ok / :halted-depth / :halted-destroy), not identity. A dequeued event rejected before it runs (no handler) publishes nothing. id may be any comparable value and re-registering the same id replaces. Process-global; a callback whose previously-observed frame is destroyed receives a one-shot :rf.epoch.cb/silenced-on-frame-destroy trace (per Tool-Pair §Surface behaviour against destroyed frames) carrying :observed-gen — the generation the silence is attributed to. A consumer decides whether a received signal is current with epoch-silence-current? (row above), the one supported receiver operation: it discards a signal owed to a replaced/dropped registration AND one superseded by a fresh delivery on the same registration, from a single ledger snapshot and without reading private registry state. Returns id, or nil when day8/re-frame2-epoch is absent.

Trace events emitted by epoch-history machinery:

:operation Tags
:rf.epoch/snapshotted :frame, :rf.epoch/id, :rf.trace/event-id, :outcome (the detailed cause enum :ok / :halted-depth / :halted-destroy; :halted-handler-exception is schema-reserved, not currently emitted)
:rf.epoch/outcome :frame, :rf.epoch/id, :rf.trace/event-id, :outcome (the consumer-facing :ok / :blocked / :error projection; fires paired with :rf.epoch/snapshotted per dequeued event)
:rf.epoch/restored :frame, :rf.epoch/id
:rf.epoch/db-replaced :frame, :rf.epoch/id
:rf.epoch/restore-unknown-epoch :frame, :rf.epoch/id, :history-size
:rf.epoch/restore-schema-mismatch :frame, :rf.epoch/id, :schema-digest-recorded, :schema-digest-current, :failing-paths
:rf.epoch/restore-missing-handler :frame, :rf.epoch/id, :missing
:rf.epoch/restore-version-mismatch :frame, :rf.epoch/id, :machine-id, :version-recorded, :version-current
:rf.epoch/restore-during-drain :frame, :rf.epoch/id
:rf.epoch/restore-non-ok-record :frame, :rf.epoch/id, :rf.epoch/outcome, :halt-reason
:rf.epoch/replace-during-drain :frame
:rf.epoch/replace-schema-mismatch :frame, :failing-paths
:rf.epoch/replace-history-disabled :frame (a replace-frame-state! precondition failure when the history ring is disabled)
:rf.error/replace-frame-state-bad-keys :frame, :reason (:no-recognized-keys / :unknown-keys), :keys (a replace-frame-state! precondition failure — no recognized partition key, or an unrecognized key; checked before frame resolution)
:rf.epoch.cb/silenced-on-frame-destroy :frame, :cb-id, :observed-gen (the reserved callback generation the silence is attributed to; a consumer calls epoch-silence-current? on the tags to discard a superseded signal, per 009 §The delayed-silence emission linearization law)
:rf.epoch.cb/listener-exception :frame, :cb-id, :rf.epoch/id, :message (an :epoch-stream listener callback threw)
:rf.warning/epoch-redact-fn-exception :frame, :rf.epoch/id, :ex-msg (an installed :redact-fn threw at projection egress)
:rf.warning/restore-quiesce-hook-exception :category, :hook, :frame, :exception (a restore-time async-quiesce hook threw)

Size-elision wire-boundary walker

Cross-reference: see Security.md §Privacy / secret handlingelide-wire-value is named there as the single normative emission site for the :rf/redacted sentinel. Every off-box egress (trace forwarders, MCP servers, error monitors) routes through this walker; the trust-boundary surfaces catalogued in Security.md compose against this primitive.

The framework primitive that walks tree-shaped values at the wire boundary and substitutes elision markers for sensitive or large slots. Consumed by every tool that emits wire data (the off-box error-monitor forwarders, the Xray-MCP / re-frame2-pair-mcp / story-mcp servers per Tool-Pair.md, the on-box dev panels). The walker is the single normative emission site for the :rf/redacted sensitive sentinel and the :rf.size/large-elided marker; per-tool reimplementation is prohibited.

API M/Fn Signature Status Tier Spec
elide-wire-value Fn (elide-wire-value v opts)v or an elision-marker substitution. opts is a map: {:rf.size/include-large? <bool> :rf.size/include-sensitive? <bool> :rf.size/include-digests? <bool> :rf.size/threshold-bytes <int> :path [...] :frame <frame-id>}. Defaults: both include-* flags false (maximum elision); :rf.size/threshold-bytes falls back to (rf/configure! {:elision ...}) then 16384. Walks v consulting [:rf.runtime/elision :declarations] and [:rf.runtime/elision :sensitive-declarations] of the named frame's runtime-db; substitutes :rf/redacted for sensitive slots and :rf.size/large-elided markers for large slots. Composition rule (normative): when both predicates match the sensitive drop wins — the size marker is suppressed because it would leak :path / :bytes / :digest. Per 009 §Size elision in traces and Spec-Schemas §:rf/elision-marker. v1 tooling 009

Commit-plane declaration path (EP-0025). The [:rf.runtime/elision] registry has exactly two slots: :declarations (:large? paths) and :sensitive-declarations (:sensitive? paths). Durable app-db classification rides the four commit-plane classification effects — a reg-event returns :large / :sensitive (or :clear-large / :clear-sensitive) alongside :db, installed by re-frame.elision/apply-classification-effects under :source :effect. A reg-app-schema {:large? true} / {:sensitive? true} slot prop is not a route into this registry: schemas describe shape and validation, not durable app-db egress policy (the schema's :sensitive? still drives schema-validation-failure-trace redaction, and machine / resource :data-schema per-slot props still classify owner-local data). The registry's declaration readers (re-frame.elision/declarations / sensitive-declarations) and the derived-tree value-match arms are reachable through the re-frame.elision home namespace; the user-facing façade exposes only elide-wire-value and project-egress. Per 015 §Durable app-db — the four commit-plane effects and implementation/core/src/re_frame/elision.cljc L1-16.

Record-level egress projection (EP-0015 / Spec 015)

Cross-reference: 015 §Projection is the normative home; project-egress is the public, record-level boundary primitive layered over elide-wire-value. The six-member :rf.egress/* profile enum is the named-boundary vocabulary; the boolean :rf.size/* flags remain the advanced override layer beneath it (EP-0015 §10/§11).

rf/project-egress is the required projection step before any off-box sink. It dispatches on a record's :kind (the :rf.observe/* record kinds) to a private per-kind projector — only project-egress is public (EP-0015 issue 2: the name names the boundary, not a record kind) — and delegates every tree-shaped slot to rf/elide-wire-value. A profile resolves to a :rf.size/* opt-set; an explicit :rf.size/* boolean composes on top (the override wins).

API M/Fn Signature Status Tier Spec
project-egress Fn (project-egress record-or-value) / (project-egress record-or-value opts) → a value safe to ship under the resolved profile. opts conforms to :rf/project-egress-opts: {:rf.egress/profile <closed six-member enum> :frame <frame-id> :path [...] :rf.size/include-sensitive? <bool> :rf.size/include-large? <bool> :rf.size/include-digests? <bool> :rf.size/threshold-bytes <int> :as-of-epoch <epoch>}. Dispatches on a record's :kind (:rf.observe/handled-event / :rf.observe/error) to a private per-kind projector, falling back to walking a kindless input as a tree-shaped value (the direct-read path); delegates tree-shaped slots to elide-wire-value. An :rf.observe/* record is frame-bearing: when opts omit :frame, the record's own top-level :frame (its owning frame) is seeded as the governing frame; an explicit :frame opt wins (override). Profile resolves to a :rf.size/* opt-set; explicit :rf.size/* booleans compose on top (override wins). Unknown profile raises :rf.error/unknown-egress-profile (closed enum). Fail-closed when no frame is known from the opt, the record, or the carried scope (no :rf/default synthesis). Per 015 §project-egress. v1 tooling 015

Frame-owned observability sinks (EP-0015 §9 / Spec 015)

Cross-reference: 015 §Frame-owned observability sink policy is the normative home. The normal production observability story (Datadog / Sentry / Honeycomb): an app declares a sink under a frame's :observability config ({:handled-events [{:sink <id> :rf.egress/profile … :opts {…}}] :errors [...]}) and registers the concrete sink fn against that <id> with rf/register-observability-sink!. The runtime routes one :rf.observe/handled-event record per processed event and one :rf.observe/error record per :rf.error/* site through rf/project-egress — under the owning frame's classification and the entry's egress profile (default :rf.egress/off-box-observability) — to the declared sink. Sinks consume already-projected records only (no sink-local redaction). Always-on (survives :advanced + goog.DEBUG=false). The lower-level corpus-wide event-emit / error-emit registries (the :events / :errors streams of register-listener!) remain for advanced integration. Routing is fail-closed: an unresolved frame or absent :observability policy routes nothing (no :rf/default synthesis), and a throwing sink is isolated from its siblings.

API M/Fn Signature Status Tier Spec
register-observability-sink! Fn (register-observability-sink! sink-id f) — register the concrete observability sink fn f under the keyword sink-id the frame's :observability {:sink <sink-id> …} entry names. f receives one already-projected :rf.observe/handled-event / :rf.observe/error record (projected under the owning frame's classification + the entry's egress profile); its return value is ignored. Re-registering the same id replaces. Returns sink-id. The framework ships no Datadog / Sentry client (EP-0015 Non-Goals); the sink fn is an app / integration-library concern. Always-on. Per 015 §Frame-owned observability sink policy. v1 tooling 015
unregister-observability-sink! Fn (unregister-observability-sink! sink-id) → nil v1 tooling 015

DOM source-coord annotations (mandatory)

Per Spec 006 §Source-coord annotation and Tool-Pair §Source-mapping, every adapter whose host has a DOM-attribute concept MUST inject data-rf2-source-coord="<ns>:<sym>:<line>:<col>" on the rendered root DOM element of each registered view. Format and exemptions (Fragments, non-DOM roots) are documented in Spec 006 §Source-coord annotation. Annotation is gated on interop/debug-enabled? (the CLJS mirror of goog.DEBUG); production :advanced builds elide the attribute via dead-code elimination — there is no DOM-bytes cost in shipped bundles. On the JVM the same annotation is stamped at the reg-view* registration boundary — a debug-gated wrapper on the stored :handler-fn (re-frame.views.jvm-source-coord-annotation), the server-side twin of the CLJS substrate wrappers — not by the SSR emitter, which carries no annotation logic and merely serialises the hiccup the registration boundary already annotated; see Spec 011 §Source-coord annotation under SSR.

Error contract

Errors are emitted as structured trace events with :op-type :error (or :warning / :info / :fx / :flow / :frame) and a per-category :operation keyword. The complete normative catalogue — every :rf.error/*, :rf.warning/*, :rf.fx/*, :rf.cofx/*, :rf.ssr/*, :rf.epoch/*, :rf.flow/*, :rf.http/*, :rf.http.interceptor/*, :rf.frame/*, and :rf.route.nav-token/* event the runtime emits — lives at 009 §Error event catalogue (single source of truth for category names, :op-type discriminator, trigger conditions, default :recovery, and :tags payload keys). Per-category Malli :tags schemas are canonicalised at Spec-Schemas §Per-category :tags schemas — one schema per catalogue row.

Recent additions consumers should be aware of: :rf.ssr/version-mismatch, :rf.ssr/schema-digest-mismatch, :rf.ssr/compatibility-check-skipped (the SSR hydration compatibility-check trio,), and :rf.cofx/skipped-on-platform (the platform-gating mirror of :rf.fx/skipped-on-platform). The catalogue at 009 is the single source of truth — do not duplicate the table here.

Per-Spec emit-sites: 002-Frames, 005-StateMachines, 006-ReactiveSubstrate, 010-Schemas, 011-SSR, 012-Routing, 013-Flows, 014-HTTPRequests, Tool-Pair. Each catalogue row's "Per [N]" cross-link names the owning Spec section.

Privacy (Spec 009 §Privacy / sensitive data in traces)

Cross-reference: see Security.md §Privacy / secret handling for the framework-wide pattern-level posture; the trust-boundary catalogue lives in Security.md. The cross-artefact inventory + composition order (every privacy surface in re-frame.core, re-frame.http, re-frame.schemas, re-frame.epoch, tools/mcp-base, with the data-flow from handler exit to off-box wire) lives in Privacy.md. The public classification model is the four commit-plane classification effects (:sensitive / :large / :clear-sensitive / :clear-large, returned by a reg-event alongside :db) for durable app-db (see 015 §Durable app-db — the four commit-plane effects) and registration-owned :sensitive payload classification on reg-event / reg-sub / reg-flow, projected at trust boundaries by project-egress and the :rf.egress/* profiles. The imperative add-marks / set-marks path-marks API, the re-frame.marks namespace, the frame :sensitive {:app-db …} annotation, and the positional redact-interceptor are all removed; the marks projection substrate lives in the marks-free re-frame.classification / re-frame.elision engine.

Per Spec 009 §Privacy the runtime stamps :sensitive? true at the top level of every trace event emitted inside the scope of a handler whose declared path overlap classifies sensitivity. (The legacy handler-meta :sensitive? annotation has been removed; sensitive-data marking is path-based per the data-classification mechanism in Spec 015.) Framework-published trace consumers (Sentry/Honeybadger forwarders, re-frame2-pair server, Xray, Story, story-mcp, re-frame2-pair-mcp) MUST default-drop the stamped events at their egress boundary.

API M/Fn Signature Status Tier Spec
sensitive? Fn (sensitive? trace-event)boolean. True iff trace-event is a map carrying :sensitive? true at the top level (not under :tags). The framework-published predicate every consumer composes against — replaces per-consumer reimplementations of the same five-token check. v1 tooling 009

Spec-internal schemas

Per Spec-Schemas.md, the spec's own runtime shapes are described as Malli schemas registered at runtime. These are the conformance contract an implementation validates against.

Schema Describes Spec
:rf/dispatch-envelope Internal envelope wrapping every dispatch 002
:rf/dispatch-opts The user-facing opts map for dispatch / dispatch-sync / subscribe 002
:rf/registration-metadata Common metadata-map shape across reg-* 001 / 010
:rf/effect-map Return value of reg-event handlers — closed: #{:db :rf.db/runtime :fx} (:rf.db/runtime reserved by convention for framework authority; app handlers use only :db / :fx) 002
:rf/trace-event Universal trace event shape 009
:rf/error-event Refinement of :rf/trace-event for :op-type :error / :warning (unified error/warning envelope) 009
:rf/handler-body-dsl Conformance corpus handler-body DSL (host-agnostic event/sub bodies; small-DSL grammar) 008 / Spec-Schemas
:rf/transition-table State-machine transition table grammar 005
:rf/machine-snapshot Runtime snapshot of a machine instance 005
:rf/hydration-payload Wire format for SSR hydration 011
:rf/response HTTP-response accumulator owned by the request frame during SSR 011
:rf.server/cookie Structured-cookie shape for :rf.server/set-cookie / :rf.server/delete-cookie 011
:rf/head-model SSR head/meta data model (title, meta, link, json-ld, html/body attrs) 011
:rf/public-error Sanitised, client-safe projection of an internal error trace event 011
:rf.fx.server/set-status-args / :rf.fx.server/set-header-args / :rf.fx.server/append-header-args / :rf.fx.server/set-cookie-args / :rf.fx.server/delete-cookie-args / :rf.fx.server/redirect-args / :rf.fx.server/safe-redirect-args Args of standard :rf.server/* SSR fx 011
:rf/frame-meta Returned by (frame-meta frame-id) 002
:rf/variant Story-variant artefact contract (post-v1 lib) — variants are data, no fn-valued slots 007
:rf/epoch-record Per-frame epoch snapshot record (Tool-Pair) Tool-Pair
:rf.fx/dispatch-args Args of standard :dispatch fx (and :raise, same shape) 002 / 005
:rf.fx/dispatch-later-args Args of standard :dispatch-later fx 002
:rf.fx/http-args Args of :http fx (user-owned recommendation) Pattern-RemoteData
:rf.fx.nav/push-url-args Args of :rf.nav/push-url fx 012
:rf.fx.nav/replace-url-args Args of :rf.nav/replace-url fx 012
:rf.fx.nav/scroll-args Args of :rf.nav/scroll fx 012
:rf.fx.nav/capture-scroll-args Args of :rf.nav/capture-scroll fx 012
:rf.fx/with-nav-token-args Args of :rf.route/with-nav-token fx wrapper 012
:rf.fx/spawn-args Args of :rf.machine/spawn fx (the canonical actor-lifecycle fx-id; emitted from any event handler's :fx) 005
:rf.fx/managed-args Args of :rf.http/managed fx (request envelope, decode, accept, retry, timeout-ms, on-success/on-failure, request-id, abort-signal) 014
:rf.fx/managed-abort-args Args of :rf.http/managed-abort fx (request-id) 014
:rf.http/reply Canonical reply envelope {:status :ok :value v …} / {:status :error :error {:kind <:rf.http/*> …} …} / {:status :cancelled :error {:kind :rf.http/aborted …} …} appended to the reply target (:reply-to unified, or :on-success/:on-failure split sugar) 014
:rf/route-rank Structural-rank tuple for route-precedence sorting 012
:rf/pending-navigation Pending-navigation slot when :can-leave guard rejects 012
:rf/elision-registry Per-frame size-elision declaration registry in the reserved runtime-db child [:rf.runtime/elision] 009
:rf/elision-marker Wire shape rf/elide-wire-value substitutes for an elided large value (:rf.size/large-elided) 009
:rf/project-egress-opts The opts map rf/project-egress accepts (:rf.egress/profile + advanced :rf.size/* overrides) 015

Schemas are open by default (consumers tolerate unknown keys; producers grow shapes additively); :closed true is opt-in at boundary-validation sites and on the effect-map.


Testing

The testing surface lives across three namespaces. re-frame.core carries the production primitives that double as testing entry points (make-frame, with-frame, with-new-frame, dispatch-sync, with-fx-overrides, app-db-value, compute-sub, machine-transition, sub-topology). re-frame.test-support ships the test-only fixture machinery and test-flavoured helpers. re-frame.test-helpers ships the view-assertion helpers (hiccup-walk + testid authoring). re-frame.test-support does not re-export from re-frame.core — a test file requires both [re-frame.core :as rf] and [re-frame.test-support :as ts], and additionally [re-frame.test-helpers :as th] for view-assertion tests. See 008-Testing.md for fixtures, framework adapters, and re-frame-test compatibility.

API M/Fn Signature Status Tier Spec Notes
assert-path-equals Fn (assert-path-equals path expected-val) / (assert-path-equals path expected-val opts) v1 testing 008 Path-form sync assertion. Mismatch fires clojure.test/is-style failure via do-report. Lives in re-frame.test-support. Mirrors the :rf.assert/path-equals event used inside a Story :script block — same name root so the fn-side and event-side are navigable without a translation table. The wider sibling event family (:rf.assert/sub-equals, :rf.assert/state-is, :rf.assert/dispatched?, :rf.assert/no-warnings, :rf.assert/effect-emitted, :rf.assert/path-matches) lives in 007 §Play functions; runner and reporting channel differ. Choose by test surface: assert-path-equals from a deftest body, :rf.assert/path-equals from a story variant's :script vector.
poll-until Fn (poll-until pred) / (poll-until pred opts) v1 testing 008 Bounded-deadline poll. JVM: synchronous — returns the truthy value, throws ex-info carrying :rf.error/id :rf.error/poll-until-timeout (the canonical discriminator, per Spec 009) on timeout. CLJS: returns a js/Promise resolving with the truthy value or rejecting on timeout. Opts: :timeout-ms (default 2000), :interval-ms (default 5), :label. Lives in re-frame.test-support.
with-fx-overrides M (with-fx-overrides {fx-id -> override, …} body+) v1 testing 002, 008 Lexical-scope :fx-overrides binding. Every dispatch / dispatch-sync inside the body merges the supplied map into its envelope's :fx-overrides. Precedence: per-call opt > lexical with-fx-overrides > per-frame :fx-overrides. Composes with with-frame. Lives in re-frame.core.
compute-sub Fn (compute-sub query-v db) v1 testing 008 Pure sub computation against an app-db value. Lives in re-frame.core.
snapshot-registrar / restore-registrar! / make-reset-runtime-fixture Fn per docstring v1 testing 008 Fixture machinery. make-reset-runtime-fixture builds the :each fixture (registrar snapshot/restore + per-process reset); the raw snapshot-registrar / restore-registrar! pair composes hand-rolled fixtures. Lives in re-frame.test-support.
with-trace-recorder! M (with-trace-recorder! [recs-sym opts?] body+) v1 testing 008 Bracket body with a fresh trace-tooling listener that accumulates matching trace events into an atom bound to recs-sym; unregisters in a finally. Opts: :pred, :shape (:flat / :by-op), :key. Lives in re-frame.test-support.

Testing — view-assertion helpers

re-frame.test-helpers ships the hiccup-walk view-assertion surface — call the view-fn directly, walk the returned hiccup, assert on content or invoke a handler. JVM-runnable; no JSDOM, no React, no act(). Pairs with render-to-string (the HTML-string view-test path per Spec 011): hiccup-walk for structure / handler assertions, render-to-string for HTML-markup assertions. Per 008-Testing §View-assertion helpers.

API M/Fn Signature Status Tier Spec Notes
expand-tree Fn (expand-tree tree) → tree v1 testing 008 Recursively expand fn-components and Form-3 class components inside a hiccup tree. After expansion every vector's first element is a keyword tag or a non-component value. Lives in re-frame.test-helpers.
attrs Fn (attrs node) → map? v1 testing 008 Return the attrs map of a hiccup node, or nil. Lives in re-frame.test-helpers.
children Fn (children node) → vector v1 testing 008 Return the child elements — everything after the tag (and optional attrs map). Lives in re-frame.test-helpers.
text-content Fn (text-content node) → string v1 testing 008 Recursively collect string leaves under node and join. Numbers coerce to strings; nils are skipped. Lives in re-frame.test-helpers.
extract-handler Fn (extract-handler node event-key) → fn? v1 testing 008 Return the value of event-key from node's attrs map, or nil. Lives in re-frame.test-helpers.
find-by-attr Fn (find-by-attr tree attr val) → node? v1 testing 008 First hiccup node whose attrs map carries attr == val, or nil. Generic over the attribute keyword (:data-testid, :data-test, :id, custom). Lives in re-frame.test-helpers.
find-all-by-attr Fn (find-all-by-attr tree attr val) → vector v1 testing 008 Every matching node, in depth-first order. Lives in re-frame.test-helpers.
find-by-attr-prefix Fn (find-by-attr-prefix tree attr prefix) → vector v1 testing 008 Every node whose attr value (a string) STARTS with prefix. Non-string attr values do not match. Lives in re-frame.test-helpers.
find-by-testid Fn (find-by-testid tree test-id) → node? v1 testing 008 Convenience over find-by-attr keyed on :data-testid. Lives in re-frame.test-helpers.
find-all-by-testid Fn (find-all-by-testid tree test-id) → vector v1 testing 008 Convenience over find-all-by-attr keyed on :data-testid. Lives in re-frame.test-helpers.
find-by-testid-prefix Fn (find-by-testid-prefix tree prefix) → vector v1 testing 008 Convenience over find-by-attr-prefix keyed on :data-testid. Lives in re-frame.test-helpers.
invoke-handler Fn (invoke-handler node event-key & args) → any v1 testing 008 Find the handler under event-key on node and call it with args. Returns the handler's return value. THROWS when node is not a hiccup vector, the node has no attrs map, or no handler is registered — the throwing failure mode is deliberate (a missing handler is almost always a test bug). Lives in re-frame.test-helpers.
testid Fn (testid id) / (testid id extra) → map v1 testing 008 Build an attrs map carrying :data-testid id. The 2-arity merges extra into the map; :data-testid always wins on collision. Authoring helper at the view call site. Lives in re-frame.test-helpers.

The single-frame view test is a composition, not a bespoke fixture: re-frame.test-support/make-reset-runtime-fixture (an :adapter + optional :init-fn) seats the ambient :rf/default frame and runs the install thunk; the walkers above call the root view fn directly and assert on the returned hiccup; re-frame.test-support/poll-until covers the async case. Per 008-Testing §Single-frame view test.


Standard interceptors

Under EP-0022 the public interceptor-authoring surface is reg-interceptor (§Registration), and event/frame :interceptors chains carry interceptor references (a bare keyword id or [id arg]), never inline values. The v2 framework-standard interceptor surface is exactly one interceptor — :rf.interceptor/path — referenced as [:rf.interceptor/path <path-vector>]. There is no public rf/path value constructor and no standard unwrap (EP-0022 §No standard unwrap — handler destructuring, or a project-registered interceptor for intentional chain-wide event reshaping, replaces it). The earlier line (keep specific-work helpers, drop trivial (->interceptor :before f) ones) narrows to path-only because path is coupled to app-db commit no-op semantics and justifies the :factory mechanism. Five v1 interceptors removed: debug, trim-v, on-changes, enrich, after (per MIGRATION §M-21).

inject-cofx / inject-cofx* — the v1 coeffect-injection interceptors — are removed (no alias). They are not on the public re-frame.core facade and carry no API-manifest row (a removed surface is not part of the canonical public API): there is no public inject-cofx var to call. Coeffect delivery is no longer a chain member: a handler declares :rf.cofx/requires on its registration metadata and the value-returning supplier's result arrives flat in the coeffects map (§Registration — reg-cofx, 001 §:rf.cofx/requires). The removal alarm survives internally: a stale call still raises the always-on hard error :rf.error/inject-cofx-removed naming :rf.cofx/requires.

Surface Shape Tier Purpose
[:rf.interceptor/path <path-vector>] interceptor reference (the one standard interceptor; the canonical :factory consumer) front-porch Focus a handler on an app-db sub-slice: :before stages the focused slice as :db, :after widens the returned slice back into full app-db. Preserves the frame-commit identical? no-op — an unchanged focused slice widens back to the original app-db object, not an assoc-in allocation (002 §Standard :rf.interceptor/path). A non-vector/malformed path arg is :rf.error/path-interceptor-bad-path.
reg-interceptor M (registrar) front-porch The public application-authoring form for any non-standard interceptor — analytics, logging, validation, ad-hoc context manipulation. The resulting interceptor is named, addressable, queryable, and referenced by id from chains. Rowed in §Registration.
->interceptor* Fn (internal lowering constructor) advanced The framework-internal constructor that lowers a descriptor into an executable chain entry ((->interceptor* & {:keys [id before after source-coord]})). NOT a public application-authoring API and MUST NOT appear in a public event/frame chain (EP-0022).

The retired v1 public interceptor-authoring helpers and their replacements:

Removed / retired API Replaced by
path (public value constructor) the standard reference [:rf.interceptor/path <path-vector>] (EP-0022)
unwrap handler destructuring (the M-19 canonical map-payload form), or a project-registered interceptor for intentional chain-wide event reshaping (EP-0022 — no standard unwrap)
->interceptor (public authoring macro) reg-interceptor (->interceptor survives only as the internal lowering constructor)
debug Trace surface (009) + 10x / re-frame-pair
trim-v Canonical map-payload call shape (M-19)
on-changes Flows (Spec 013)
enrich Flows (derived state) / :schema (validation) / a project-registered interceptor (escape hatch)
after Registered fx (:fx [[:my-fx ...]]) for side-effects; a project-registered interceptor for context-shaped work

reg-flow / clear-flow (Spec 013)

reg-flow is rowed canonically in §Registration. Per the canonical 3-slot grammar (rf2-bqstzr) it is (reg-flow flow-id metadata derive-fn): flow-id first, the pure :derive fn last, and metadata carrying :inputs / :output-path (both REQUIRED) plus optional :doc / :schema / EP-0025 classification keys and the :frame mounting key (which selects the owning frame — the metadata middle slot, like every other 3-slot reg-* surface). clear-flow — whose primary arg IS the flow-id — keeps its trailing opts map for the frame override: (clear-flow id) / (clear-flow id opts). clear-flow is rowed canonically in §Clearing registrations; it deregisters the flow from the named frame and dissoc-ins its :output-path from that frame's app-db only (per Spec 013 §Frame-scoping).

Frame-destroy teardown. destroy-frame! releases every per-frame piece of flow state (the destroyed frame's slot in the per-frame flow registry, its last-inputs dirty-check rows, and its pending abandoned-output-paths) per Spec 013 §Frame-destroy teardown. Under single-store there is no registrar-slot prune (the old double-store "last owner" unregister / realign is gone — the :flow registrar kind is RESERVED-but-empty). Sibling frames' state is preserved.

Flow-eval failures in production. A throw inside a flow's :derive fn surfaces as :rf.error/flow-eval-exception on the always-on error-emit substrate — registered error-emit callbacks (the :errors stream of register-listener!) fire under CLJS :advanced + goog.DEBUG=false. The error is NOT trace-only. Per Spec 013 §Failure semantics rule 4 and 009 §Production builds.

Reserved fx-ids for runtime flow management via :fx:

Name Kind Signature Status
:rf.fx/reg-flow Reserved fx-id [:rf.fx/reg-flow [flow-id metadata derive-fn]] — register a flow at runtime via :fx (the same 3-slot triple reg-flow takes; the dispatching frame threads through as the :frame metadata key) v1 (optional capability)
:rf.fx/clear-flow Reserved fx-id [:rf.fx/clear-flow id] — clear a registered flow via :fx v1 (optional capability)

Interceptor / context plumbing

The interceptor context accessors get-coeffect / assoc-coeffect / get-effect / assoc-effect are removed from the public re-frame.core façade and carry no API-manifest row (a removed surface is not part of the canonical public API). Post-EP-0017/EP-0022 they lost their audience — the setters had zero callers, the getters one. The intended interceptor model is to author with reg-interceptor and let the :before / :after fns receive and return the context map directly: read coeffects with (get-in ctx [:coeffects k]) and write effects with (assoc-in ctx [:effects k] v). The underlying re-frame.interceptor/{get,assoc}-{coeffect,effect} fns remain in their owning namespace as framework-internal context helpers; they are not a public surface. See §Removed / not shipped.


Lifecycle / utility

API M/Fn Signature Status Tier Spec
init! Fn (init! adapter-map) — idempotent boot. Required arg: the adapter spec map. Each adapter ns exports an adapter Var; consumers require the ns and pass the Var, e.g. (rf/init! reagent/adapter). Calling (init!) with no args raises a language-level ArityException at compile/load time ( — the no-arg arity was cut so the missing-adapter mistake surfaces before runtime). Calling (init! nil) or (init! :reagent) raises :rf.error/no-adapter-specified at runtime. Per 006 §Adapter selection at boot. Installs adapters and runtime capabilities only — it creates no frame (EP-0002: no auto :rf/default); the app mounts its frame at the root with frame-root (ENSURE) — or constructs one programmatically with make-frame v1 front-porch 006
init-platform Fn (init-platform p) — set the host-wide active-platform marker (:server or :client). The runtime tracks the active platform so reg-fx/reg-cofx :platforms metadata can gate execution (per 011 §Effect handling on the server). CLJS hosts default to :client, JVM hosts to :server; call at boot to override (CLJS-on-Node SSR runtime: (rf/init-platform :server); JVM-runnable test simulating browser: (rf/init-platform :client)). Per-frame :config :platform (set by the :ssr-server preset) still wins over this host-wide marker. p must be :server or :client; anything else raises :rf.error/invalid-platform. Idempotent / re-callable. Sibling boot call to init! — independent (adapter install vs platform marker). v1 advanced 011
install-adapter! Fn (install-adapter! adapter-map) — must be called before any frame is created. Lower-level than init!; most consumers call init! instead v1 advanced 006
destroy-adapter! Fn (destroy-adapter!) — tear down the exact installed adapter generation. This is a one-way terminal boundary: calls the adapter spec's :dispose-adapter! fn (if present), attempts all owned cleanup, finally clears only the generation it claimed, and leaves adapter-disposed? true even when the primary cleanup failure is rethrown. Later cleanup failures remain secondary diagnostic evidence. A fresh adapter may install afterward; stale finalization never clears a replacement. Per Conventions §Tear-down verb axisdestroy- cluster (lifecycle boundary; symmetric with install-adapter! and with destroy-frame!). The adapter-spec map key :dispose-adapter! (an internal contract slot adapters implement) is unchanged. v1 advanced 006
current-adapter Fn (current-adapter) → discriminator keyword (:rf.adapter/reagent / :rf.adapter/reagent-slim / :rf.adapter/uix / :rf.adapter/hicasso / :rf.adapter/plain-atom / :rf.adapter/ssr / :custom, plus the retired-but-reserved :rf.adapter/freehand / :rf.adapter/ui that nothing produces since 2026-08-16) or nil when no adapter is installed. Answers "what substrate am I on?" — predicate / branch code. For the spec map (fn handles, identity checks), use current-adapter-spec. v1 advanced 006
current-adapter-spec Fn (current-adapter-spec) → the installed adapter spec map (the value passed to (rf/init! ...)) or nil when no adapter is installed. Answers "give me the adapter fns to call" — tools, routing, identity checks across the install/dispose lifecycle. For the discriminator keyword, use current-adapter. v1 advanced 006
adapter-disposed? Fn (adapter-disposed?)true iff terminal teardown of the most recent installed generation has been claimed and no subsequent install-adapter! has fired. Cleanup success is not implied: it remains true when destruction rethrows a cleanup failure. false for never-installed (fresh process) and after a fresh install. Read-only — the breadcrumb is owned by the install / destroy pair. Use to distinguish :rf.error/no-adapter-installed (fresh process) from :rf.error/adapter-disposed (torn down). Per 006 §Disposed-vs-never-installed. v1 advanced 006
configure! Fn (configure! {key opts, …}) — runtime config from a single nested map; non-map arg fails loud, missing top-level key leaves that subsystem untouched, unknown top-level key silently no-ops. Key vocabulary in §Configure keys. One of three orthogonal configuration surfaces per Conventions §Configuration surfaces (configure! for process-level data knobs; set-! / install-! for adapter-pluggable hooks; per-frame metadata for frame-scoped overrides). v1 front-porch

Feature inspection

re-frame2's optional capabilities ship as separate Maven artefacts (day8/re-frame2-<feature>) whose implementation namespaces core reaches through the late-bind hook registry at call time (per Conventions §Facade re-export, artefact require). The upside is bundle-isolation — an app that omits a feature does not carry its code. The downside this front-porch closes: the late-binding is otherwise invisible, so a developer who forgets to :require the impl artefact calls a re-exported fn that exists and is met with an opaque artefact-missing error. These three surfaces make the optional-feature inventory self-explaining.

The known optional features are :schemas, :machines, :routing, :flows, :http, :ssr, :epoch (the closed per-feature split set per Conventions §Artefact tiers).

These three fns ship to production. They are runtime queries, not dev-time instrumentation, so — unlike the trace / epoch surfaces — they are NOT gated on interop/debug-enabled? and do NOT elide under :advanced + goog.DEBUG=false. A production caller may legitimately probe (rf/feature-loaded? :routing) before taking a routing-dependent path. The feature→coordinate mapping is static data in the always-loaded re-frame.core facade (a plain table of {:feature {:maven … :require … :spec …}} strings), never a live :require reaching into the optional impl namespaces — a live reach-in would create a hard facade→optionals reference that pulls every optional artefact into every production bundle, breaking bundle-isolation. feature-loaded? detects presence by a pure keyword lookup in the always-loaded late-bind hooks atom against a representative key the impl publishes at ns-load — no reach into the optional namespace.

API M/Fn Signature Status Tier Spec
features Fn (features) → map of every optional feature keyword to its inspection entry: the static coordinate data (:maven / :require / :spec) merged with the live :loaded? boolean. E.g. {:epoch {:maven "day8/re-frame2-epoch" :require "re-frame.epoch" :spec "Tool-Pair (Time-travel / epoch)" :loaded? true} …}. Ships to production (NOT elided). v1 advanced
feature-loaded? Fn (feature-loaded? feature)true when the feature's impl artefact is on the classpath, false otherwise (including for an unknown feature keyword). Pure keyword lookup against the always-loaded late-bind hooks atom — no require into the optional namespace. Ships to production (NOT elided). v1 advanced
require-feature! Fn (require-feature! feature)true when the feature is loaded; otherwise throws :rf.error/feature-not-loaded carrying the EXACT copy-pasteable Maven coordinate (:maven) + require namespace (:require-ns) and a :reason string naming both (an unknown feature keyword throws :rf.error/unknown-feature with the :known set). Use as a self-explaining early guard at the top of feature-dependent code. Ships to production (NOT elided). v1 advanced

Artefact-missing errors carry the require. This front-porch is paired with a hard rule: every artefact-missing error in the framework — including the existing late-bind facade throws (:rf.error/<feature>-artefact-missing, raised via re-frame.late-bind/require-fn! from the re-frame.core-<feature> wrappers) — carries the exact copy-pasteable Maven coordinate and the namespace to require at app boot in its :reason slot. The named pattern is documented once at Conventions §Facade re-export, artefact require.


Configure keys

Runtime configuration is uniformly via (rf/configure! {<key> <opts>, …}) — a single nested map keyed by the top-level keys enumerated here. The argument MUST be a map (a non-map argument fails loudly); a missing top-level key leaves that subsystem untouched; a present key delegates to that subsystem's configurator in table order (:epoch-history, :trace-buffer, :elision) preserving its slot-merge semantics; an unknown top-level key is a silent no-op (closed-and-additive). Keys are plural-noun-shaped; opts are an open map of per-key settings.

(rf/configure! {:epoch-history {:depth 100}
                :trace-buffer  {:events-retained 25}
                :elision       {:rf.size/threshold-bytes 8192}})
Key Opts shape Default Status Spec
:epoch-history {:depth N :trace-events-keep N :redact-fn (fn [record] …)}:depth non-negative integer (0 disables the ring); :trace-events-keep non-negative integer caps raw :trace-events retention (per Security §Epoch privacy posture); :redact-fn is fn? or nil — the advanced PROJECTION-SIDE override invoked once per record at the off-box egress boundary (inside projected-record, after the frame/profile projection), NOT at build/storage time: the in-process ring buffer and every :epoch-stream listener deliver the RAW record (post-EP-0010 causal replay material — storage-side mutation was removed so restore-epoch! fidelity is never affected), with the :rf.epoch/sensitive? rollup computed at build-time from raw signals, throws caught and surfaced as :rf.warning/epoch-redact-fn-exception with fallback to the projected record. Per Tool-Pair §Time-travel. {:depth 50, :redact-fn nil} v1 (dev-only) Tool-Pair
:trace-buffer {:events-retained N} — non-negative integer event-slot count (one slot per event / pipeline run); 0 disables retention (the surface stays live) {:events-retained 50} v1 (dev-only) 009
:elision {:rf.size/threshold-bytes N} — non-negative integer; 0 disables runtime auto-detect (only declared / schema entries elide) {:rf.size/threshold-bytes 16384} v1 009

Retired key. The earlier :sub-cache {:grace-period-ms N} knob is gone. Sub-cache disposal is now synchronous on derefer-count → 0 (per 006 §Reference counting and disposal); there is no deferred-grace timer to configure.

SSR error-projection policy (:public-error-id, :dev-error-detail?) is not a configure key — it is per-frame metadata on the frame's :ssr map (see Conventions §Configuration surfaces bucket 3 and 011 §Server error projection). Different frames in the same process can carry different projector / dev-detail settings, so the natural lifetime is per-frame, not process-global.

Opts-key naming rule

The opts map for any configure key mixes two shapes — it encodes which contract owns the sub-key:

  • Framework-owned semantic sub-keys use a namespaced keyword under a reserved :rf.<area>/* sub-namespace (per Conventions §Reserved namespaces). The namespace identifies the cross-spec policy area the sub-key participates in — the same key shape appears verbatim wherever that policy is consumed, not only inside configure. Example: :elision carries {:rf.size/threshold-bytes N} because :rf.size/threshold-bytes is the same per-call policy key consumed by rf/elide-wire-value and the wire-elision walker (per Conventions §Reserved namespaces — :rf.size/*). The namespaced form makes the cross-surface identity grep-visible and prevents collision with adjacent per-knob settings.
  • Ergonomic per-knob sub-keys are unqualified bare keywords (:depth, :trace-events-keep, :redact-fn). These sub-keys are local to a single configure key's opts map — they do not appear elsewhere in the framework's vocabulary, so a framework-owned namespace would add noise without adding identity. The bare form is the default at this leaf position; reach for it whenever the knob is unique to one configure key.

The discriminator is whether the sub-key names a cross-surface policy slot or a one-off knob. A sub-key earns a :rf.<area>/* namespace when it names a contract that lives in more than one place (:rf.size/threshold-bytes is read by :elision, by elide-wire-value, and by the MCP wire walker). A sub-key stays bare when it is local to its parent configure key (:events-retained under :trace-buffer and :depth under :epoch-history each tune one ring's slot count and live nowhere else in the vocabulary — separate knobs, no shared contract, so each stays bare).

New configure keys MUST apply the same rule: if a sub-key participates in a cross-spec policy area, qualify it under the area's reserved namespace; otherwise leave it bare. The rule is closed — there is no third shape (no :configure/depth, no :rf.configure/* prefix). A sub-key that would want a third shape is evidence the proposed knob is doing two things and should be split.

Fixed-and-additive

The configure-keys vocabulary is fixed-and-additive (Spec-ulation): existing keys cannot be renamed or removed; new keys are added by extending the table. Because configure! takes a single map and silently ignores unknown top-level keys, user code that wraps configure can pass a composed config value straight through — unknown keys no-op rather than throw.


Machines

Split between the v1 machine-as-event-handler foundation and the post-v1 re-frame.machines scaffolding library — see 005-StateMachines.md §Disposition. The machine is the event handler: a machine is registered as one reg-event whose body comes from make-machine-handler.

API M/Fn Signature Status Tier Spec
reg-machine M (reg-machine machine-id machine-spec) / (reg-machine machine-id opts machine-spec) — registers a machine as an event handler. Walks the literal spec form at expansion time and co-locates per-element source on each :guards / :actions entry + a reference-site :source-coords on each :states-tree map node. The optional opts metadata map is the canonical Spec 001 MIDDLE slot; it carries an event-vector :schema — the machine + event-vector-schema shape. v1 advanced 005
reg-machine* Fn (reg-machine* machine-id machine-spec) / (reg-machine* machine-id opts machine-spec) — plain-fn surface beneath the macro. No source-coord walking. The optional opts metadata map is the MIDDLE slot (uniform with the macro); it carries the event-vector :schema. v1 advanced 005
defmachine M (defmachine name [docstring] spec)def-shape that walks the literal spec at the definition site and co-locates per-element source onto the def'd value, for the def-then-register shape (defmachine m {…}) / (reg-machine :id m). Does not register. v1 advanced 005
make-machine-handler Fn (make-machine-handler spec) → event-handler fn v1 advanced 005
machine-transition Fn (machine-transition definition snapshot event)re-frame.machines.result value (result/ok?; result/snap / result/fx read the snapshot + fx; result/fail carries result/info) v1 advanced 005
machines Fn (machines) → vector of registered machine-ids v1 tooling 005
machine-meta Fn (machine-meta machine-id) → registration metadata; :guards / :actions entries carry co-located :source-coords / :source-code and each :states-tree map node carries its own reference-site :source-coords when registered via the macro v1 tooling 005
machine-by-system-id Fn (machine-by-system-id system-id) / (machine-by-system-id system-id {:frame target}) → spawned-machine id bound to system-id in the frame's [:rf.runtime/machines :system-ids] reverse index (or nil). The 2-arity takes an {:frame target} opts map (the frame-last positional spelling is retired to internal / test / tooling plumbing). Per 005 §Named addressing via :system-id. v1 advanced 005
:rf.machine/dispatch-to-system (fx) Named-addressing escape (registered globally by re-frame.machines). From a machine action's :fx, [:rf.machine/dispatch-to-system [system-id event]] resolves system-id through the frame's [:rf.runtime/machines :system-ids] reverse index and dispatches event to the bound actor (no-op when unbound); actions can't read app-db, so the fx is how an action messages a NAMED child. Zero in-repo consumers as of 2026-07-10; retained for XState v6 actor-system parity (systemId addressing). The redundant dispatch-to-system call-site FN twin was deleted. Per 005 §Cross-machine messaging by name. v1 — (fx-id) 005
:rf.machine/spawn (fx) Canonical actor-lifecycle fx (registered globally by re-frame.machines). Args per :rf.fx/spawn-args. v1 — (fx-id) 005
:rf.machine/destroy (fx) Canonical actor-destroy fx (registered globally by re-frame.machines). Args: an actor id. v1 — (fx-id) 005
:raise (fx) Reserved fx-id inside a machine action's :fx (machine-internal, routed pre-commit). Args: an event vector. v1 — (fx-id) 005
:final? / :output-key (state-node keys) :final? marks a leaf state as terminal — entering it auto-destroys the machine. :output-key (requires :final?) designates the child's :data slot reported back via the parent's :on-done. Capability axis :fsm/final-states. Per ; see 005 §Final states. v1 — (spec key) 005
:on-done (:spawn spec key) (fn [{:keys [data result]}] new-data) on the parent's :spawn map. Fires synchronously when the spawned child enters a :final? state; result is the child's :data slot named by the final state's :output-key (or nil). Returns the parent's new :data map. Per and. v1 — (spec key) 005
:child-machine (transition-table key) Declarative state-scoped child-machine binding. post-v1 lib — (spec key) 005
machine->xstate-json Fn (machine->xstate-json definition) → JSON post-v1 lib tooling 005
machine->mermaid Fn (machine->mermaid definition) → string post-v1 lib tooling 005

Canonical descriptions (factory purity, spec keys, snapshot location, registration-time validation, etc.) in 005-StateMachines.md and Spec-Schemas.

v1 transition-table grammar subset is enumerated in 005 §Capability matrix; shape in Spec-Schemas §:rf/transition-table.

Standard registered subs (machines)

Standard sub Returns Spec
[:rf/machine <machine-id>] The machine's snapshot {:state :data :tags} (or nil if not yet initialised) 005
[:rf.machine/has-tag? <machine-id> <tag>] true iff the machine's current snapshot's :tags set contains tag (false for an unknown / not-yet-initialised machine) 005

The canonical machine read is the registered [:rf/machine machine-id] subscription vector — see 005 §Subscribing to machines. It is read like any other subscription — @(rf/subscribe [:rf/machine machine-id]), @(rf/subscribe [:rf.machine/has-tag? machine-id tag]) — and named projections chain off it with :<-. There is no named-read-sugar fn: a runtime-db framework read is a subscription vector, one grammar.


Story / variant / workspace library (post-v1)

See 007-Stories.md.

All Story surfaces are tooling (a Storybook-shaped dev surface — registration, execution, introspection — not application logic). Absorbs story F-8; see §Tiering of cross-tool surfaces.

API M/Fn Signature Status Tier Spec
reg-story M (reg-story id metadata) post-v1 lib tooling 007
reg-variant M (reg-variant id metadata) post-v1 lib tooling 007
reg-workspace M (reg-workspace id metadata) post-v1 lib tooling 007
reg-tag M (reg-tag id metadata) post-v1 lib tooling 007
reg-decorator M (reg-decorator id metadata) post-v1 lib tooling 007
reg-story-panel M (reg-story-panel id metadata) post-v1 lib tooling 007
run Fn (run target) / (run target opts) → promise/future of the unified run-result. target is a keyword (registered variant) OR a map (inline plan). The single execution verb. post-v1 lib tooling 007
is Fn (is target) / (is target opts) → runs target and reports each assertion to clojure.test / cljs.test. JVM blocks (bounded by :timeout-ms, default 30000) and returns the run-result; CLJS returns the run promise. post-v1 lib tooling 007
explain Fn (explain target) / (explain target opts) → the plan's :explain map (args-validation, sub-overrides, decorators, …) without running. post-v1 lib tooling 007
variants-with-tags Fn (variants-with-tags tag-set) → seq of variant ids post-v1 lib tooling 007
snapshot-identity Fn (snapshot-identity variant-id){:variant-id ... :content-hash "..."} post-v1 lib tooling 007
story-view Fn (story-view variant-id) → hiccup post-v1 lib tooling 007

The public execution surface is exactly the three verbs run / is / explain (each accepts a registered-variant keyword OR an inline-plan map). run-variant / is-variant / run-plan / is-plan / watch-variant / reset-variant are implementation / migration vocabulary, NOT the P1 public surface — they are not rowed here. The unified run-result is the single execution-record boundary; read its verdict via result-status / result-passed? (:pass / :fail / :cannot-run / :error — there is no :passing? boolean). Canonical execution model: story spec 017 §Public execution API (the re-frame2-story library spec, the normative home for the verbs + run-result shape).


Removed / not shipped

These surfaces are removed or renamed — not part of the public projection and not a tier (the deprecated tier is reserved for surfaces still shipping while on the way out; pre-alpha carries none). This is a migration table: each row names what to use instead.

API What to do Reference
dispatch-with (master) Use (dispatch event {:fx-overrides {...}}) MIGRATION M-4
dispatch-sync-with (master) Use (dispatch-sync event {:fx-overrides {...}}) MIGRATION M-4
dispatch-to (proposed earlier) Use (dispatch event {:frame :todo}) 002
subscribe-to (proposed earlier) Use (subscribe query-v {:frame :todo}) 002
frame-dispatcher / bound-dispatcher / bound-subscriber (proposed earlier) Use (rf/capture-frame) (the keystone OPERATION BUNDLE — captures the frame at creation; safe during render and from async callbacks) 002
bound-fn (CLJS macro) Use (rf/capture-frame) — the keystone OPERATION BUNDLE captures the frame and carries dispatch / subscribe across the boundary. 002
frame-bound-fn (macro) / frame-bound-fn* (fn) DELETED from the facade (API-shrink #1, rf2-csbbwu) — capture-frame is the ONE public carry primitive. The frame-rebinding closure semantics (re-establish *current-frame* around an arbitrary already-held fn) survive internally as re-frame.frame/bind-fn for framework / test / tooling reach; not app-facing. 002
frame-value->id (fn) DELETED from the facade (API-shrink #1, rf2-csbbwu) — every public surface accepts a frame VALUE or its id: the routing ops (dispatch / subscribe / app-db-value / frame-provider / …) normalize a value to its id interchangeably, and destroy-frame! accepts either but reads the value's exact-incarnation lifecycle authority (rf2-moftbs — a stale value no-ops against a same-id successor; a keyword is address-directed). Either way there is no need to unwrap a value to its id. 002
dispatcher Use (:dispatch (rf/capture-frame)) or the dispatch injected in a reg-view body 002
subscriber Use (:subscribe (rf/capture-frame)) or the subscribe injected in a reg-view body 002
current-frame Renamed to current-frame-id (returns a frame-id keyword) 002
get-frame-db Renamed to app-db-value (returns the app-db VALUE, a plain map) 002
(dispatch frame event) / (dispatch-sync frame event) / (subscribe frame-id query-v) / (subscribe-once frame-id query-v) frame-first positional forms DELETED (API-shrink #1, rf2-csbbwu) — every sig is [payload] / [payload opts]; target an explicit frame via the trailing {:frame …} opt (a frame-id keyword or a live frame value). 002
enable-performance-api-tracing! (proposed earlier) Performance-API instrumentation is gated on the compile-time re-frame.performance/enabled? goog-define, not a runtime toggle (see 009 §Performance instrumentation) 009
add-trace-listener / remove-trace-listener (proposed earlier) Use register-listener! / unregister-listener! 009
register-trace-listener / unregister-trace-listener (no-bang, proposed earlier) Renamed to register-listener! / unregister-listener! (bang form matches the side-effecting nature of listener registration) 009
Bare [:my-view "args"] keyword-tagged hiccup Use the Var form [my-view "args"] (canonical) or [(rf/view :my-view) "args"] for late-binding by id Conventions
h macro (proposed earlier) Removed. Use the Var form [my-view "args"] or [(rf/view :my-view) "args"] Conventions
reg-global-interceptor Use the frame-level :interceptors config key (frame-level is the canonical "global within this frame"). For cross-frame observation use register-listener!. MIGRATION M-17
clear-global-interceptor No replacement needed — re-declare the frame (make-frame with an updated :interceptors vector; absent-key semantics clear it). MIGRATION M-17
reg-sub-raw Use reg-sub (app-db reads), Pattern-AsyncEffect (non-app-db sources), state machines (lifecycle), or the 006 adapter contract (bridging external reactivity). MIGRATION M-18
reg-event-db Use reg-event (no alias) — destructure :db from the coeffects map and wrap the return in {:db …}: (reg-event id (fn [{:keys [db]} ev] {:db BODY})). A stale call raises the always-on hard error :rf.error/reg-event-db-removed naming reg-event. The ^:no-doc facade throwing stub carries no API-manifest row. 001 §The retired event-registration names
reg-event-fx Use reg-event (no alias) — reg-event IS the identical shape under the bare name (coeffects in, effects out); just rename the call. A stale call raises :rf.error/reg-event-fx-removed naming reg-event. 001 §The retired event-registration names
reg-event-ctx A framework-internal primitive. Express application full-context work as a registered interceptor (reg-interceptor with :before/:after, referenced by id from a reg-event chain). A stale public call raises :rf.error/reg-event-ctx-removed. 001 §The retired event-registration names
reset-frame! (rf2-lxwpob) Use (destroy-frame! id) then (make-frame config), re-supplying the SAME config (which carries :id, and :images for an image-loaded frame) you already hold. A stale call raises :rf.error/reset-frame-removed naming the composition. The ^:no-doc facade throwing stub carries no API-manifest row. 002 §Resetting a frame — destroy + make-frame
reload-images! (rf2-lxwpob) Image hot-reload is folded into re-construction — re-call make-frame against the SAME :id with a new :images vector; it swaps the generation while preserving frame memory. To read the reload diff, call frame-generation before/after and diff with generation-diff. A stale call raises :rf.error/reload-images-removed. The ^:no-doc facade throwing stub carries no API-manifest row. 002 §Image resolution and composition
get-coeffect / get-effect Removed from the façade (no audience; carry no manifest row). Inside a reg-interceptor :before/:after fn read the context map directly: (get-in ctx [:coeffects k]) / (get-in ctx [:effects k]). The owning-namespace re-frame.interceptor/get-coeffect / get-effect fns remain framework-internal. 001, 002
assoc-coeffect / assoc-effect Removed from the façade (zero callers; carry no manifest row). Inside a reg-interceptor :before/:after fn write the context map directly: (assoc-in ctx [:coeffects k] v) / (assoc-in ctx [:effects k] v). The owning-namespace re-frame.interceptor/assoc-coeffect / assoc-effect fns remain framework-internal. 001, 002
re-frame.alpha/reg The shipped per-kind registrars: reg-event / reg-sub / reg-fx / reg-cofx / reg-flow. (The v1 event trio reg-event-db / reg-event-fx / reg-event-ctx is not a v2 target — those are removed/withdrawn throwing stubs and migration inputs only, see the rows above and EP-0018; reg-event is the single event-registration form.) MIGRATION M-23
re-frame.alpha/sub Vector-form (rf/subscribe [::id arg]). MIGRATION M-23
re-frame.alpha/reg-sub-lifecycle and built-in lifecycle policies (:safe, :no-cache, :reactive, :forever) Sub-cache uses a single algorithm — synchronous ref-counting (dispose on derefer-count → 0), per Spec 006 §Reference counting and disposal. For specific edge cases file a follow-up bead. MIGRATION M-23

Cross-references