Dependencies
Every dependency here is a pinned git SHA, and every pin has a reason. The two rules worth carrying into any other jolt project:
- A transitive coordinate's pin must match what its dependent itself pins. Jolt's dependency walk is breadth-first, so a top-level coordinate silently overrides the transitive one. Pinning glitter to a newer SHA than glitter-uikit was built against would quietly override what glitter-uikit was actually tested with. This also means the coordinate name, not just the SHA, has to match: when glitter-uikit moved its own glitter pin from the
burincorg tojlt-commons(GitHub redirects the old URLs, so nothing broke outright), this project's ownio.github.burinc/glitterhad to be renamed too, orjolt -Streewould have resolved two separate copies side by side instead of one shared one. - A pin's own
:jolt/nativedeclarations become this project's problem too, whether or not this project's code ever calls into them. This bit for real: see "The GTK4 requirement that shouldn't have existed" below for the fix, once this project actually hit it.
org.clojure/spec.alpha needs an explicit declaration here that it wouldn't need on the JVM. Jolt is Clojure, so org.clojure/clojure is a terminal dependency under jolt: it contributes neither an artifact nor children, which means it never drags in spec.alpha transitively the way the JVM artifact does. Declared explicitly, it behaves like any ordinary pure-Clojure dependency; jolt's dependency resolver handles Maven coordinates right alongside git ones.
The jolt 0.8.0 upgrade: the write-order break
jolt 0.8.0 made jolt.ffi name-for-name compatible with babashka.ffi, and two of its changes break existing callers silently. A fixed array in a layout became [:array type count] instead of [:array count type], which raises at compile time, so it's self-announcing. ffi/write started taking its value before the offset, (write p t v offset) where it used to be (write p t offset v), and that one can't announce itself: an offset and a value are both integers, so an old call site on the new runtime writes the right kind of value to the wrong place and says nothing.
This project's own FFI surface, three defcfn bindings plus ffi/null (see AppKit integration), had nothing to change. Its dependencies did. Auditing every pinned git dependency under ~/.jolt/gitlibs against jolt 0.8.0 with the old pins in place found real damage:
- glitter-uikit, at its old pin, builds a
CFRunLoopSourceContextas nine zeroed offsets and aperformfunction pointer at offset 72, the old argument order. Under 0.8.0, that call reads as "write 72 at offset perform," a function pointer interpreted as a byte offset, and requiringdemo.coredied at load with an invalid memory reference. The demo never started at all. - jolt-lang/http-client, at its old pin, fills
addrinfohints,pollfd,timeval, andz_streamstructs by offset in the same old order. The hints struct'sai_socktypefield landed in the wrong bytes,getaddrinfofailed, and Live Currency's fetch threw before a socket ever opened. It would have said "Rate fetch failed" forever, with nothing in the error to point at the real cause. - glitter was unaffected: its own FFI surface is GTK, never loaded by this demo since
glitter.coredoesn't require it, and it carries no raw writes of its own regardless. - jolt-crypto, pulled in transitively through http-client, had two old-order writes in its DER encode/decode path, but http-client only takes
SecureRandomfrom it, a path this demo never exercises. Upstream had already fixed it the same day; http-client's own pin at the time simply hadn't caught up yet.
Upstream had fixed both real breakages before this project even hit them, one commit per repo, titled the same way in both: "Move ffi/write's value argument before the offset." So the fix here was two pin bumps, not a fork: glitter-uikit to the commit carrying that fix plus its own CI and docs updates, and http-client to the commit carrying the fix and nothing else. Both dependencies now also declare :jolt/min-version "0.8.0", and so does this project's own deps.edn: the old and new ffi/write argument orders can't be told apart at runtime, so a jolt older than 0.8.0 has to refuse loading outright rather than silently write to the wrong memory. The floor only bites from 0.8.0 onward, since an older runtime simply ignores a :jolt/min-version key it doesn't recognize, so it can't guard against this exact break. What actually guards this one is the pins themselves.
Verification after the bump ran the whole path that the old argument order corrupted: a headless load printing all four example ids, a real (http/get ...) against Frankfurter answering 200 with a rate, and a scripted app/run mounting the hub, draining a thunk through the CFRunLoopSourceContext scheduler, opening Live Currency, and watching its status move from :loading to :ok with a real rate in roughly 350 milliseconds, three times over as the currency selection changed.
Two more pins, found the same way
Later jolt releases kept tightening what the compiler will accept, and two more dependencies needed bumping for reasons this project's own code never touched directly.
jolt-lang/time supplies the java.time classes that clojure/data.json needs for its date writers (RFC 0008 moves those classes out of jolt core). A dependency has to declare which classes it provides, per RFC 0014, and this project's pin predated the commit that added that declaration. jolt could see the dependency was present but not what it provided, and refused to load DateTimeFormatter with the same "no dependency provides" error as if jolt-lang/time weren't declared at all. The fix was a pin bump to the commit that adds the :jolt/provides map.
jolt-lang/http-client's connect-retry loop had a recur inside a try whose enclosing loop sat outside that try, a shape Clojure has always refused (a recur can't cross a try boundary) but which an older jolt compiled anyway, into a loop that silently rebound nothing. jolt refuses it too as of 0.8.2, so the walk stopped building at the old pin. This one was already fixed upstream by the time it surfaced here: the fix moves the retry decision out of the try and issues the recur after it returns, so nothing about the connect logic's actual behavior changes.
The pattern behind both: pin bumps aren't only about picking up new features. On a fast-moving compiler, an old pin can simply stop being buildable, and the fix is almost always already sitting on the dependency's own default branch.
The GTK4 requirement that shouldn't have existed
This project used to pin io.github.jlt-commons/glitter directly, for one reason only: demo.core/demo.registry need glitter.core and glitter.alias. But glitter's own deps.edn declares GTK4/GLib/ GObject/GIO under top-level :jolt/native, and jolt inherits a dependency's declared natives transitively, hard-failing in load-natives! before any namespace loads if one is missing. So this demo — pure AppKit, zero GTK calls anywhere — needed GTK4 installed just to boot. An :aliases-scoped :jolt/native doesn't help either; it's silently ignored (verified live, same finding glitter-uikit's own deps.edn records).
Fixed 2026-09-17 by extracting the toolkit-agnostic two-thirds of glitter (glitter.core, glitter.protocols, glitter.alias, and friends — no :jolt/native anywhere in its own deps.edn) into a standalone glitter-core repo. Proposed and tracked at jlt-commons/meta#1. This project now pins glitter-core directly instead of glitter, and glitter-uikit (a dependency of this project too) did the same — so glitter is no longer anywhere in this project's dependency graph at all. Verified: (find-ns 'glitter.gtk) returns nil under the new pins, and jolt path shows no glitter gitlib path whatsoever.
Three more pin churns followed the same day, each for the reason :jolt/min-version's own header comment predicts — a top-level coordinate overriding a transitive one means everyone's pin has to track everyone else's:
glitter-corehad no commit pushed to its GitHub repo yet at first (a private, brand-new repo), so its pin briefly usedgit@(SSH) instead of this file's usualhttps://form — jolt's git fetch can't do an anonymoushttps://clone of a private repo. Switched back tohttps://onceglitter-corewent public.glitter-corelater raised its own:jolt/min-versionfrom0.7.24to0.8.0(for family-wide consistency, not becauseglitter-coreitself needs it — it has zerojolt.ffiusage).glitter-uikit's ownglitter-corepin needed bumping to pick that up, and this project's pin needed bumping to matchglitter-uikit's new commit in turn.glitter-uikit's history got rewritten (an unrelated commit-message cleanup), which orphaned every SHA on its oldmain— including the one this project had just pinned. Re-pinned to the equivalent commit on the rewritten history.
The lesson isn't really about GTK4. It's that pinning a coordinate this project doesn't use directly (glitter-core, nexus-jolt) still means tracking that coordinate's own upstream churn, because jolt's breadth-first, top-level-wins resolution means whatever THIS project declares is what actually gets built, regardless of what any transitive dependency itself pins.