Target audience: the re-frame-debux maintainer. Synthesis of three parallel surveys (rfd-pa2): current-state of the library, GitHub issue triage, philoskim/debux feature comparison. Cross-referenced with
re-frame-pair's recent debux survey (/home/mike/code/re-frame-pair/docs/inspirations-debux.md) so the recommendations also serve as a checklist of what would amplify re-frame-pair's on-demand REPL hot-swap recipe.
re-frame-debux is in stable maintenance mode with a now-expanded tracing surface (day8.re-frame.tracing/{fn-traced,defn-traced,dbg,dbg-last,fx-traced,defn-fx-traced} plus day8.re-frame.tracing.runtime), a working zipper-based AST walker, and a production-elision story (tracing-stubs jar + shadow-cljs :ns-aliases). The core engine works and produces useful output through re-frame-10x's Code panel.
This document began as an early-2026 recovery plan. The original emergency items have since shipped: issue #40's loop/recur hang is fixed, core dependency pins were refreshed, the :code payload schema is documented, :locals / :if landed, and the v0.7 feature quartet added :once, fx tracing, frame markers, and verbose tracing. What remains is no longer "unstall v0.6"; it is keeping the maintainer-facing roadmap truthful, closing the operator-owned upstream issue loop, and treating the new trace payloads as stable contracts for downstream tools.
Current highest-leverage follow-ups, in priority order:
docs/v0.6-roadmap.md should stay historical/status documents rather than active queues.:code / :trace-frames / :fx-effects payloads, tap output, and tracing-stubs parity are now the highest-value regression surfaces.Public surface. Two macros, fn-traced (tracing.cljc:123) and defn-traced (tracing.cljc:89), gated by (is-trace-enabled?) (a goog-define on trace-enabled?, default false). Both expand to dbgn-forms calls that wrap each form in the function body with send-trace!. A lower-level dbgn macro (line 33) is exported and a custom-macro registration API exists (register-macros!, line 38) but is rarely needed.
Architecture. The zipper machinery lives in src/day8/re_frame/debux/dbgn.clj. It walks each form via a custom sequential zipper (util.cljc:20), classifying it against the macro registry in cs/macro_types.cljc (control flow, definition forms, threading macros, special forms — ~50 entries) and either skipping or instrumenting accordingly. The walker is two-pass — insert-skip → insert-trace → remove-skip — with a maintainer TODO (dbgn.clj:243) questioning whether this can collapse to one pass.
Trace sink. send-trace! (util.cljc:132) writes through re-frame.trace/merge-trace! into the current trace event's :tags :code vector, with payload {:form, :result, :indent-level, :syntax-order, :num-seen}. A separate send-form! (line 129) sets :tags :form once for the outermost form. re-frame-10x picks up :code and renders its Code panel.
Production split. Two mechanisms, both documented in README.md:74–128:
[day8.re-frame/tracing-stubs "0.5.3"] in :prod profile; the stubs jar (tracing-stubs/src/...) defines defn-traced and fn-traced as plain defn / fn.:ns-aliases (Option 2, since v0.5.5) — single jar; release config aliases day8.re-frame.tracing to day8.re-frame.tracing-stubs. This is the recommended path for shadow-cljs users.Both fail silently if misconfigured (no runtime warning), which is a real footgun — see §5.
Tests. ~1,482 lines across core_test.clj, dbgn_test.clj, traced_macros_test.clj, indenting_test.cljc, common/util_test.cljc, plus a Karma-driven CLJS browser runner. Coverage is good for macro-expansion edge cases. The gap is integration tests — there is no end-to-end test that fires a real re-frame event through a fn-traced handler and asserts the resulting :code tag landed in 10x's epoch buffer. Several of the closed-and-reopened bug categories (cond macroexpansion, indentation regressions) would have been caught earlier with one.
Half-done / smells. Remaining TODOs in dbgn.clj and util.cljc still flag known limitations: clojure.core symbol cleanup not generalised (#102), macroexpanded form not captured in payload (#134), and a few indentation / skip-shape notes that need fresh triage. The old plain-list and fx-map TODOs have been retired as stale after v0.7: quoted/data-list forms stay opaque through quote / :skip-all-args-type, and per-key fx-map tracing is handled by fx-traced / -emit-fx-traces! rather than zipper descent. The spy-* family (util.cljc:343–366) appears unused. The README claims Clojure 1.8.0+ support but the project pins 1.10.3.
Example fixture. example/ exists, builds, and runs, but pins re-frame 0.10.8 (current is 1.4+) and Clojure 1.10.1 — clearly not exercised in years. A documentation-test (or a CI step that runs lein dev and checks for a clean compile) would notice the staleness immediately.
GitHub reports 6 open issues, all 2+ years old. Themes:
| # | Title | Theme | Age | Scope | Action |
|---|---|---|---|---|---|
| 40 | fn-traced/defn-traced doesn't terminate with loop/recur | bug | 2021-06 | 1 week | fix-now (P0) |
| 38 | Images missing in README | docs | 2020-09 | 1 hour | easy fix or close-as-stale |
| 37 | Send Trace marking function entry/exit | feature | 2020-07 | 1 week | defer, depends on 10x design |
| 36 | Include all trace — don't filter noisy bits | feature | 2020-07 | 1 week | defer, design-required |
| 35 | Self-hosted ClojureScript support | feature | 2020-07 | 1 month | defer, architectural |
| 34 | Send exceptions to 10x | feature | 2020-07 | 1 month | defer until design discussion resolved |
Pattern from the closed issues. The closed log (#21, #22, #23, #29, #30, #31, #33) shows that macro-walker fragility is a recurring source of friction. #31 (cond NPE), #29 / #22 / #23 (indentation bookkeeping), and now #40 (loop/recur cycling) all stem from the same area: position/skip tracking failing when nesting is unusual or recursion involves tail calls. #31 was closed with "Almost 100% sure this was fixed on master now" — without a regression test. Recommendation: add a test fixture covering each of cond, cond->, loop+recur, letfn, for, and case with deeply nested bodies; treat that fixture as a load-bearing regression set.
On the 2020 sprint backlog (#34, #35, #36, #37): None has had any movement since opened. The honest move is one of:
#38 (broken images) is a one-hour fix or a close-as-stale; either is fine.
re-frame-debux retains debux's zipper engine (dbgn) but exposes ~20% of the upstream macro surface. Of the missing options, three transfer cleanly to the re-frame-trace sink model; the rest are console-only.
| Option | Transfers? | Effort | Value |
|---|---|---|---|
:locals / :l — capture lexical bindings | yes | ~50 LOC | High. Adds {:locals [[sym val] ...]} to the trace payload. The single biggest readability win for post-mortem inspection. |
:if — conditional emit | yes | ~5 LOC | Medium. One when guard around send-trace!. Useful for filtering high-frequency traces (e.g. only log when (:status %) is :error). |
:once / :o — duplicate suppression | partial | ~80 LOC | Low–medium. Needs a per-form ID (gensym at macro time) plus a runtime atom of {id → last-result}. Doable but adds a runtime cache that needs lifecycle thought. |
:final / :f | partial | small | Low. Could filter the payload to only the outermost form, but the existing two-step (send-form! for outer + send-trace! for inner) effectively already provides this with a different framing. |
:style, :js, :print, :ns | NO | — | Console-only. re-frame-debux deliberately routes to edn trace tags; styling has no consumer in 10x. |
dbg, dbg-last | yes | shipped | dbg (commit 992fd28) and dbg-last (commit 304ef11) ship single-form tracing onto the same :code surface as fn-traced. |
dbgt, clogt (transducer-aware) | NO | — | Out of scope — see §6 "Deliberately out of scope". Per-element step tracing is a poor fit for the :code payload (firing N records per transduce call) and the userland (comp xf (map #(do (tap> ...) %))) recipe covers the rare case without macro surface. |
clog, clogn (console variants) | NO | — | Console-only formatting; dbg/dbgn already cover the re-frame-trace path with the same call shape. |
tap> integration | yes | shipped | set-tap-output! (commit 18b06dc) plus tap parity for forms, frames, and fx effects (d207b45); dbg/dbg-last accept :tap? for in-trace tap fan-out. |
break / DevTools breakpoints | NO | — | Halts the runtime; conflicts with re-frame-pair's hot-swap workflow. |
The single most valuable port is :locals. Locals capture turns fn-traced from "what value did this expression produce?" into "what values did this expression produce, given which bindings were in scope?". For a typical re-frame handler that walks (let [user-id ... cart ...] ...), the trace is currently opaque about which inputs produced which intermediate values; :locals fixes that.
The :if option is the lowest-risk addition. Five lines of code, no new state, no design questions. It's the kind of thing that's worth shipping in the same commit as :locals to amortise testing effort.
re-frame-pair is a Claude Code skill that drives running re-frame apps via REPL. It already has hot-swap as a primitive — meaning the skill can wrap any registered handler with fn-traced on demand, with no source edits. re-frame-debux is uniquely well-positioned for this because its trace data already flows into re-frame.trace/merge-trace!, which re-frame-pair already reads via 10x's epoch buffer.
The integration is described in detail in /home/mike/code/re-frame-pair/docs/inspirations-debux.md. The three original re-frame-debux-side recommendations have all shipped; the original text is retained here as design history with current status notes.
(a) Document the :code payload schema. ✓ Shipped in commit 6b4f042. The fields {:form, :result, :indent-level, :syntax-order, :num-seen} were written to :tags :code but their semantics were not documented anywhere — neither in the README nor in inline comments. The schema comment near send-trace! now names each field, gives an example shape, and notes the tidy-macroexpanded-form step, turning the payload into a contract for re-frame-pair, third-party 10x panels, and custom inspectors.
(b) Make the production-mode warning loud, not silent. ✓ Shipped in commit 10d27fd. If a user's release build accidentally ships day8.re-frame.tracing instead of the stubs, the first trace send now emits a loud production-mode warning. The check is covered by prod_mode_warn_test.cljc.
(c) Expose public wrap-handler! / unwrap-handler! runtime functions. ✓ Shipped in day8.re-frame.tracing.runtime in commit 4ed07c9, with follow-up coverage for missing-handler, double-wrap, sub/fx/event-fx/event-ctx, and production-stub parity. re-frame-pair can now wrap and restore registered handlers with one public call instead of synthesising macro forms at the REPL.
Sub-tracing and fx-tracing. ✓ Shipped. wrap-sub! / wrap-fx! are available as runtime conveniences, and fx-traced / defn-fx-traced shipped in commit bae1b0d to emit per-key :fx-effects entries without generalising the zipper walker. Runtime wrap-event-fx! / wrap-event-ctx! later moved to the same per-effect surface in commit 67181fa.
This section is now an implementation ledger for the original roadmap. "Operator-owned" means local code work is complete or intentionally declined, but the human maintainer still needs to update upstream GitHub issue state.
loop/recur non-termination — ✓ Shipped. recur was added to :skip-form-itself-type with regression coverage in commit db9b7de; the root o-skip? FQN bug was fixed in 48de2e8; the integration wiring and a tail-position fix landed in 8d4e331.rfd-iqz series (c940967 / a1fc73e / acea1d0 / 4109a0c) and commit ee768f2, with later Clojure / CLJS pin alignment in 48258d4.:code payload schema — ✓ Shipped in commit 6b4f042.docs/v0.6-roadmap.md carries close-comment templates for all four issues.:locals and :if options to fn-traced — ✓ Shipped in commit 4d6e507, with follow-up fixes for option parsing, varargs locals, and production stubs.wrap-handler! / unwrap-handler! runtime API — ✓ Shipped in commit 4ed07c9. Follow-ups added feature detection, edge-case tests, sub/fx/event-fx/event-ctx wrappers, and tracing-stubs parity.10d27fd.6e4f5d1 scaffolded four pending integration tests; commit 8d4e331 wired them into bb test and fixed the macro-walker bugs they exposed. Browser coverage continued later in the CLJS integration fixture.:once / duplicate-suppression option — ✓ Shipped in v0.7.0 in commit 33225e8.fx-traced / defn-fx-traced in commit bae1b0d, with runtime wrap-event-fx! / wrap-event-ctx! parity added in commit 67181fa.:trace-frames tag emitted by fn-traced / defn-traced in commit 8ba53a8.:verbose / :show-all option on fn-traced / defn-traced / dbgn in commit 0177254.dbg (992fd28) and dbg-last (304ef11).:final / :f (8eeadf6) and :msg / :m (b0a68b9).set-tap-output! (18b06dc) and later tap parity for forms, frames, and fx effects (d207b45).b471026, 622a9dd, a13f7d8, and the integration/browser fixtures that now exercise the tricky paths.docs/v0.6-roadmap.md, and this plan aligned with the shipped surface.:code, :trace-frames, :fx-effects, tap output, runtime wrappers, and tracing-stubs parity.Self-hosted ClojureScript support (#35) — declined 2026-04-27. Architectural cost too high for the demand. Close #35 with this context.
Exception tracing (#34) — declined 2026-04-27. Design tension with browser break-on-exception not worth resolving. Close #34 with this context.
Transducer-aware tracing macros (dbgt / clogt) — declined 2026-04-28. Upstream debux v0.8 ships dbgt to instrument transducers so each step of a comp chain emits a console record per element. Three reasons it doesn't earn a slot in day8.re-frame.tracing:
:code and the 10x Code panel are designed around per-form records (one trace per evaluated subexpression). A transducer that runs across a 10k-element collection would emit tens of thousands of records per dispatch, swamping the panel and the :tags vector. :if / :once filters help but don't change the shape — the surface is wrong for high-frequency per-element data.dbgn already shows the input collection, the composed transducer value, and the final reduction result for (transduce xf rf init coll) — the gap is per-element step inspection, which is a niche debugging need.(map #(do (tap> [::label %]) %)) between transducers in a comp, or write a 3-line tap-xf helper. tap> already routes through the configured tap output (set-tap-output!), so the same downstream consumers (REPL, custom panels) see the data with no library change. Adding a macro, production stub, and CLJC walker integration to wrap that recipe would be more surface than the demand justifies.If demand surfaces later (e.g. a re-frame app that genuinely needs step-level transducer inspection in 10x), the right shape is a runtime helper in day8.re-frame.tracing.runtime — (wrap-xf "label" xf) returning a step-instrumented transducer that emits compact :debux/xf-step tap payloads — not a dbgn-style macro that re-implements the zipper walker for transducer composition. That would be ~30 LOC, no compile-time analyzer, and zero impact on the :code panel's existing per-form contract.
dbg / single-form tracing alongside fn-traced? No deliberate omission. dbg shipped in commit 992fd28, and dbg-last followed in 304ef11 for thread-last pipelines.:locals capture from &env (compile-time bindings) or use a runtime probe? Resolved in rfd-880 (commit 4d6e507) — &env only; CLJS captures less, accepted.wrap-handler! / unwrap-handler! shipped in day8.re-frame.tracing/runtime (commit 4ed07c9); re-frame-pair consumes via the public surface (rfp-6z2, commit a285c1d).CHANGELOG.md now exists and carries user-visible entries for dependency bumps, option additions, runtime API changes, and production-stub parity.End of plan. Recommended first action: finish upstream issue hygiene and keep this file as a historical/status ledger, not an active queue of already-shipped work.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |