Spike: what Seaside halos are, how they map onto stube, the invasive points, and an incremental build-up sequence with cost estimates.
In Pharo Seaside, a halo is a small overlay drawn around every rendered
WAComponent when "halos" is toggled on the dev toolbar. Each halo
carries a fixed set of icon buttons plus a class-name label. The
canonical icon set:
| icon | action | maps to stube? |
|---|---|---|
| Inspect | open Smalltalk inspector on the component instance | yes — show the instance map |
| Browse | open class browser on the component's class | partial — open the defcomponent form via file/line metadata |
| Halos | toggle halos on nested children (per-frame, not just global) | yes — UI toggle on the overlay |
| Configure | open the component's #configuration (Seaside configuration objects) | no — no analog; stube has no config object |
| Render source (XHTML) | show the literal rendered HTML for that component | yes — and trivial |
| Add/remove decoration | wrap/unwrap a Seaside decoration | no — stube composes via embed/call, not decorations |
| Class name strip | shows class, doubles as drag handle | yes — show :instance/type + iid |
Then there's the dev toolbar at the page bottom, which is separate from per-component halos but ships together. It carries:
The Memory tool is the big one — it lets you click a point in session
history and the page snaps back. Stube already has :conv/history; this
is essentially exposing what's already in the kernel.
You're well set up:
:instance/id, and renders already use it
as the outer DOM id (convention). That's the anchor the overlay needs.:conv/instances + :conv/stack + :instance/children already model
the full tree.:conv/history already records snapshots — Memory tool is one
endpoint away.todo.md §3 calls out).render-frame is the natural choke point to decorate output with halo
metadata.(s/inspect cid) REPL helper already exists per README.:init/:render/:handle
maps; there's no parallel object to mutate.embed/call/call-in-slot; "wrap with X" isn't a thing.Each tier ships something usable on its own. Gate the whole thing on
(s/start! {:halos? true}) — never on in prod, never on by default.
What already exists (s/inspect) plus:
(s/tree cid) — pretty tree of stack + slots, with :instance/type
and iid.(s/instance cid iid) — returns the instance map (EDN-clean check
baked in).(s/history cid) — list snapshots with timestamps.(s/where cid iid) — (meta (registry/lookup! type)) → :file/:line
of the defcomponent.Cheap, useful regardless of whether halos ship.
?halos=1 on the shell URL flips :conv/halos? true. Also: keyboard
chord ? once halos JS is loaded, to toggle.data-stube-iid and data-stube-type to every instance's root
element during render. Cleanest spot: a tiny helper in render-frame
that walks the top-level hiccup of an instance and merges the attrs.
Risk: today there's no kernel-side guarantee the user's hiccup has
its outer attr map at index 1 — examples follow the convention but
it's not enforced. Worth tightening anyway./stube/halos.js (one file, no build step). On load:
query [data-stube-iid], draw a 1px dashed outline + a small tag in
the corner showing type + iid. That's it for T1.You stop here if you want — the label + outline alone is already a real debug improvement.
/stube/halos/<cid>/panel returning hiccup;
rendered as an iframe or a fixed-position div in the shell.:conv/instances + stack. Click
an iid → fills Instance + HTML tabs.clojure.pprint or
puget.:elements per iid in the conv — ~30 LOC in run-effects).(server/swap-conv! cid (fn [_] (nth (:conv/history c) n))) then
re-emit a frame.todo.md §3 describes. Either disable Memory for those convs or
label "in-memory only" in the panel.{:t ts :iid … :event … :payload …} appended in dispatch!.editor:// link built from
:file/:line (Emacsclient, vscode://, idea://). User configures
their preferred handler. Trivial once T0's where exists.Three pinch points worth flagging now:
Instance-root attribute injection. Adding data-stube-* to every
instance's outer element is the foundation for everything else.
Today render functions return arbitrary hiccup; the kernel doesn't
enforce that index-1 is an attr map. T1 needs either a normaliser in
render-frame (small but the kernel is already over budget per
todo.md §1) or an explicit (s/root-attrs self) helper users must
include. Prefer the normaliser, and extract it to a
dev.zeko.stube.halos namespace so the kernel-LOC budget doesn't
regress further.
A second conversation for the panel. Trying to mount halo UI
inside the app conv creates ordering problems (halo dispatches
snapshotting into the app's :conv/history, halo instances mixing
into :conv/stack). A sibling conv keyed by the app cid is cleaner;
costs an extra register-control-conversation! on the server side.
Production gate. :halos? must be opt-in at start! time, the
static halos.js must 404 when off, and the server option should be
checked at every endpoint, not just the shell. Belt and suspenders,
because halos expose full session state.
Not hard. T0 + T1 is ~1.5 days and gets you 70% of the practical value
(labels, tree, REPL inspect). T2 gets you the inspector pane — which is
the thing you'll actually click. T3 (Memory) is the most Seaside-feeling
piece and the one cloroutine persistence (todo.md §3) gates. T4 – T5
are niceties.
If sequencing this on top of todo.md, slot T0 – T2 as a new
§9 Developer tooling section, done before the kernel-shrink work
in §1, because T1's normaliser is going to be one of the things
extracted from the kernel anyway, and it's cleaner to extract it once
with a known consumer than twice.
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 |