server{listen80;server_nameyour-domain.com;# or your VPS IPlocation/{proxy_passhttp://localhost:3000;proxy_set_headerHost$host;proxy_set_headerX-Real-IP$remote_addr;proxy_set_headerX-Forwarded-For$proxy_add_x_forwarded_for;proxy_set_headerX-Forwarded-Proto$scheme;}}
7. Add HTTPS with Let’s Encrypt (optional but recommended)
sudo snap install--classic certbot # install certbotsudo ln-s /snap/bin/certbot /usr/local/bin/certbot # Prepare the Certbot command sudo certbot --nginx# follow the prompts to set up HTTPS
Certbot will auto-edit your Nginx config and set up auto-renewal.
Useful commands after deployment:
sudo systemctl restart my-app # restart after uploading new jarsudo journalctl -u my-app -f# tail logssudo systemctl status my-app # check status
When you deploy a new version, just scp the new jar over and sudo systemctl restart my-app.
I was recently asked for book recommendations and realized I have
never written down the ones that actually changed how I think. Not the
ones that taught me a language or a framework. The ones that shifted
something in me as an engineer. Here they are, in no particular order.
1. A Philosophy of Software Design — John Ousterhout
Short, opinionated, and right about most things. The core idea is
Deep Modules: hide complexity behind simple interfaces. This is
ETC from another angle. If your module has a complex surface, every
consumer pays that tax forever. Make the surface small and the
internals can evolve freely. I read it in a weekend. I have been
thinking about it for years.
2. Thinking in Systems: A Primer — Donella Meadows
I already thought in systems before reading this. What Meadows gave me
was the vocabulary. Stocks and flows. Feedback loops. Leverage
points. The places to intervene in a system, ranked from least to most
effective. When I write about NixOS or Emacs or OneLoop, I am
reaching for these ideas even when I do not name them directly. Short
and elegant.
3. The Art of Doing Science and Engineering — Richard Hamming
Hamming spent decades at Bell Labs studying what separates great
scientists from merely good ones. His central question: why do so few
people make significant contributions while so many are forgotten? The
answer is mostly about how you allocate your attention and whether you
have the courage to work on important problems instead of safe ones.
This maps directly to what I wrote about transmittable vs.
untransmittable knowledge. Hamming is rigorous, contrarian, and
deeply practical.
4. Designing Data-Intensive Applications — Martin Kleppmann
The best systems engineering book of the last decade. Kleppmann builds
from simple primitives (logs, hashes, SSTables) all the way up to
distributed systems, always grounding abstractions in the physical
realities they paper over. Even if you never build a distributed
database, the way Kleppmann thinks will change how you design any
system. This book is first principles applied to data.
5. Programmers at Work — Susan Lammers
Interviews with 19 legendary programmers from the 1980s. Bill Gates,
Andy Hertzfeld, Charles Simonyi, and others. What makes it timeless
is the focus on how they think about problems, not what they built.
You start to see patterns across how these minds approach complexity.
6. The UNIX Programming Environment — Kernighan & Pike
I live the Unix philosophy every day. Reading it from the source is
different than absorbing it osmotically. Kernighan and Pike do not
just describe the philosophy. They demonstrate it, building real
tools from shell pipelines, showing how composability creates power
that no monolith can match. Every time I write curl | jq or compose
tools in Emacs, I am living this book. My OneLoop agent (read, write,
edit, bash as four primitives) is an example of Unix way of doing things.
7. Structure and Interpretation of Computer Programs — Abelson & Sussman
The spiritual ancestor of everything I love about Lisps.
Homoiconicity. Functions as first-class citizens. Building abstractions
upward from the smallest possible primitives. SICP teaches you to
think about computation itself, not any particular language. If you
are a Clojure programmer and you have not read it cover to cover, fix
that.
8. The Design of Everyday Things — Don Norman
Not a software book, and that is the point. Norman's central idea is
that good design is invisible and bad design blames the user. This
applies to everything from Makefile help targets to agent tool
interfaces. Your self-documenting system? Your one obvious path? Your
inspectability obsession? Norman gives you the design vocabulary to
articulate why something feels wrong and how to fix it.
9. Computer Power and Human Reason — Joseph Weizenbaum
Weizenbaum wrote ELIZA in 1966, then spent the rest of his career
warning about delegating human judgment to machines. He is not
anti-computer. He is pro-human. He argues that some things should not
be computed, not because they cannot be, but because doing so corrupts
the thinking. Reading this in 2026, in the middle of the AI hype
cycle, feels like finding a kindred spirit from fifty years ago. This
is the philosophical depth behind "the agent is not the point."
10. The Mythical Man-Month — Frederick Brooks
The original essay on why adding people to a late project makes it
later. But it is really about something deeper: the irreducible
complexity of communication and the necessity of conceptual integrity
in software. Brooks' concept of conceptual integrity (a system should
reflect one set of design ideas, not a committee's compromises) is my
oneness principle by another name. The anniversary edition includes
"No Silver Bullet," which is even more relevant in the age of "AI will
solve everything."
—
These are not the only books I have read. They are the ones I keep
coming back to. The ones where I will catch myself mid-conversation
reaching for an idea and realizing it came from one of these pages. If
you have read them, you know what I mean. If you have not, start with
1, 2, and 9.
Syntax highlighting is a tool. It can help you read code faster. Find things quicker. Orient yourself in a large file.
Like any tool, it can be used correctly or incorrectly. Let’s see how to use syntax highlighting to help you work.
Christmas Lights Diarrhea
Most color themes have a unique bright color for literally everything: one for variables, another for language keywords, constants, punctuation, functions, classes, calls, comments, etc.
Sometimes it gets so bad one can’t see the base text color: everything is highlighted. What’s the base text color here?
The problem with that is, if everything is highlighted, nothing stands out. Your eye adapts and considers it a new norm: everything is bright and shiny, and instead of getting separated, it all blends together.
Here’s a quick test. Try to find the function definition here:
and here:
See what I mean?
So yeah, unfortunately, you can’t just highlight everything. You have to make decisions: what is more important, what is less. What should stand out, what shouldn’t.
Highlighting everything is like assigning “top priority” to every task in Linear. It only works if most of the tasks have lesser priorities.
If everything is highlighted, nothing is highlighted.
Enough colors to remember
There are two main use-cases you want your color theme to address:
Look at something and tell what it is by its color (you can tell by reading text, yes, but why do you need syntax highlighting then?)
Search for something. You want to know what to look for (which color).
1 is a direct index lookup: color → type of thing.
2 is a reverse lookup: type of thing → color.
Truth is, most people don’t do these lookups at all. They might think they do, but in reality, they don’t.
Let me illustrate. Before:
After:
Can you see it? I misspelled return for retunr and its color switched from red to purple.
I can’t.
Here’s another test. Close your eyes (not yet! Finish this sentence first) and try to remember what color your color theme uses for class names?
Can you?
If the answer for both questions is “no”, then your color theme is not functional. It might give you comfort (as in—I feel safe. If it’s highlighted, it’s probably code) but you can’t use it as a tool. It doesn’t help you.
What’s the solution? Have an absolute minimum of colors. So little that they all fit in your head at once. For example, my color theme, Alabaster, only uses four:
Green for strings
Purple for constants
Yellow for comments
Light blue for top-level definitions
That’s it! And I was able to type it all from memory, too. This minimalism allows me to actually do lookups: if I’m looking for a string, I know it will be green. If I’m looking at something yellow, I know it’s a comment.
Limit the number of different colors to what you can remember.
If you swap green and purple in my editor, it’ll be a catastrophe. If somebody swapped colors in yours, would you even notice?
What should you highlight?
Something there isn’t a lot of. Remember—we want highlights to stand out. That’s why I don’t highlight variables or function calls—they are everywhere, your code is probably 75% variable names and function calls.
I do highlight constants (numbers, strings). These are usually used more sparingly and often are reference points—a lot of logic paths start from constants.
Top-level definitions are another good idea. They give you an idea of a structure quickly.
Punctuation: it helps to separate names from syntax a little bit, and you care about names first, especially when quickly scanning code.
Please, please don’t highlight language keywords. class, function, if, elsestuff like this. You rarely look for them: “where’s that if” is a valid question, but you will be looking not at the if the keyword, but at the condition after it. The condition is the important, distinguishing part. The keyword is not.
Highlight names and constants. Grey out punctuation. Don’t highlight language keywords.
Comments are important
The tradition of using grey for comments comes from the times when people were paid by line. If you have something like
of course you would want to grey it out! This is bullshit text that doesn’t add anything and was written to be ignored.
But for good comments, the situation is opposite. Good comments ADD to the code. They explain something that couldn’t be expressed directly. They are important.
So here’s another controversial idea:
Comments should be highlighted, not hidden away.
Use bold colors, draw attention to them. Don’t shy away. If somebody took the time to tell you something, then you want to read it.
Two types of comments
Another secret nobody is talking about is that there are two types of comments:
Explanations
Disabled code
Most languages don’t distinguish between those, so there’s not much you can do syntax-wise. Sometimes there’s a convention (e.g. -- vs /* */ in SQL), then use it!
Here’s a real example from Clojure codebase that makes perfect use of two types of comments:
Disabled code is gray, explanation is bright yellow
Light or dark?
Per statistics, 70% of developers prefer dark themes. Being in the other 30%, that question always puzzled me. Why?
And I think I have an answer. Here’s a typical dark theme:
and here’s a light one:
On the latter one, colors are way less vibrant. Here, I picked them out for you:
Notice how many colors there are. No one can remember that many.
This is because dark colors are in general less distinguishable and more muddy. Look at Hue scale as we move brightness down:
Basically, in the dark part of the spectrum, you just get fewer colors to play with. There’s no “dark yellow” or good-looking “dark teal”.
Nothing can be done here. There are no magic colors hiding somewhere that have both good contrast on a white background and look good at the same time. By choosing a light theme, you are dooming yourself to a very limited, bad-looking, barely distinguishable set of dark colors.
So it makes sense. Dark themes do look better. Or rather: light ones can’t look good. Science ¯\_(ツ)_/¯
But!
But.
There is one trick you can do, that I don’t see a lot of. Use background colors! Compare:
The first one has nice colors, but the contrast is too low: letters become hard to read.
The second one has good contrast, but you can barely see colors.
The last one has both: high contrast and clean, vibrant colors. Lighter colors are readable even on a white background since they fill a lot more area. Text is the same brightness as in the second example, yet it gives the impression of clearer color. It’s all upside, really.
UI designers know about this trick for a while, but I rarely see it applied in code editors:
If your editor supports choosing background color, give it a try. It might open light themes for you.
Bold and italics
Don’t use. This goes into the same category as too many colors. It’s just another way to highlight something, and you don’t need too many, because you can’t highlight everything.
In theory, you might try to replace colors with typography. Would that work? I don’t know. I haven’t seen any examples.
Using italics and bold instead of colors
Myth of number-based perfection
Some themes pay too much attention to be scientifically uniform. Like, all colors have the same exact lightness, and hues are distributed evenly on a circle.
This could be nice (to know if you have OCD), but in practice, it doesn’t work as well as it sounds:
The idea of highlighting is to make things stand out. If you make all colors the same lightness and chroma, they will look very similar to each other, and it’ll be hard to tell them apart.
Our eyes are way more sensitive to differences in lightness than in color, and we should use it, not try to negate it.
Let’s design a color theme together
Let’s apply these principles step by step and see where it leads us. We start with the theme from the start of this post:
First, let’s remove highlighting from language keywords and re-introduce base text color:
Next, we remove color from variable usage:
and from function/method invocation:
The thinking is that your code is mostly references to variables and method invocation. If we highlight those, we’ll have to highlight more than 75% of your code.
Notice that we’ve kept variable declarations. These are not as ubiquitous and help you quickly answer a common question: where does thing thing come from?
Next, let’s tone down punctuation:
I prefer to dim it a little bit because it helps names stand out more. Names alone can give you the general idea of what’s going on, and the exact configuration of brackets is rarely equally important.
But you might roll with base color punctuation, too:
Okay, getting close. Let’s highlight comments:
We don’t use red here because you usually need it for squiggly lines and errors.
This is still one color too many, so I unify numbers and strings to both use green:
Finally, let’s rotate colors a bit. We want to respect nesting logic, so function declarations should be brighter (yellow) than variable declarations (blue).
Compare with what we started:
In my opinion, we got a much more workable color theme: it’s easier on the eyes and helps you find stuff faster.
It’s also been ported to many other editors and terminals; the most complete list is probably here. If your editor is not on the list, try searching for it by name—it might be built-in already! I always wondered where these color themes come from, and now I became an author of one (and I still don’t know).
Feel free to use Alabaster as is or build your own theme using the principles outlined in the article—either is fine by me.
As for the principles themselves, they worked out fantastically for me. I’ve never wanted to go back, and just one look at any “traditional” color theme gives me a scare now.
I suspect that the only reason we don’t see more restrained color themes is that people never really thought about it. Well, this is your wake-up call. I hope this will inspire people to use color more deliberately and to change the default way we build and use color themes.
I have a weird relationship with statistics: on one hand, I try not to look at it too often. Maybe once or twice a year. It’s because analytics is not actionable: what difference does it make if a thousand people saw my article or ten thousand?
I mean, sure, you might try to guess people’s tastes and only write about what’s popular, but that will destroy your soul pretty quickly.
On the other hand, I feel nervous when something is not accounted for, recorded, or saved for future reference. I might not need it now, but what if ten years later I change my mind?
Seeing your readers also helps to know you are not writing into the void. So I really don’t need much, something very basic: the number of readers per day/per article, maybe, would be enough.
Final piece of the puzzle: I self-host my web projects, and I use an old-fashioned web server instead of delegating that task to Nginx.
Static sites are popular and for a good reason: they are fast, lightweight, and fulfil their function. I, on the other hand, might have an unfinished gestalt or two: I want to feel the full power of the computer when serving my web pages, to be able to do fun stuff that is beyond static pages. I need that freedom that comes with a full programming language at your disposal. I want to program my own web server (in Clojure, sorry everybody else).
Existing options
All this led me on a quest for a statistics solution that would uniquely fit my needs. Google Analytics was out: bloated, not privacy-friendly, terrible UX, Google is evil, etc.
What is going on?
Some other JS solution might’ve been possible, but still questionable: SaaS? Paid? Will they be around in 10 years? Self-host? Are their cookies GDPR-compliant? How to count RSS feeds?
Nginx has access logs, so I tried server-side statistics that feed off those (namely, Goatcounter). Easy to set up, but then I needed to create domains for them, manage accounts, monitor the process, and it wasn’t even performant enough on my server/request volume!
My solution
So I ended up building my own. You are welcome to join, if your constraints are similar to mine. This is how it looks:
It’s pretty basic, but does a few things that were important to me.
Setup
Extremely easy to set up. And I mean it as a feature.
Just add our middleware to your Ring stack and get everything automatically: collecting and reporting.
(def app
(-> routes
...
(ring.middleware.params/wrap-params)
(ring.middleware.cookies/wrap-cookies)
...
(clj-simple-stats.core/wrap-stats))) ;; <-- just add this
It’s zero setup in the best sense: nothing to configure, nothing to monitor, minimal dependency. It starts to work immediately and doesn’t ask anything from you, ever.
See, you already have your web server, why not reuse all the setup you did for it anyway?
Request types
We distinguish between request types. In my case, I am only interested in live people, so I count them separately from RSS feed requests, favicon requests, redirects, wrong URLs, and bots. Bots are particularly active these days. Gotta get that AI training data from somewhere.
RSS feeds are live people in a sense, so extra work was done to count them properly. Same reader requesting feed.xml 100 times in a day will only count as one request.
Hosted RSS readers often report user count in User-Agent, like this:
My personal respect and thank you to everybody on this list. I see you.
Graphs
Visualization is important, and so is choosing the correct graph type. This is wrong:
Continuous line suggests interpolation. It reads like between 1 visit at 5am and 11 visits at 6am there were points with 2, 3, 5, 9 visits in between. Maybe 5.5 visits even! That is not the case.
This is how a semantically correct version of that graph should look:
Some attention was also paid to having reasonable labels on axes. You won’t see something like 117, 234, 10875. We always choose round numbers appropriate to the scale: 100, 200, 500, 1K etc.
Goes without saying that all graphs have the same vertical scale and syncrhonized horizontal scroll.
Insights
We don’t offer much (as I don’t need much), but you can narrow reports down by page, query, referrer, user agent, and any date slice.
Not implemented (yet)
It would be nice to have some insights into “What was this spike caused by?”
Some basic breakdown by country would be nice. I do have IP addresses (for what they are worth), but I need a way to package GeoIP into some reasonable size (under 1 Mb, preferably; some loss of resolution is okay).
Finally, one thing I am really interested in is “Who wrote about me?” I do have referrers, only question is how to separate signal from noise.
Performance. DuckDB is a sport: it compresses data and runs column queries, so storing extra columns per row doesn’t affect query performance. Still, each dashboard hit is a query across the entire database, which at this moment (~3 years of data) sits around 600 MiB. I definitely need to look into building some pre-calculated aggregates.
clojuressh: Unified SSH Support for Clojure and Babashka
A while back I wrote about my plans for Spire saying that the codebase needed to be broken into smaller, more focused libraries. The first of those was bbssh. The second is clojuressh. It is a Clojure library wrapping JSch, extracted and cleaned up from the SSH support that&aposs been inside Spire for years, and made API compatible with bbssh.
clojuressh gives you SSH from Clojure without requiring a local ssh binary. It covers:
Custom user-info callbacks for interactive prompts
Integration with babashka.process
API Compatibility with bbssh
clojuressh was designed to share its API with bbssh. The namespaces and function signatures are identical across both. The version numbers match for compatibility. So clojuressh 0.7.0 is the first release and matches the API of bbssh 0.7.0. This version sync will continue with dual releases going forwards.
Using it in Babashka Instead of bbssh
One interesting facet of clojuressh is that it can be used directly as a dependency inside babashka. In this case it acts as a shim to the bbssh pod. This means you can add it as a dep in your deps.edn and use it the same way in both Clojure and Babashka. There is no need for reader conditionals or pod-loading boilerplate.
;; deps.edn - works for both `clj` and `bb`
{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.12.5"}
io.epiccastle/clojuressh {:mvn/version "0.7.0"}}}
(ns myapp.core
(:require [clojuressh.core :as ssh]
[clojuressh.session :as session])
[clojuressh.terminal :as terminal]))
(defn -main []
(terminal/get-width) ;; print JNA warning early on JDK 22+
(let [s (ssh/ssh "my-host")]
(-> (ssh/exec s "uname -a" {:out :string})
deref
:out
println)
(session/disconnect s)))
Run with Clojure:
clj -M -m myapp.core
Run with babashka:
bb --config deps.edn -m myapp.core
Differences from clj-ssh
If you are just writing code for clojure and don&apost care about Babashka why not just use clj-ssh? Here&aposs a few things clojuressh does differently:
SCP. I originally used clj-ssh in Spire but ran into limitations with its SCP implementation. The SCP code in clojuressh comes from Spire where it has had a lot of real-world use. clj-ssh fails to preserve timestamps or file permissions on files that are copied. clojuressh also supports a progress-fn that can be passed a callback and used to monitor the progress of the copy making it easy to implement things like user feedback.
Default user interactions. clojuressh integrates with your existing keychain. In the case of presented keys that need decrypting or passwords that need entering, clojuressh falls back to behaviour just like ssh, prompting the user. The same is true for unknown host keys, asking the user if you want to trust the unknown host key and then adding it to known hosts.
SSH config file parsing.clojuressh.config-repository can read ~/.ssh/config, picking up your host aliases, port overrides, and identity file settings.
Terminal management. The clojuressh.terminal namespace handles raw mode, terminal size, and readline which can be useful if you&aposre building anything interactive.
Inter-operation with babashka.process.clojuressh.core/exec supports process threading with -> to and from babashka.process processes. This means you can stream stdout from a local shell command to a remote command and back to a local process again with a single ->:
Note on JDK 22+:clojuressh uses JNA for terminal handling. On JDK 22 and newer the JVM will print a warning the first time a native library loads. This isn&apost an error. The README covers how to silence it with --enable-native-access=ALL-UNNAMED or to print it early. It&aposs confusing because it usually pops up just after clojuressh prompts for the user password.
Today fellow Clojurian Søren Knudsen asked the following question on Clojurians Slack:
Say I&aposd like an overview of which fns in my Clojurescript app don&apost have :x metadata and aren&apost children of functions that have :x. I&aposd love this overview as data.
Anyone know a relevant analysis tool for this purpose?
Let&aposs represent this problem in code form. Read it from bottom to top.
(defn grandchild [] ; no :x, but reachable via child: ignore
:leaf)
(defn child [] ; no :x, called by ^:x grandparent: ignore
(grandchild))
(defn ^:x grandparent [] ;; has :x metadata, ignore
(child))
(defn standalone [] ;; has no :x metadata and not reachable from anything with :x metadata, include
:other)
It turns out that clj-kondo analysis
data is well suited to solve this problem. In this blog post, let&aposs write a babashka script, that uses the clj-kondo pod. This bit of setup lets you do that. Of course you could also use clj-kondo as a regular JVM dependency, but we&aposre going for ease here, since it&aposs just a tiny script at this point.
Clj-kondo lets you find var-definitions and var-usages. Clj-kondo can also include var metadata. The arguments to clj-kondo&aposs run! API function then should look like this:
To illustrate how it works, we&aposll introduce a multi-namespace project:
;; src/app/core.cljs
(ns app.core
(:require [app.util :as util]))
(defn ^:x grandparent []
(util/child))
(defn standalone []
:other)
;; a top level var usage, not inside any var definition:
(util/child)
The :from key describes from which namespace the reference was used. The :from-var key describes in which var definition the var was used, and this is the key ingredient of tracking transitive var usages. The :to + :name keys describe which var was used.
Vars
In clj-kondo&aposs analysis you can request metadata from vars with :meta [:x] (or all metadata with true). To distinguish all project vars from those that have :x metadata we can do the following:
Here project-vars is a set of symbols of all the project vars and with-x are only those that have :x metadata.
Building the call graph
Now we&aposre ready to build the call graph that lets us solve our problem. In the following we&aposre making a map that looks like: caller -> callees, but we limit callees only to project vars since we&aposre not interested in vars like cljs.core/assoc, reagent.core/atom etc.
(def graph
(reduce (fn [g {:keys [from from-var to name]}]
(let [callee (fq to name)]
(if (and from-var (contains? project-vars callee))
(update g (fq from from-var) (fnil conj #{}) callee)
g)))
{}
(:var-usages analysis)))
The from-var condition leaves out any top level var usages. The (contains? project-vars callee) takes care of filtering only on project vars. After running this, we&aposll end up with a graph (map) that looks like:
So app.core/grandparent calls app.util/child and app.util/child calls app.util/grandchild.
Transitive reachability
Next we write a function to find out what vars are reachable from a set of vars starts.
(defn reachable [starts]
(loop [seen #{}
todo (set starts)]
(if (empty? todo)
seen
(let [seen (into seen todo)
used-vars (set (mapcat graph todo))
unvisited (set/difference used-vars seen)]
(recur seen unvisited)))))
(def children (set/difference (reachable with-x) with-x))
(prn {:graph graph
:with-x with-x
:children-of-x children
:without-x (set/difference project-vars with-x children)})
The reachable function just calculates the transitive closure of the graph, given a set of starting nodes (vars). The children var is the set of reachable vars without the starting points (the vars with :x metadata).
So the answer we were looking for is #{app.core/standalone}. This function is neither a transitive child of any function with :x metadata, nor does it have any :x metadata itself.
I hope you learned how useful clj-kondo analysis data can be for tracking relations between vars and that you can use this data in casual babashka scripts as well!
As I wrote about previously, I've been working
on splitting Biff up into a bunch of separate libraries and changing various
things along the way. I've completed a rough draft of all twelve
libraries and am now
going through them one-by-one to polish and release them. The first library is
now ready.
biff.core: system
composition and other interfaces for Biff projects. This is the glue that holds
all the other libraries together, and that's why I'm releasing it first.
For a long time Biff has had this "modules and components" structure where each
application namespace in your project exposes a "module" map, then you have a
bunch of boilerplate to combine stuff from those modules into a single "system"
map, and then we thread the system map through your "component" functions on
startup. Biff 2 retains that structure, and it has some additional stuff to deal
with that boilerplate.
For an example of what I'm talking about, see this
code
which takes the :routes (and :api-routes) keys from your modules and turns
them into a :biff/handler value for the system map. I wanted a first-class way
to be able to extract that kind of logic cleanly into a library so that the
library's instructions can just be "add this module to your project" without an
accompanying "and then paste all this stuff into your main namespace."
So this new biff.core library includes a concept of "init functions." These are
functions that take a collection of modules and return a single map that can be
merged into your system map. Ta da. Here's an
example.
Init functions are stored in the :biff.core/init key in your module maps, so
we get that nice "all you need are modules (well, and components)" effect.
The main complication here is that the boilerplate of defining a (def handler ...) var in your application code actually has a nice side benefit: late
binding. If you change any of your modules, the handler var will get updated,
and if you set :biff/handler in your system map to the var instead of the
value (#’handler), incoming Ring requests get the latest handler without you
having to restart the web server. If we extract that boilerplate into library
code, we don't get the var.
I ended up on this solution:
Init functions take a var of your modules vector, not the vector value
itself.
Anything in the system map that you want to get updated without a restart
needs to be a function. In some cases this means instead of setting a
:com.example/my-thing key on the system map, you need to set a
:com.example/get-my-thing function which returns my-thing.
That function on the system map should dereference the modules var and pass it
to a memoized function that builds whatever thing it is you need (like the
Ring handler).
Again, see this
example.
The result is kind of aesthetically pleasing: you get a nice clean main
namespace
that shouldn't need to change much, and all you do is add
modules
and
components.
There's always the temptation to consolidate things further. Why even have a
separate components vector? Why not have modules support :biff.core/on-start
and :biff.core/on-stop keys and then have some way to express dependencies
between these lifecycle functions so we can call them in the right order?
And the answer is so that we don't have to have some way to express dependencies
between these lifecycle functions so we can call them in the right order. It's
not that hard to put the components in the right order yourself (especially
since the Biff starter project does that for you), and then it's easier to
understand how components work. It's just a sequence of functions that you pass
a map through. If you work on a project with so many stateful resources that
it's hard to keep track of them all, you can always layer something on top that
figures out what your components vector should be before you pass it to
biff.core.
Plug: my team is
hiring
for a senior software engineer, writing ClojureScript and Python mostly. We make modeling software
for renewable energy projects.
Squint added a
browser REPL.
Go beyond hot-reloading your Squint code. Now you can eval on the live page
over nREPL. Try it out!
Jolt takes
SCI to new places: the Janet runtime. If you
like Janet’s lightweight runtime, but you want a full Clojure dialect, you
might be interested in trying the early version.
Do you want to run Clojure in outer space? Why not fly it there yourself? Check
out sfsim. It aims to be a realistic 3D
space flight similator. See if you can take off, orbit the planet, make it home
safe, and live to code another day.
Who needs Twitch when you can just
stream your terminal to the web?
Try Atomstream. To use it, swap
out charm.clj, launch your application
in the terminal, and watch all your TUI goodness dance before your eyes in the
browser.
EDN and
Transit in C? Was that on your bingo card
for 2026? Perhaps not, but if you’re in the market for a native,
high-performance, dependency-free implementation, you may want to take a look.
transit.c - A data interchange format and set of libraries for conveying values between applications written in different programming languages.
kontor - A trans-national accounting kernel with modules for all business aspects.
parens-to-production - How to build your next Clojure application with SSR with Datomic
jon-nested-menu - Nested MUI menus for Reagent/ClojureScript and React: a dropdown, a right-click context menu, per-item icons, custom labels and keyboard navigation.
biff.core - Defines the interfaces and code that connect all the other Biff libs
spel0.9.8 - Idiomatic Clojure wrapper for Playwright. Browser automation, API testing, Allure reporting, and native CLI - for Chromium, Firefox, and WebKit
ansatz0.1.22 - Dependently typed Clojure DSL with a Lean4 compatible kernel.
guardrails1.3.3 - Efficient, hassle-free function call validation with a concise inline syntax for clojure.spec and Malli
fulcro-tui1.0.2 - A JLine-base TUI wrapper, allowing you to write TUI projects in CLJ or Babashka via Fulcro
fulcro-rad-tui 1.0.0-RC2 - UI Plugin for Terminal UI support in Fulcro RAD (fulcro-rad-statecharts)
calva2.0.590 - Clojure & ClojureScript Interactive Programming for VS Code
clj-tg-bot-api 1.2.266 - 🤖 The latest Telegram Bot API spec and client lib for Clojure-based apps
aleph0.9.9 - Asynchronous streaming communication for Clojure - web server, web client, and raw TCP/UDP
rephrase1.0.3 - Rephrase exceptions to be more beginner-friendly
c4k-keycloak 2.1.0 - a kubernetes deployment for keycloak
phel-lang0.42.0 - A functional, Lisp-inspired language that compiles to PHP. Inspired by Clojure, Phel brings macros, persistent data structures, and expressive functional idioms to the PHP ecosystem.
awesome-backseat-driver1.0.15 - Plugin marketplace for Clojure AI context in GitHub Copilot: agents, skills, and workflows for REPL-first interactive programming with Calva Backseat Driver
diehard0.12.1 - Clojure resilience library for flexible retry, circuit breaker and rate limiter
calva-backseat-driver0.0.35 - VS Code AI Agent Interactive Programming. Tools for CoPIlot and other assistants. Can also be used as an MCP server.
teensyp0.6.0 - A small, zero-dependency Clojure TCP server that uses Java NIO
Terraform is excellent at standing infrastructure up, but Day 2 work often begins after apply: DNS updates, cache purges, configuration, notifications, and handoffs to other tools. BigConfig packages keep Terraform as a powerful implementation detail while exposing code-first, local-first, and agent-first lifecycle verbs. The BigConfig SDK supports TypeScript, Python, and Clojure.
This tutorial shows how to install and try the three Claude Code skills that scaffold minimal, launcher-conformant BigConfig packages in TypeScript, Python, and Clojure. Each starter package includes validate and build verbs, a root run file, packaged resources, tests, and the shape needed to run through bc-pkg.
I slowed down. I don’t know why — maybe I have a lot of work to do, maybe my brain browned out. Maybe it’s because I am leading a team of quants in India, and we are expanding our business. So maybe that slowed me down. Another reason is Rust is proving to be a bit difficult for me. I was reading and I have read till enums, and I couldn’t grasp the concepts as quickly as I thought I would.
I really miss this book, PHP: The Complete Reference. When I read it, I picked up PHP in a jiffy.
Everything had complete examples. Whatever code that was newly added was highlighted and called out, and it was really easy to follow. This was before LLMs, and I think even before Stack Overflow became really popular.
Compared to PHP: The Complete Reference, the Rust book is no match. The Rust community doesn’t want beginners to learn Rust — it wants advanced programmers to learn Rust, which could even be fatal for the language in the long run.
So I am looking for new sources apart from the Rust book, and I have zeroed in on a freeCodeCamp video.
Maybe this video has a practical demonstration of how to code with Rust, and maybe, by watching the video and typing along side by side, I can learn Rust quicker.
So that’s about it for this week. Let me report back next week on what I’m up to. I also miss Clojure. Compared to Clojure, Rust is super difficult — you don’t have a REPL and all those things. I just really miss my Clojure projects, and I want to contribute to them because I love Clojure. Let’s see how things go, and let’s see if I forge ahead or give up. I don’t think I’ll give up before I write a Lisp interpreter in Rust. I really want to learn to write a compiler.