Posts
-
Projectile 3.4
Projectile 3.4 is out! After the four releases that preceded it this summer, this one is polish rather than ambition.
Most of it came out of an annoyance I’d been living with for years without ever quite naming it: a lot of what I work on isn’t one directory. CIDER is really CIDER plus
cider-nreplplusorchardplusclj-refactorplus a handful of others. RuboCop is RuboCop plusrubocop-astplusrubocop-railsand the other extension gems plus the style guide. I’ve been maintaining families of repositories for the better part of a decade.Lately I’ve also been reaching for git worktrees far more than I used to, so the same repository is now checked out two or three times on my disk at any given moment.
Projectile saw all of that as a pile of unrelated projects, which is exactly what it looks like from the outside.
Read More -
RuboCop 1.89: Project-Wide Analysis with Rubydex
RuboCop 1.89 is out, and it comes with the usual assortment of new cops and bug fixes. But there’s one theme running through this release that I’m particularly excited about, and that’s what I want to talk about today: RuboCop is finally learning to look beyond a single file.
Read More -
Leveling Up CIDER's ClojureScript Support
Continuing the series on the notable changes in CIDER 2.0, let’s talk about ClojureScript - forever the trickier sibling in the CIDER family.
I’ll start with a confession I’ve made before: I rarely use ClojureScript myself, which is a big part of why its support in CIDER has historically lagged behind Clojure’s. Every “State of CIDER” survey reminds me of this, usually in the comments section, occasionally in all caps. So in the 2.0 cycle I decided to stop feeling vaguely guilty about it and actually do something - across every layer of the stack: CIDER itself,
cider-nrepl, and Piggieback.First, a strategic decision
The most important ClojureScript change in CIDER 2.0 isn’t a feature - it’s a decision about what not to build. Some of CIDER’s most powerful tools (the debugger, enlighten, tracing, profiling) are deeply tied to JVM runtime introspection, and porting them to ClojureScript would be a massive effort with a poor cost/benefit ratio. Rather than keeping them in eternal “maybe someday” limbo, we’ve explicitly scoped them as Clojure-only and focused the actual work on the things cljs users hit every day: evaluation, testing, error reporting, and clear behavior everywhere else.
That last part matters more than it sounds. Historically, invoking a JVM-only command in a ClojureScript REPL would fail in some confusing way - a cryptic error, a JVM-flavored result, or silence. Now the ops themselves report a
clojure-onlystatus, and CIDER tells you plainly that the command isn’t supported for ClojureScript. Knowing what a tool won’t do is half of trusting it.What actually got better
- The regular test commands (
cider-test-run-ns-testsand friends) now work in ClojureScript REPLs, asynchronouscljs.test/asynctests included. Previously CIDER just refused, and you were stuck evaluating(run-tests)by hand like an animal. - Expanding your own macros (e.g.
ones brought in via
:refer-macros) used to silently echo the form back unexpanded - a bug filed all the way back in 2017. The compiler environment is now threaded to the analyzer properly, and it just works. cider-nreplnow resolves the ClojureScript compiler environment through a provider chain, with a dedicated shadow-cljs provider - so the static-analysis ops keep working in a shadow REPL that never loads Piggieback.- The new
cider-tapviewer works with ClojureScript too: a runtime helper buffers tapped values and the JVM side streams them to Emacs. (Tapped cljs values aren’t inspectable - they live in the JS runtime - but you see them as they happen.) - ClojureScript stack frames now render their
ns/fnproperly instead of degrading tonil/nil, and unqualified core vars resolve againstcljs.corerather than falling back toclojure.core(which quietly broke things like indentation metadata). - A recent ClojureScript on the classpath (whose
Closure compiler wants JDK 21+) no longer crashes
cider-nreplat startup on an older JDK - you get a Clojure-only session instead of no session. - Piggieback itself got a round of bug fixes in the 0.6.x/0.7.0 releases - it’s easy to forget it exists (which is rather the point of it), but it powers most cljs REPLs CIDER talks to.
The documentation kept pace too: the new full-stack Clojure + ClojureScript guide covers the two-REPLs-one-project setup that trips up nearly everyone, and the ClojureScript docs got a general refresh.
An unexpected assist
Fun aside: this is the area where AI coding agents helped me the most during the 2.0 cycle. My ClojureScript experience is limited, but between the excellent bug reports from the community and the ability to quickly prototype and test fixes against real shadow-cljs and figwheel setups, problems that had been “someone who knows cljs should look at this someday” for years finally got fixed. Make of that what you will.
What’s next
I keep pondering some form of “native” shadow-cljs support, given that shadow-cljs is what most ClojureScript users actually run these days. That’s still very much in the hammock phase, so don’t hold me to it - but the direction is clear: fewer moving parts, clearer errors, and honesty about what’s supported.
If you’re a ClojureScript user, I’d genuinely love to hear how 2.0 feels in your daily work - the feedback loop is what keeps this improving. Keep hacking!
Articles in the Series
- The regular test commands (
-
Sharpening CIDER's Debugging Tools
The series on the notable changes in CIDER 2.0 rolls on. This time: the “what is my code actually doing?” toolbox - the debugger, tracing, enlighten, and the new tap viewer. This was the part of the release I enjoyed working on the most, and the part that needed the most love.
The debugger, dusted off
CIDER’s interactive debugger is one of its most impressive features and, paradoxically, one of its least reliable ones. Instrumenting arbitrary Clojure code is hard - the debugger rewrites your forms to capture locals at every step, and the corner cases are endless. Over the 2.0 cycle (and the 0.62.x releases of
cider-nrepl) a whole family of long-standing instrumentation bugs got fixed:- Record literals embedded in code survive instrumentation instead of being quietly downgraded to plain maps - which used to break protocol dispatch in anything that compiled routes or components into records (compojure users know the pain).
defrecord/deftypeinline methods no longer blow up with the infamousUnable to resolve symbol: STATE__error - the instrumenter now sensibly skips the method bodies, which compile to real JVM methods that can’t capture debugger state.#dbgon a bare collection literal triggered the same error; fixed too. And heavily destructured argument lists used to crash instrumentation in their own special way - not anymore.- A form too large to instrument (yes, that’s a JVM limitation -
Method code too large!) now degrades gracefully: CIDER retries without local capture and tells you what happened, instead of surfacing a raw compiler error. - Quitting a debug session uses nREPL’s interrupt machinery instead of the
deprecated
Thread.stop, so it keeps working on modern JDKs whereThread.stopis simply gone.
The UX got attention too. Quitting the debugger with
qfinally restores point to where you started the session - a request filed in 2016 - instead of stranding you at the last breakpoint. The force-step-out key works again. And all the debugger’s single-key commands are now proper named commands with a transient menu (?) listing them, so you’re never stuck trying to remember whether locals waslorL.Tracing grew a home
clojure.tools.trace-style tracing has been in CIDER forever, but the output was always interleaved into the REPL, where it fought with your actual work. CIDER 2.0 gives traces a dedicated, live-streaming*cider-trace*buffer:
Calls fold and unfold (
TAB, orF/Ufor everything at once),n/pmove between calls, and.jumps to a function’s definition.cider-list-tracedanswers the eternal “wait, what did I even trace?”, andcider-untrace-allcleans the slate.Enlighten, back from the dead
Enlighten - the mode that displays the values of locals inline as your code runs - has been in “experimental” limbo since 2016. It finally got a proper overhaul: a real test suite, fixes for the same record/deftype instrumentation bugs as the debugger (they share machinery), and - importantly - manners. You can now enlighten a single form with
cider-enlighten-defun-at-pointinstead of flipping a global mode, andcider-enlighten-stopturns everything off at once, rather than making you re-evaluate every function in penance.Every local and every intermediate result, right there in the buffer:

Tap into your programs
New in 2.0:
cider-tap, a buffer that streams every value sent totap>and lets you crack any of them open in the inspector withRET.tap>has quietly become the Clojure community’s favorite debugging primitive, and now you don’t need an external tool like Portal or Reveal for the basic workflow - though those remain great if you want more. (ClojureScript taps stream too; they’re just not inspectable, since the values live in the JS runtime.)It’s
printlndebugging, minus the println guilt.The connective tissue
A few related quality-of-life items round out the picture: stack frames for top-level anonymous functions jump to their actual source instead of
clojure.core/fn(a bug from 2020), ClojureScript frames render theirns/fnproperly, and the macroexpansion tooling - a debugging tool in its own right - got a full makeover that deserves (and will get) its own article.None of these tools is new. That’s rather the point: the 2.0 debugging story is mostly the existing tools becoming trustworthy. A debugger you don’t trust is worse than no debugger at all.
The debugging docs cover everything in detail. Keep hacking!
Articles in the Series
-
Projectile 3.3
Projectile 3.3 is out! That’s the fourth release this month, which probably tells you something about how much fun I’ve been having with Projectile lately.
This one is mostly about a single theme - Projectile knowing more about your project without you having to tell it anything.
Read More
Subscribe via RSS | View Older Posts