Imported from day8/re-frame2 (
skills/re-frame2/SKILL.md). Install upstream withnpx skills add day8/re-frame2 --skill re-frame2. Copyright stays with the author.
re-frame2
Authors re-frame2 ClojureScript application code. Router skill: this file carries decision shortcuts; depth lives one level deep in references/, patterns/, and decision-trees/.
When to load
.cljs / .cljc authoring of: event handlers, subscriptions, flows, state machines, views, schemas, routes, stories, or the canonical patterns. References to reg-event, reg-sub, reg-fx, reg-flow, reg-machine, dispatch, subscribe, app-db, flows, frames, regions, tags, or pattern names are sufficient triggers — re-frame2 need not be named.
When NOT to use
Full skill-disambiguation matrix lives at skills/README.md §Skill routing — single source. In brief: not for live-runtime inspection, greenfield bootstrap, v1→v2 migration, porting existing Reagent views onto Fresco (use reagent-migration), porting re-frame2 itself, or spec / API / EP rationale reading.
This skill's view surface is the adapters — Reagent, reagent-slim, UIx. Fresco is re-frame2's re-frame-native peer view layer; references/fundamentals/views.md §Fresco carries what it changes and where its contract lives. Everything upstream of the view is the same either way, so the rest of this skill applies unchanged.
Cardinal rules
- Implementation is ground truth. When spec and
implementation/**disagree, the implementation wins. Recipes here are verified againstimplementation/**andexamples/**. - Recipes over explanations. Use the canonical shape; do not re-derive from first principles.
- Distinguish orchestration from state. State machines for modes (legal-transitions-depend-on-current-state); slices for fields. See
decision-trees/slice-or-machine.md. Standing mental model for machines: think in xstate, then map onto re-frame2 — most concepts translate cleanly; a handful re-frame2 renames or omits, and those divergences are the trap. Translation catalogue:references/state-machines/xstate-translation.md; declaration grammar + divergence flags:references/state-machines/reg-machine.md. - Schemas at boundaries. Schema the paths that cross trust boundaries — HTTP payloads, persisted state, snapshot restores — and do not schema-fence every internal key. Which surface you reach for depends on when you need the check to run.
reg-app-schemaand handler:schemaare development-build assertions: a production build registers them but never checks them, so they are tripwires that catch you in dev, not guards on the deployed bundle. Register them anyway — then, where untrusted data must be rejected in production too, give the handler that ingests it a:schemaand set:boundary? trueon the same registration, which is the schema surface that survives the production gate. Details and the full behaviour matrix:references/fundamentals/schemas.md. - Match the canonical shape. When a pattern has a worked example or reference declaration, match its shape; don't re-derive. (Repo:
examples/**; consumer app: the relevant pattern/fundamentals leaf plus any reference views your project follows.) - Frames before globals. Talk to a frame via
dispatch/subscribe. Do not import frame internals or bypass to mutate state. :rf/*is reserved. Application keywords pick their own feature prefix (:cart/...,:auth/...).reg-*macros over the runtime-fn forms. Macros capture source coordinates that tools rely on; the functional counterparts — the*-suffixed twins forreg-view*/reg-machine*, or the same name in value position for everything else (reg-event,reg-sub,reg-interceptor, …) — are for advanced/programmatic cases only. Naming a machine spec is the one place a plaindefsilently defeats this:reg-machinesees a symbol rather than a literal and captures nothing per-element, so userf/defmachine, which stamps the value itself and does not register, then pair it withreg-machine(references/state-machines/reg-machine.md).- Pillar 4 — assume training knowledge. Teach the re-frame2-specific binding, not FSM theory / HTTP retry / React rendering.
Decision shortcuts
Slice vs machine vs region — decision-trees/slice-or-machine.md. Tell: if the prompt names transitions or modes, machine. If it names fields, flags, or counters, slice. A sub-concern of a larger feature's lifecycle is a region inside that feature's machine, not its own top-level machine.
Which pattern fits — decision-trees/pick-a-pattern.md. Quick map:
| Need | Pattern leaf |
|---|---|
| HTTP request with request/response lifecycle | patterns/remote-data.md |
Cached server-state shared across views, with invalidation, or a reusable session/tenant scope (reg-resource-scope) — read/cache side (TanStack-Query-shaped) |
patterns/resources.md |
Workflow after a write succeeds (navigate / toast / fold errors — mutation :reply-to), mixed-scope invalidation, optimistic writes |
patterns/resources-mutations.md |
| HTTP with status-aware retries / error projection / batching | patterns/managed-http.md |
| Form input with validation and submit | patterns/forms.md |
| Long-running browser-side work | patterns/long-running-work.md |
| Custom Promise / callback / Web Worker integration, or a fire-and-forget effect | patterns/async-effect.md |
| App boot (configure, hydrate, navigate) | patterns/boot.md |
| Real-time bidirectional connection | patterns/websocket.md |
| View rendering every legal lifecycle state | patterns/nine-states.md |
| Async result arrives after state moved on (epoch suppression idiom) | patterns/stale-detection.md |
| Parameterised widget rendered N times (entity-id idiom) | patterns/reusable-components.md |
| View wrapping a stateful JS library (chart / map / editor) | patterns/stateful-components.md |
| SSR form POST handling (progressive enhancement) | patterns/form-action.md |
SSR data load before render — route-owned blocking :resources (:blocking? true); machines are synchronous-only under SSR, so there is no loader-machine fan-out |
patterns/resources.md §Route-driven loading |
Patterns compose; a screen can use Forms on submit, RemoteData for the request, and WebSocket for a push.
Cross-cutting concerns — orthogonal to pattern choice; load alongside the primary leaf when the trigger fires.
| Concern | Cross-cutting leaf |
|---|---|
Declaring data sensitive / large; the owner-classifies / framework-projects / sinks-consume egress model + six :rf.egress/* profiles |
references/cross-cutting/privacy-and-elision.md |
Datadog / Sentry / Honeycomb production listeners that survive goog.DEBUG=false |
references/cross-cutting/production-observability.md |
Head/meta (reg-head / head-model); extending the shipped :rf/hydrate handler (re-register only to change merge policy) |
references/cross-cutting/ssr-authoring.md |
Path overlap / valid segments / [:rf.path/param …] templates; canonical EDN identity for a resource key / route param / work id |
references/cross-cutting/path-and-identity.md |
Testing your views
"Does the screen show the right thing? Does the button dispatch the right event?" — use the hiccup-walk pattern (walk the view-fn's returned hiccup by :data-testid, not a browser-mount; state-only assertions miss the view-broken and wrong-frame-dispatch bugs the walk catches). Recipe: references/cross-cutting/testing-views.md.
Where the depth lives
Load at most two leaves per task. If a task seems to need three, it likely spans patterns and should be broken up.
Fundamentals — references/fundamentals/: events.md, fx.md, cofx.md (value-returning reg-cofx, :rf.cofx/requires), subs.md, views.md (reg-view, injected dispatch/subscribe, reg-view*), flows.md (reg-flow, flow-vs-sub), schemas.md, frames.md, images.md (rf/image, image-order composition, frame-isolation story — EP-0023), event-state-cycle.md, project-structure.md.
State machines — references/state-machines/: reg-machine.md (declaration, guards/actions, divergence flags), xstate-translation.md (the full xstate→re-frame2 catalogue), machine-schemas.md (:schemas :data/:output + snapshot redaction), regions.md (parallel), tags.md, spawn.md (child machines), history.md (:type :history re-entry), cancellation.md. Standing model across all: think in xstate, then map onto re-frame2.
Tooling — references/tooling/: stories.md, routing.md, story-recorder.md (record canvas interactions as a :script), story-mcp-loop.md (story-mcp author/refine side; run-loop handoff to re-frame2-pair), xray.md (devtools panel mount + launch modes). Standing model for Story: think in Storybook JS, then map onto Story (stories.md has the concept map).
Cross-cutting — references/cross-cutting/: testing.md (with-frame, dispatch-sync, compute-sub, machine snapshots, fx stubs), testing-views.md (the view-tree axis: re-frame.test-helpers, the single-frame e2e shape, the hiccup walk), api-cheatsheet.md (one-page reg-* signature index), privacy-and-elision.md (path-based fail-open egress: owner classifies / framework projects / sinks consume; the six :rf.egress/* profiles), production-observability.md (rf/register-observability-sink! + frame :observability / the rf/configure! default), ssr-authoring.md (reg-head/head-model + the :rf.ssr/check-* fxs), path-and-identity.md (the :rf/path algebra + canonical EDN identity).
Patterns — patterns/: one leaf per canonical pattern (see table above). Each opens with load triggers, the canonical mini-declaration, the features it uses, trade-offs, and the worked-example link. Pattern → example app: examples-map.md.
Decision trees — decision-trees/: pick-a-pattern.md, slice-or-machine.md.
Authoring workflow (every task)
- Identify the surface — event? sub? fx? cofx? view? machine? route? story? schema?
- Load at most two leaves (the relevant fundamentals or pattern; a second only if the task spans two surfaces).
- Match the canonical declaration in the leaf; do not re-derive.
- Pick the feature prefix (
:cart/...,:auth/...) — never:rf/*. - Cross-check a worked example or reference view that uses the same shape when one exists (in the re-frame2 repo, that's
examples/**; in a consumer app, your project's own reference views). - Schema only at boundaries.
- Use
reg-*macros unless the macro shape can't express the need. - Cut-test comments: would I write this same comment in a React / Vue / Elm app? If yes, cut it.
- Run the gate: discover the nearest declared noninteractive one (
references/cross-cutting/testing.md§Discovering a project's gates), run it, fix what it finds, and report the exact command and result. Hand off only when it is interactive / visual, needs a live runtime (re-frame2-pair), does not exist, or the user said not to — and say which.
Done checklist
- Ids do not collide (
grepthe codebase for the chosen id). - Schema registered for any new boundary.
- No
:rf.*application keywords. - Cut-test passed on comments.
- Shape matches the canonical declaration in the leaf.
- If a worked example exists, the new code's shape matches it.
- Run the nearest relevant gate before declaring done — exact command + result reported, or the hand-off reason named (step 9).
How re-frame2 differs from re-frame v1
Do not re-derive v1 mappings from training memory. Migration workflow + breaking-change rule index: skills/re-frame-migration/; authoritative rule corpus: migration/from-re-frame-v1/README.md.
Background reading (optional)
For "why does it work this way?" or a feature whose shape isn't obvious. All route via the repo-root table: SKILL-REDIRECT.md → Principles, Conventions, Construction prompts (AI-shaped templates), and the EP — … rows under §Spec corpus for design rationale.
re-frame2 (v2 line). v1: re-frame. Full skill-disambiguation matrix: skills/README.md §Skill routing — single source. Deep-dive links route through SKILL-REDIRECT.md.