Disclaimer — this project is AI-generated.
A Clojure dev-time UI inspired by Code Bubbles: open vars as draggable bubbles on a shared canvas, click cross-references to fan out the call graph, auto-arrange when it gets busy.

Leiningen (project.clj)
:profiles {:dev {:dependencies [[io.github.ertugrulcetin/code-bubble "0.2.1"]]}}
tools.deps (deps.edn)
{:aliases
{:bubbles
{:extra-deps {io.github.ertugrulcetin/code-bubble
{:mvn/version "0.2.1"}}}}}
clj -A:bubbles -M -r to enter a REPL with code-bubble available.
(require '[code-bubble.core :as bubbles])
(bubbles/show!) ; open the canvas
(bubbles/show! 'clojure.string/blank?) ; + add a bubble
Hover any identifier in a bubble — the cursor switches to HAND on navigable references; click to open them.
Drop a Maven coord onto the classpath and into the namespace browser in one call. Two shapes:
;; Already at the REPL:
(bubbles/load-lib! '[dev.weavejester/medley "1.10.0"])
;; Or as part of opening the window:
(bubbles/show-with-libs! '[[dev.weavejester/medley "1.10.0"]
[org.clj-commons/humanize "1.1"]])
This (a) resolves the lib via tools.deps, (b) eagerly requires every namespace it ships, (c) refreshes the browser so they appear in the left column.
If the lib is already on the classpath (e.g. you put it in -Sdeps),
this works in any context — no interactive REPL needed. If it's not yet
loaded, load-lib! falls back to clojure.repl.deps/add-libs which
requires Clojure 1.12+ and an interactive REPL.
A complete shell one-liner:
clj -Sdeps '{:deps {io.github.ertugrulcetin/code-bubble {:mvn/version "0.2.1"}
dev.weavejester/medley {:mvn/version "1.10.0"}}}' \
-M -e '(require (quote [code-bubble.core :as b])) (b/show-with-libs! (quote [[dev.weavejester/medley "1.10.0"]]))'
| Gesture | Effect |
|---|---|
| Click-drag (within 50 ms) | Pan canvas |
| Click-and-hold then drag on a bubble | Move bubble / select text / resize |
| Click empty canvas | Drop focus from any bubble |
| Wheel over empty canvas / unfocused bubble | Pan |
| Wheel after clicking into a bubble's body | Scroll that bubble |
| Shift + wheel | Pan horizontally |
| Two-finger pinch (macOS) | Zoom (cursor-anchored) |
Cursor is the only affordance for navigable refs — no underline. Pinch
needs Apple's com.apple.eawt.event classes (most modern macOS JDKs ship
them).
| Shortcut | Action |
|---|---|
| ⌘K | Open Function… |
| ⌘T | New tab |
| ⌘W | Hide window |
| ⌘⇧K | Clear active workspace |
| ⌘⇧A | Auto-arrange (Sugiyama tree layout) |
| ⌘L | Toggle connectors |
| ⌘J | Toggle browser |
| ⌘= ⌘- ⌘0 | Zoom in / out / reset |
| ⌘, | Preferences… |
⌘ on macOS, Ctrl elsewhere.
Each tab is an independent canvas — its own bubbles, edges, zoom, focus. Double-click a tab title to rename. Closing the last tab spawns a fresh one.
Left sidebar (⌘J to toggle). Two filterable lists side-by-side: every
loaded namespace, then every var in the selected namespace. Filters do
fuzzy subsequence matching with score-based ranking. Double-click a var
to open it as a bubble in the active workspace.
Click a navigable identifier: a connector arrow is drawn from the line containing the click (highlighted in soft amber) straight to the new child bubble. The new bubble is positioned at the same y as that row — arrows stay short and roughly horizontal.
code-bubble.core/…:
| fn | what |
|---|---|
show! [] / [sym] | open window, optionally with a bubble |
show-with-libs! [coords] | open window after loading libs |
load-lib! [coord] / [lib version] | load a lib + refresh browser |
close! [sym] / clear! / dispose! | … |
align! toggle-connectors! | … |
zoom-in! zoom-out! reset-zoom! | … |
toggle-browser! show-preferences! | … |
set-font! [family size] | apply font globally |
new-tab! [] / [title] / close-tab! | tabs |
open? | bool |
add-libs (used by load-lib! when the jar isn't on the classpath) is
Clojure 1.12+ and REPL-only. Preloading via -Sdeps sidesteps both.MIT.
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 |