glitter: Guide
Why this exists
glitter is a Replicant-style GTK4 renderer for Jolt. Its sibling, glimmer, already applies Reagent's model (ratoms, automatic dependency tracking, component-local state) to GTK4; glitter deliberately applies a different model; Replicant's single application- state atom, pure state -> hiccup view function, top-down re-render, and data-driven action-dispatch handlers. This guide covers how that model was adapted to a native, retained-mode, C-ABI toolkit that has no DOM underneath it.
What glitter is
A .clj (Jolt/Chez Scheme host, not JVM) library:
(require '[glitter.app :as app]
'[glitter.core :as core]
'[glitter.gtk :as gtk])
(defonce state (atom {:count 0}))
(defn view [{:keys [count]}]
[:box {:spacing 12}
[:label {:label (str "Count: " count)}]
[:button {:label "+ 1" :on {:click [[:action/inc]]}}]])
(core/set-dispatch!
(fn [_event actions]
(doseq [[kind] actions]
(case kind :action/inc (swap! state update :count inc) nil))))
(app/run (fn [window] (gtk/mount! window view state)))
Every subsequent swap! on state triggers a full re-render of view; glitter.core's reconciler diffs the new hiccup against the previous vdom and issues the minimal set of IRender/IMemory calls needed to bring the live GTK widget tree in sync.
Pages
Orientation
examples.md: the six interactive demos (four of them 7GUIs tasks) and the twenty-six live-GTK smokes: what each one runs, and what it pins.counter.cljis the 20-line version of the whole model and the fastest way in.architecture.md: the reconcile โIRender/IMemoryflow,mount!'s state-atom watcher, why elements are tracking atoms rather than raw GTK pointers.porting-and-attribution.md: the four sourcing buckets (ported from Replicant / forked from glimmer / new to glitter / ported from nexus; seenexus.md), and every documented deviation from the pure Replicant port.
Dispatch
nexus.md:glitter.nexus, a port of nexus's data-driven action/effect/placeholder dispatch engine. It covers:- The four concepts. Effects (the only place a
swap!is allowed), placeholders (resolving event data into action data), actions/expansions (pure functions of state that decide what should happen), and interceptors (thebefore-*/after-*mechanism the engine itself runs on). - The two glitter-specific wiring pieces every consumer registers for itself:
:glitter/valueand:nexus/on-errorโclojure.tools.logging, and why they live in each demo rather than in the ported files. - The two consumer shapes this project ships.
flights.clj's pure-effects-only Flight Booker, versuscrud.clj's andtodo.clj's action-expansion retrofits. - The action log and its
:entries/:chronologyaccumulation tree ((pr-str @log), no viewer yet), plus thet/parse-dateleniency finding that makes Flight Booker's date validation actually work.
- The four concepts. Effects (the only place a
GTK integration
gtk-widget-layer.md: the hiccup-tag โ widget registry and the signal connect/disconnect lifecycle. The long one: a per-widget record of how each of the 43 supported tags was added and what it taught. Grouped by what it covers:- Reconciler traps GTK forced.
insert-before's reorder-vs-insert branch andreplace-child!'s prev-sibling capture;:scrolled's hiddenGtkViewportauto-wrap around a non-GtkScrollablechild; and a use-after-dispose bug inlist-box-reorder-child!/flow-box-reorder-child!, both of which reused a pointer GTK had already disposed while removing the old wrapping row. - Signal shapes that forced
set-event-handlerto generalize. Most widgets reuse the uniform 2-arg-void shape for free. Four did not::switch(3-arg, non-void return),:list-box(a distinct 3-arg-void shape, later reused for free by:expander/:paned'snotify::*),:notebook(4-arg"switch-page": the first signal that must read its own raw argument, because the getter is still stale when it fires), and:scale-button(the first case where the signal name alone can't determine the shape, making dispatch tag-aware). - Container strategies beyond
:box.:center-box's three named slots,:paned's two,:overlay's single queryable main slot plus an unenumerable overlay set,:header-bar/:action-bar's hybrid (one named slot plus an ordered pack-start list), and:grid, whose child placement is driven entirely by the child's own props through the:glitter/structural-propsmechanism. - Findings that changed the design. Namespaced keyword props never reach
IRender/set-attributeat all;signal-valuehad to be keyed by[tag signal]rather than signal name; a ctor/apply audit found four previously-shipped bugs; and, not glitter-specific: bulkGtkEditabletext replacement fires"changed"once or twice depending on the buffer's starting state. - Free wins. The display-only widgets (
:spinner,:progress-bar,:image,:level-bar,:revealer,:picture,:inscription,:search-bar), theGtkEditable-delegate reuses (:password-entry,:search-entry,:editable-label), and:menu-button/:popover: the first popup surface here, and the first hiccup relationship that isn't an ordinaryappend-child!-managed tree child.
- Reconciler traps GTK forced.
app-loop-and-threading.md: theGtkApplicationbootstrap and cross-thread marshalling that lets aswap!from any thread safely reach the GTK main loop.
Verify
testing-and-tasks.md(the unit suite, the headless fake-IRendertest renderer, the twenty-six automated live-GTK smokes, and thejolt/bbtask surfaces that run them.limitations.md) every known v1 gap, and the reasoning behind leaving each one unfixed for now.
See also
- glimmer: the Reagent-style sibling this project forked its GTK4 FFI/widget layer from.
- Replicant: the source of
glitter.core's reconciler and most of the non-GTK-specific namespaces. NOTICE(repo root): the authoritative file-by-file attribution ledger;porting-and-attribution.mdexplains it,NOTICEis the source of truth for exact commit SHAs and per-file deviation notes.