Skip to content

day8/re-frame2

Repository files navigation

re-frame2 logo

This, milord, is my family's axe. We have owned it for almost nine hundred years, see. Of course, sometimes it needed a new blade. And sometimes it has required a new handle, new designs on the metalwork, a little refreshing of the ornamentation ... but is this not the nine hundred-year-old axe of my family? And because it has changed gently over time, it is still a pretty good axe, y'know. Pretty good.

— Terry Pratchett, The Fifth Elephant — reflecting on identity, flow, and derived values (aka the Ship of Theseus)

re-frame2 is the same axe as before, but made from different bits and with new ornamentation.

What is it?

re-frame2 is an architectural pattern for building single-page apps that target a virtual-DOM substrate — React, in practice.

What's interesting?

1. The spec is the artefact. The code is downstream.

Historically, here's how every other framework works: somebody writes the implementation, the implementation is the thing, and the documentation heroically tries to describe what the implementation does, usually failing to a greater or lesser extent.

re-frame2 inverts that. The pattern is defined by its specification, and the ClojureScript reference implementation is a consequence of the spec, not the source of truth.

The goal: the spec is complete enough that a sufficiently capable AI can one-shot a working implementation from it. In ClojureScript, which is what ships here. But also, in principle, in TypeScript, Melange/ReScript, Fable, PureScript, Scala.js, Kotlin/JS, Squint — any language that cross-compiles to JavaScript and reaches React.

The implication, which I'd like you to sit with for a moment, is this: if you don't like this specification, change it, and one-shot your own framework. Roll your own with whatever fork of the spec pleases you. Value has moved up the chain. Code is trending toward $0 and disposable. The spec is the valuable, durable thing.

Yes, I know how that sounds. Don't shoot the messenger. Let's keep going.

2. Views are derivative, not causal.

Humans have a cognitive bias: "What is focal is perceived as causal" — the thing you are looking at right now is often conferred with undue importance — and the React ecosystem seems to have a double dose of it.

Components have become more and more causal. They own state via hooks. Components fetch data. Components route. Components subscribe to stores via a useFoo hook that some library or other plumbed in. Effects are colocated with the view tree. And, what a Turing-complete mess they have become.

Of course, it wasn't always this way. Back in 2014, React embraced v = f(s) and it was beautiful. And Redux might have been clunky and incomplete, but it was directionally right.

re-frame is retro, or maybe it's classic or something. Views are not causal, they are simple and derivative.

Your language of choice should be Turing complete; your architecture shouldn't be.

— Me, being snarky about the direction of the JS/TS frameworks

Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy.

— Alan Perlis, apparently equally furious

A re-frame2 frame is, quite literally, a small virtual machine. Registered handlers are loaded into a frame, and those handlers are like the instruction set. Events — coming from user actions, and other sources — are the instructions, and a frame processes them as an event stream, every event running the same pipeline, no exceptions, no escape hatches. We call one turn of that pipeline an epoch. The stream of events is the program running on the machine you create via your registered handlers.

3. Tooling is first-class.

re-frame2's predictable computational pipeline has a single, deeply integrated trace bus.

Result: your application is the ultimate surveillance state. With the ClojureScript implementation, you can even get trace form-by-form (think statement-by-statement), but not by default.

Tools can attach to this trace bus and see deep into a running program. Every event leaves an epoch you can scrub through forwards and backwards. Pair-programmer AI tooling can interact with your running system. Same with tests, stories.

And, yes, you can set policies to elide sensitive things, like auth tokens, as well as too-large binary blobs.

4. Managed external effects.

Your app likely talks async with the outside world A LOT — HTTP, websockets, postMessage, socket.io, IPC, push notifications, background workers, server-side fetches during SSR. Every framework lets you do this, but treats each as its own integration story: different retry shape, different abort dance, different error taxonomy, different (or no) privacy-redaction story. The integrations don't compose. The bugs don't transfer.

re-frame2 has the managed external effect — one primitive shape every outbound conforms to. Effects are data, returned from handlers, not invoked as callbacks. The framework owns retry, abort, fan-out, in-flight registry, teardown, observable trace events, :sensitive? + :large? elision composition, and a structured failure taxonomy under each surface's :rf.<surface>/* namespace.

:rf.http/managed is HTTP. :spawn / :spawn-all on state machines are managed effects. SSR per-request lifecycle is one. The next surface — WebSockets, postMessage relays, file watchers, Service Worker channels — inherits the shape by name. re-frame2 deliberately ships only a focused set (it does not fold in a managed WebSocket, for instance); the WebSocket pattern shows how you roll your own from the primitives so it composes exactly like the built-ins.

And the external story composes naturally with the internal management primitives that handle, say, the Nine States of GUIs problems in an unsuspenseful way. External and internal share the same effects-as-data shape. You learn the model once, you apply it everywhere.

5. Lisp's quiet advantage.

Apologies for the Clojure-poet framing here but ...

re-frame was born out of Clojure's ethos, and Clojure is a modern Lisp. Alan Kay once described Lisp as "Maxwell's equations of software," and Paul Graham wrote at length about Lisp as a competitive advantage. I'm not going to relitigate those essays here. I'll just note that Lisp, and by extension re-frame2, inherits 50 years of foliated excellence from some of the best minds the field has produced, and a thriving ClojureScript community alongside it. Unlike TS or JS, Lisp went through its painful growing pains and industrial-level churn 40 years ago. It is a peaceful place now. Like meditating in a Zen garden.

Not interested? Okay, fine, you can still roll your own version of re-frame2 in your alternative language of choice and transitively reap the benefits anyway. Blink twice if you are being held hostage.

Novelty, bah humbug!

Who cares about novelty? I just want a feature-rich, excellent, productive framework!

Well, beyond the novel parts, re-frame2 comes with SOTA batteries in various dimensions:

  • Xray — the human-facing devtools panel, mounted in-app and preloaded into dev builds. Thirteen tightly-integrated panels — event detail, causality graph, time-travel scrubber, app-DB diff, subscriptions with TanStack-style freshness badges, machine inspector, schema-violation timeline, hydration debugger, an AI co-pilot rail, and more. Claude described it to me as a masterpiece, and who am I to argue.
  • re-frame2-pair — a Claude skill that pair-programs against your running application via nREPL + MCP. The trace bus gives the AI deep insight; it dispatches events, scrubs epochs, hot-swaps handlers, and reads your DOM tree (every element is tagged with source coordinates). If something breaks, the AI can do a full retrospective on the cascade leading up to the failure, patch the code in place, scrub back, and try the revision for you. There's even a meta-skill that watches your pair sessions and surfaces improvements to the pair tool itself — AI improving AI tooling.
  • Story — a Storybook-class component playground. Parity with Storybook 9, Histoire, and Ladle on the chrome shape, plus differentiators those tools can't easily reach: EDN-first variants (round-trip through MCP and visual-regression services), schema-derived controls (Malli walks generate the args editor automatically), per-variant frame isolation (no state leaks between scenarios), machine-state visualisation, a time-travel scrubber linked to the trace stream, and Test Codegen (record canvas interactions as a :play body — Storybook 9's killer feature, with Story's EDN-first form making the captured output cleaner).
  • Routing — URL-driven navigation with frame-aware semantics. Routes are registry entries; navigation is an event; :route is a sub. Per-pane routes are possible because frames are a thing. Same handler runs server- and client-side.
  • SSR — server-side rendering and hydration that doesn't require a different mental model. Pure hiccup → HTML emitter, JVM-runnable (no React-on-the-server needed). Per-request frame lifecycle. Hydration-mismatch detection with structured error projection. :rf/hydrate is an event like any other.
  • State Machines — near-parity with XState v6 (a wonderful library we've learned much from), the alpha-stage design direction re-frame2 tracks for behavioural parity rather than a released compatibility target, and improved by deep integration into the framework rather than living as a sidecar. Machines are event-handlers; their transitions ride the same event pipeline every other event does; snapshots are values you can scrub, restore, and observe through the trace bus. Hierarchical states, parallel regions, history states (shallow / deep / default-target), :after, :always, declarative :spawn, spawn-and-join, actor model, :tags query layer. History states ride the revertible snapshot — the recording is part of the value, so restore-epoch and SSR hydration get it for free, with no parallel side-table.
  • Flows — registered, runtime-toggleable derived-computation declarations that recompute only when inputs change. Reactive without the framework gymnastics. The v2 incarnation of v1's on-changes interceptor, but registered globally and toggleable as data rather than scattered across event handlers.
  • Schemas — Malli-backed boundary validation, opt-in, production-elidable via Closure dead-code elimination. Validate at every documented boundary — event vector, sub return, cofx, app-db slice. Pay for exactly what you turn on; production builds carry zero overhead.
  • Security — auth tokens shouldn't be leaked in logs or sent to tools. re-frame2 has first-class mechanisms to help here: you classify data once at its owner, the framework projects it at every trust boundary under a named egress profile, and sinks consume records that are already safe. There is also a spec-level Security Contract.
  • Large Things — PDF blobs and other large things shouldn't accidentally get logged to Datadog. The same owner-declared classification keeps them off the observability wire too.
  • MCP servers — two Model Context Protocol servers (re-frame2-pair-mcp and story-mcp) that expose the running app and the story playground to AI clients. Shared :rf.mcp/* wire vocabulary; cross-server vocabulary conformance is gated in CI.

Status

Pre-alpha. I'm still preserving optionality.

We are building apps against the ClojureScript reference implementation, however out of an abundance of caution I have not yet published artifacts to Clojars and NPM. Soon.

You should absolutely not use it yet — there could be dragons and there is still a chance of change. If you are a daredevil, add as a :git/sha coordinate in deps.edn and hold on for dear life. And use the Skills, Luke: re-frame-migration for you-know-what, then re-frame2-pair for coding. Finally, use re-frame2-pair-retro to do a session retrospective and file an issue if you find friction.

AI-first

re-frame2 is AI-first, and that decision permeates every other decision in the project.

If the artisanal craftsman in you finds this offensive — and look, the artisanal craftsman in me finds it offensive, I have spent forty years agonising over the human ergonomics of code and UIs and I do not part with those skills lightly — I get it. But the world has changed, and AI ergonomics is now as important as human ergonomics. The good news, which I think is genuinely good news, is that AI ergonomics and good architecture tend to converge. They both reward predictability, explicit data flow, observable runtimes, and small computational models. The things that make a codebase pleasant for an AI to reason about are, very largely, the things that made it pleasant for humans to reason about in the first place. We were just bad at insisting on them.

Here are examples of how AI-first shows up in practice:

  1. The pattern is one-shot-able. The spec in this repo is complete enough that an AI can one-shot the implementation. The corollary is that the spec is the durable artefact; the implementation is downstream and replaceable. Don't like the spec? Fork it, change it, generate your own framework. That's a real workflow now, and I think it's going to be more important than people realise.

  2. Apps built on re-frame2 are AI-pair-programmable at runtime. The runtime exposes deep trace and integration surfaces specifically so an AI can interact with the dynamics of your running app, not just stare at static source. The thing your AI pair is looking at is the actual state, the actual event stream, the actual subscription graph — not a static file from yesterday. The successor to v1's nREPL-attached re-frame-pair ships in this repo as the re-frame2-pair Claude skill, with an MCP companion (re-frame2-pair-mcp) on the runtime side.

  3. Migration is AI-driven. re-frame2 contains breaking changes from v1. I am not happy about this. To soften the blow, the repo ships a complete migration prompt for an AI — currently 40+ rules, mechanical where the rewrite can be mechanical, flagged-for-human-review in the rare cases where the rewrite depends on intent. And to the Clojurists reading: I apologise for the breakage. It hurts my soul too. Please, please don't tell Mr Hickey.

Project layout

spec/                          Full specification (AI-targeted; the primary artefact)
  README.md                    Spec index — corpus layering + reading order
  000-Vision.md                Goals, hard constraints, the pattern's minimal core
  001-Registration.md          Registration grammar, hot-reload semantics
  002-Frames.md                Frames, dispatch envelope, drain semantics, view ergonomics
  004-Views.md                 View contract; reg-view in the CLJS reference
  005-StateMachines.md         Transition-table machines
  006-ReactiveSubstrate.md     Substrate adapter contract
  007-Stories.md               Storybook-class tooling (post-v1)
  008-Testing.md               Testing primitives
  009-Instrumentation.md       Trace events, error contract, 10x compat
  010-Schemas.md               Malli schemas (CLJS reference)
  011-SSR.md                   Server-side rendering + hydration
  012-Routing.md               URL ↔ frame state contract
  013-Flows.md                 Registered, toggleable computed-state declarations
  014-HTTPRequests.md          Managed HTTP effect — retry, abort, taxonomy
  015-Data-Classification.md   Path-marked sensitive/large declarations; redaction sentinels
  Principles.md                The AI-first practical principles
  Conventions.md               Reserved namespaces, fx-ids, app-db keys
  Ownership.md                 Contract-surface → owning-Spec map (the "where does X live?" reference)
  API.md                       Consolidated CLJS reference API
  Construction-Prompts.md      Templates for AI-driven scaffolding
  CP-5-MachineGuide.md         Companion to Construction-Prompts CP-5 (deeper machine guidance)
  Spec-Schemas.md              Spec-internal shape descriptions
  AI-Audit.md                  Specs scored against the AI-first properties
  Implementor-Checklist.md     Decision-ordered companion to the Host-profile matrix (porting to a new host)
  Tool-Pair.md                 Runtime contract for pair-shaped AI tools (re-frame-pair equivalents)
  Runtime-Architecture.md      Bird's-eye view of the runtime
  Cross-Spec-Interactions.md   Edge cases at Spec boundaries
  Cross-Cutting-Designs.md     Designs that touch multiple specs at once
  Managed-Effects.md           The managed-external-effect pattern (shape every outbound conforms to)
  Security.md                  Pattern-level security posture, threat model, decisions log
                               (CLJS-reference specifics in implementation/SECURITY.md)
  Pattern-*.md                 Worked-example conventions (Forms, RemoteData, WebSocket, ...)
  conformance/                 EDN fixture corpus
docs/
  index.md                     Human guide landing
  core/                        Core track
  release-process.md           Operational doc — multi-artefact release pipeline
  quiet-tests.md               Recipe for silent-on-success cljs.test / clojure.test reporters
examples/                      Worked examples organised by concept — core/ fundamentals,
                               capabilities/ (one framework subsystem per folder), patterns/
                               composition recipes, real-apps/ (RealWorld et al.), substrates/
                               (the same apps on other substrates — UIx, reagent-slim),
                               ui/ (re-frame.ui variants). Test-free.
testbeds/                      Shared framework-behavior testbed surfaces (consumed by Xray + Story
                               + MCP servers). Bundle-isolated from production builds.
  deliberate_throw/            4 :rf.error/* trigger sites (handler / fx / flow / machine)
  schema_violation/            4 :where surfaces of schema-validation-failure
  http_toggle/                 8 :rf.http/* failure categories per Spec 014
  multi_frame/                 Cross-frame dispatch with per-frame app-db
  deep_machine/                Hierarchical machine — parallel regions, :after, :always, :spawn
  long_flow_w_failure/         Multi-second flow with mid-flow failure injection
  drain_depth_trigger/         Recursive dispatch hitting the drain-depth ceiling
  non_trivial_app_db/          ~5-level nested app-db for diffing visualisation
  large_dispatcher/            Payloads above :rf.size/large-elided threshold
  ssr_basic/                   SSR hydration baseline — payload → :rf/hydrate → verify-hydration!
  ssr_hydration_mismatch/      Deliberate hash mismatch — emits :rf.ssr/hydration-mismatch
  ssr_multi_frame/             Per-frame hydration — three frames, one payload slice each
implementation/                CLJS reference implementation — per-artefact subdirs
                               (per Conventions §Packaging conventions). Each subdir is its own
                               jar with its own deps.edn, on the top-level shadow-cljs classpath.
  core/                        day8/re-frame2 — registry, drain, fx, dispatch, subscribe,
                               frame-provider, trace, source-coords, substrate, elision
  freehand/                    re-frame.freehand — the re-frame-native view layer (EP-0036).
                               In-tree only so far; publication lands with its own gate
  ui/                          re-frame.ui, the compiled-view substrate — in-tree only, never
                               published; the donor being absorbed into Freehand (EP-0036)
  adapters/
    reagent/                   day8/re-frame2-reagent — the Reagent adapter (browser default)
    reagent-slim/              day8/reagent-slim — slim Reagent fork (reagent2.core); same view
                               semantics, smaller footprint, capped Form-3 surface
    uix/                       day8/re-frame2-uix — the UIx adapter
    test-react/                local-test-only (no Maven coord) — pure-CLJC React-lifecycle
                               simulator; catches lifecycle bugs at unit-test speed (no React/DOM/jsdom)
  schemas/                     day8/re-frame2-schemas — Malli boundary-validation artefact
  machines/                    day8/re-frame2-machines — state-machine artefact (xstate-flavoured)
  flows/                       day8/re-frame2-flows — registered computed-state declarations
  http/                        day8/re-frame2-http — managed :rf.http/* effect
  resources/                   day8/re-frame2-resources — declarative server-state resources
                               (reg-resource, work ledger, managed-HTTP lowering, SSR preload)
  routing/                     day8/re-frame2-routing — URL ↔ frame routing
  ssr/                         day8/re-frame2-ssr — server-side rendering + hydration
  ssr-ring/                    day8/re-frame2-ssr-ring — Ring adapter for SSR pipeline
  epoch/                       day8/re-frame2-epoch — epoch ring-buffer + partial-record contract
  test-quiet/                  Quiet-on-success reporter shared across 17 test runners
  security/                    Cross-cutting security regression tests (MCP egress, schema
                               redaction, SSR escaping) — test-only, no shipped namespace
  derivation-conformance/      test-only, not published — cross-family derivation/process
                               conformance (subscriptions + flows + resources + machines composed)
  event-conformance/           test-only, not published — cross-artefact public event-model
                               conformance (registration, coeffects, replies, error channels)
  reply-conformance/           test-only, not published — managed-async reply-envelope
                               conformance across HTTP, resources, machines, routing
  scripts/                     Build / release scripts
  shadow-cljs.edn              Top-level build coordinator: pulls all artefacts onto one classpath
  deps.edn                     Top-level build coordinator (clojure-tools): :local/root deps for all
  SECURITY.md                  CLJS-reference security specifics — named fns, numeric defaults,
                               JVM-vs-CLJS stub semantics, full audit trail (companion to
                               spec/Security.md)
tools/                         CLJS dev/inspection tools that consume re-frame2's instrumentation
                               API (Spec 009, Tool-Pair). Sibling of implementation/, not part of
                               it — bundle-isolated from production builds.
  template/                    day8/re-frame2-template — deps-new template for new re-frame2
                               apps; the v1 re-frame-template's v2 successor. Build-time scaffold.
                               Permanent home: github.com/day8/re-frame2-template (lives here while
                               the rebuild settles; see tools/template/spec/005-Repo-Split.md).
  story/                       day8/re-frame2-story — Storybook-flavoured playground
  story-mcp/                   day8/re-frame2-story-mcp — MCP agent surface for story
  re-frame2-pair-mcp/          @day8/re-frame2-pair-mcp — MCP agent surface for the re-frame2-pair
                               nREPL companion
  xray/                        day8/re-frame2-xray — Xray, the re-frame2 devtools panel;
                               the structural successor to re-frame-10x
  machines-viz/                day8/re-frame2-machines-viz — substrate-agnostic MachineChart
                               component + read-only viewer surfaces (the chart extracted out of
                               Xray; also hosts the pure Mermaid emitter)
  testbed-support/             Shared testbed helpers (re-frame.testbed.config project-root resolver
                               + re-frame.testbed.story-host hash-toggle host); consumed by the
                               framework testbed builds
  mcp-base/                    Shared CLJC library for the MCP servers (arg parsing, redaction,
                               cap, overflow); bundle-isolated boundary helpers
  mcp-conformance/             Cross-server MCP conformance harness — gates wire-vocabulary parity
                               in CI
skills/                        Claude skills
  README.md                    Skill routing — single source disambiguating which skill to use
  re-frame2/                   Authoring skill — canonical patterns + reference docs
  re-frame2-setup/             Greenfield-app scaffolding skill
  re-frame2-implementor/       Skill for porting re-frame2 to a new host language
  re-frame2-improver/          Critique-mode skill for existing re-frame2 code
  re-frame2-pair/              Runtime pair-programming skill (nREPL + MCP companion)
  re-frame2-pair-retro/        Retrospective skill — reviews pair sessions, drafts improvements
  re-frame2-xray/              Xray devtools-panel skill (launch modes, panels, components)
  re-frame-migration/          re-frame v1.x → re-frame2 migration skill
  shared/                      Cross-skill protocols (retro-protocol, evidence discipline)

Start here: The user guide, followed by the Core track.

Information for AIs

re-frame2 is spec-first and AI-implementable. If you're an LLM landing here to implement, port, scaffold, or audit the pattern, this section is the map you need. The narrative above is for humans; this section is for tools.

Spec is the artefact. spec/ is the contract. docs/core/ is narrative for humans. The Pattern-*.md files are worked-example conventions, not normative. The reference implementation in implementation/ is downstream of the spec, not the source of truth.

Reading order

  1. The corpus indexspec/README.md. Foundation / Capability / Companion layering.
  2. The contract, in normative order:
  3. Naming + packagingspec/Conventions.md — reserved namespaces, fx-ids, app-db keys, artefact naming.
  4. Surface-to-spec mapspec/Ownership.md — every public surface's canonical home spec, plus informational cross-references.
  5. Consolidated public APIspec/API.md — every shipped fn / macro / fx / cofx with signatures.
  6. Spec-internal shapesspec/Spec-Schemas.md — Malli-shaped definitions of every internal map (effect-map, registration-metadata, hydration-payload, trace-event, machine snapshot, etc.).
  7. AI-first auditspec/AI-Audit.md — every spec scored against the AI-first practical principles.
  8. Migration promptmigration/from-re-frame-v1/README.md — mechanical re-frame v1.x → re-frame2 rewrite rules.
  9. Implementor checklistspec/Implementor-Checklist.md — porting to a new host; decision-ordered companion to the host-profile matrix.
  10. Tool-Pair contractspec/Tool-Pair.md — runtime surfaces that pair-shaped AI tools (re-frame-pair and equivalents) consume.

Quick lookups

When porting to a new host

Start at spec/Implementor-Checklist.md. Part 1 is the capability-declaration list; Part 2 walks technology choices with options-by-host; Part 3 is conformance against the claimed list. The host-profile matrix in spec/000-Vision.md §Host-profile matrix tells you what's pattern-required vs. host-discretion vs. CLJS-only.

Licence

re-frame2 is MIT licensed.