SKILL.md
name: re-frame-pair description: > Pair-program with a live re-frame application via shadow-cljs nREPL. Inspect app-db, dispatch events, hot-swap handlers, trace dispatches, read epoch data, time-travel, and perform post-mortems without source edits when probing. Use whenever the user asks about their running re-frame app or mentions: re-frame, app-db, dispatch, subscribe, reg-event, reg-sub, reg-fx, epoch, interceptor, re-frame-10x, re-com, shadow-cljs. allowed-tools:
- Bash(scripts/discover-app.sh *)
- Bash(scripts/app-summary.sh *)
- Bash(scripts/eval-cljs.sh *)
- Bash(scripts/console-tail.sh *)
- Bash(scripts/handler-source.sh *)
- Bash(scripts/inject-runtime.sh *)
- Bash(scripts/dispatch.sh *)
- Bash(scripts/trace-recent.sh *)
- Bash(scripts/watch-epochs.sh *)
- Bash(scripts/tail-build.sh *)
- Read
- Edit
- Write
- Grep
- Glob
re-frame-pair
You are pair-programming with a developer on a live, running re-frame application in a browser tab behind shadow-cljs watch.
Build tool: shadow-cljs only. re-frame-pair drives the runtime via
(shadow.cljs.devtools.api/cljs-eval ...)over nREPL. Figwheel-main, lein-cljsbuild, and vanilla cljs builds aren't supported. Ifdiscover-app.shreturns:reason :unsupported-build-tool, surface it verbatim — multi-build-tool support is tracked at #14.
The point isn't just reading app-db — it's closing the debug loop against the live runtime. Ask the running app which event fired, what changed in app-db, which effects fired, which subs re-ran or were served from cache, which views rendered, and where the source lives.
The Empirical Loop
Prefer this loop over reading source and guessing:
- Observe the current runtime state.
- Inspect the relevant epoch.
- Form a hypothesis.
- Probe with a dispatch, app-db read, hot-swap, side-effect stub, or REPL eval.
- Compare the new epoch with the baseline.
- Only then edit source.
Reach for the loop especially when:
- A UI didn't update, or its visible result is wrong.
app-dbended up in a bad state.- The user can't remember the path that caused a bug — search the recent epoch trail.
- You need to find the source behind a visible control.
- You want to test a handler / sub / fx change before committing it.
What is an epoch?
An epoch is the record of everything that happened in response to one dispatch — the event, interceptor chain, handler result, effects fired, subscriptions that ran or hit cache, components that rendered, and app-db before/after diff. The full field list lives in docs/skill/operations.md. Use compact :app-db/diff first; request full snapshots only when the diff isn't enough.
When Not To Use
- The app isn't running yet, so there's no live runtime to attach to.
- The code being debugged isn't routed through re-frame.
- The user wants pure source-only refactor work with no behavioural verification.
Quick Start
scripts/discover-app.sh
scripts/app-summary.sh
scripts/dispatch.sh --trace '[:cart/apply-coupon "SPRING25"]'
discover-app.sh is mandatory at the start of each session. It finds the nREPL port, switches to CLJS mode, injects the runtime namespace, and returns the app-db's top-level keys plus dispatch-ids of the most recent events. If it reports :warning :multiple-builds, ask the user which build to attach to before continuing.
Most sessions need only three ops — app-summary, dispatch, and last-epoch (via eval-cljs). For the full reference see docs/skill/operations.md.
Detail On Demand
| Question / task | Read |
|---|---|
| Operation names, command forms, epoch fields, console tags | docs/skill/operations.md |
Post-mortem — how did app-db get here? Don't ask the user to reconstruct repro steps; search recent epochs for the bad transition, follow :parent-dispatch-id to walk the chain |
docs/skill/post-mortems.md |
UI didn't update; rendered output wrong; resolve a visible control to :src |
docs/skill/ui-debugging.md |
Runtime probing, hot-swap, time-travel, side-effect stubs (--stub flag) |
docs/skill/probing-and-hot-swap.md |
Source metadata; :no-source-meta / nil :event/source; offering the swap to re-frame.core-instrumented |
docs/skill/source-meta.md |
| Form-by-form trace inside a handler / sub / fx (re-frame-debux — an optional source-printing instrumentation library) | docs/skill/debux.md |
Errors, setup failures, :reason keyword translations, health checks |
docs/skill/troubleshooting.md |
Operating Principles
- Read live state before guessing.
app-db/snapshot,trace/last-epoch, orapp-summaryfirst; hypothesis after. - Compare epochs, don't rely on intent. Capture the epoch, run the smallest change, compare.
- REPL access is your second mode. Hot-swap a handler / sub / fx, redefine a
defn, orreset!app-db. REPL changes are ephemeral; source edits stick. After a successful hot-swap, ask the user before transferring the patch to source. - After any source edit, wait for hot reload. Run
scripts/tail-build.sh --probe '<form>'before dispatching, or your next call may run against the pre-reload code. - Surface failures verbatim. Every script returns structured EDN. Translate
:reasonto plain English usingdocs/skill/troubleshooting.md.:reinjected? trueis informational, not an error — the runtime was re-shipped after a browser refresh. - Narrow detail as you go. Summaries first; drill in on request.
- Resolve UI references to
:src—re-com/button at app/cart/view.cljs:84grounds the conversation. - Prefer dedicated shims over raw
eval-cljs. Shims handle namespacing, quoting, and flag combinations. - Time-travel rewinds
app-dbonly — side effects already fired aren't reversed. Before anyundo/*op, check whether the cascade contained:http-xhrio/ navigation / etc., and warn the user. - Requires
re-frame.trace.trace-enabled? true. Ifdiscover-app.shreports:trace-enabled-false, almost every workflow degrades — fall back toapp-db/snapshotand dispatch only, and tell the user how to enable tracing (10x's install guide covers the:closure-definesflag). - Offer the source-meta swap when relevant. When you read source files and notice the host uses plain
re-frame.core(orre-frame.alpha), and a trace shows:no-source-metawhile the user is asking a "where did this come from?" question, offer to swap their:requiretore-frame.core-instrumented. Seedocs/skill/source-meta.md. - Drill into elision markers; don't treat them as data loss. Every cljs-eval response is wire-bounded — values that would exceed shadow-cljs's ~1MB printer cap are replaced in place with
{:rfp.wire/elided true :path [...] :cursor "<id>" :type ... :count ... :summary {...}}markers. The runtime keeps the full value buffered; fetch a slice witheval-cljs.sh '(re-frame-pair.runtime.wire/fetch-path "<cursor>" [<path>])'. The bash response also surfaces top-level:rfp.wire/cursorand:rfp.wire/elisionswhen an elision happened, and:rfp.wire/value-fits? falseflags it. Read the elision's:summaryfirst ({:type :map :count 84 :sample-keys [...]}) — usually that answers the question without needing to drill at all.