API Reference
KaimonSlate is normally used through the slate app, its UI, and the agent's slate.* tools. The serving layer below is a small public Julia API for embedding the hub in your own scripts — the app is the everyday entry point.
Serving a notebook
KaimonSlate.NotebookServer.serve_notebook Function
serve_notebook(path; host="127.0.0.1", port=8765, quiet=true, app=false, appdefaults=Dict())Open the notebook at path in a hub and serve it. Blocks until stopped (Ctrl-C shuts the hub and its workers down cleanly). Once the hub is answering HTTP, prints a framed banner with the openable notebook URL (so a launcher like run.jl surfaces a ready, clickable link rather than a bare port). With quiet=true (default) the console stays clean after the banner: the hub's log detail (worker spawns, connects, warnings) goes to a file in the same tmp dir as the worker logs — the banner shows the path; only errors still print.
KaimonSlate.NotebookServer.start_server Function
start_server(path; host="127.0.0.1", port=8765, app=false, appdefaults=Dict()) -> HubStart a hub and open the single notebook at path. Non-blocking; returns the Hub (stop it with stop_hub). The notebook is served at /n/<id> (printed); / is the index. For a blocking launcher use serve_notebook.
app=true serves the notebook as an application: the reading view (markdown, output, figures and live @bind controls — no code, no cell chrome) with the authoring API refused server-side. Presentation defaults for visitors go in appdefaults; build it with app_defaults. See server_app.jl for what app mode does and does not guarantee.
KaimonSlate.NotebookServer.stop_server Function
Stop a hub started by start_server (drains SSE, frees the port).
Hub (multiple notebooks)
A Hub is one HTTP server hosting many notebooks.
KaimonSlate.NotebookServer.start_hub Function
start_hub(; host = "127.0.0.1", port = 8765, app = false, appdefaults = Dict()) -> HubStart one HTTP server that hosts many notebooks, and return the Hub. Notebooks are added and removed while it runs with open_notebook! and close_notebook!; stop_hub shuts it down. This is the layer the slate app itself runs on — reach for it when you are embedding Slate in your own script rather than opening a single notebook with serve_notebook.
host defaults to loopback, so the hub is reachable only from this machine; bind "0.0.0.0" to serve a network. There is no authentication at any bind address — whatever can reach the port can drive every notebook on it.
app = true serves in application mode: prose, results, figures and live controls, with the authoring routes refused server-side rather than merely hidden. appdefaults (build it with app_defaults) sets what a visitor sees before choosing for themselves.
KaimonSlate.NotebookServer.open_notebook! Function
open_notebook!(hub, path) -> idLoad the notebook at path into the hub (reusing the existing entry if already open) and start its file watcher. Returns the hub id (its /n/<id> route).
KaimonSlate.NotebookServer.close_notebook! Function
Remove a notebook from the hub: drain its SSE connections and drop it.
KaimonSlate.NotebookServer.stop_hub Function
Stop the hub: drain every notebook's SSE connections, then close the server.
Top-level
KaimonSlate.KaimonSlate Module
KaimonSlateA warm-session, reactive Julia notebook served as a live browser UI — packaged as a Kaimon extension.
Cells evaluate in isolated modules; edits and @bind widgets drive pruned reactive recompute; Makie/MIME figures and interactive ECharts render inline; the source round-trips to a plain .jl file so the agent and the browser share one source of truth.
It runs out-of-process on HTTP 2.0, independent of Kaimon core's HTTP version — the two talk over the Gate (ZMQ), never a shared HTTP stack.
Standalone use:
using KaimonSlate
serve_notebook("notebook.jl"; port = 8765) # blocksAs a Kaimon extension, create_tools exposes slate.open / slate.list / slate.close to the agent, and Kaimon manages the subprocess lifecycle.
KaimonSlate.blob_chunk_mb Method
Configured data-channel chunk size in MB; 0.0 = unset (env / 8 MiB default applies).
KaimonSlate.carry_max_s Method
Configured boot-carry per-entry ceiling in seconds; 0.0 = unset (env / 30s default applies).
KaimonSlate.catalog_config Method
The optional "catalog" object in slate.json — where the Extensions gallery gets its data: {"catalog": {"url": "…/catalog.json", "registry": "SlateRegistry", "registry_url": "…"}}. Edited by hand; installed into the server at init. Point all three elsewhere to run a fork's or a mirror's catalog. Empty ⇒ the built-in curated registry.
KaimonSlate.configured_port Method
Persisted hub port; 0 means unset — the KAIMONSLATE_PORT env var or the 8765 default applies.
KaimonSlate.create_tools Method
create_tools(GateTool) -> Vector{GateTool}Tools exposed to the agent under the slate.* namespace. GateTool is passed in so the extension needs no Kaimon dependency — handlers are plain typed functions, reflected into MCP JSON Schema by Kaimon.
KaimonSlate.ext_prompt_choice Method
The user's onboarding answer for extension registration: "yes" | "dismissed" | "" (not asked).
KaimonSlate.memo_cap_gb Method
Configured durable memo-store cap in GB; 0.0 means unset (worker env / adaptive default applies).
KaimonSlate.on_event Method
on_event(channel, data, session_name)Gate event-bus callback (the extension manifest subscribes to the agent: topic prefix). Kaimon's agent service publishes each agent session's {kind,turn,data} events on agent:<id>; we relay them onto the bound notebook's SSE so the chat pane updates live. Other channels are ignored.
KaimonSlate.on_shutdown Method
on_shutdown()Stop every running notebook server before the extension subprocess exits.
KaimonSlate.parallel_default Method
Whether inter-cell parallel execution is on by default for notebooks (persisted; default true).
KaimonSlate.register_extension Method
register_extension(; auto_start=true, enabled=true, force=false, project_path=pkgdir(KaimonSlate)) -> BoolAdd this package to Kaimon's extension registry (extensions.json in Kaimon's config dir — %APPDATA%\Kaimon on Windows, ~/.config/kaimon elsewhere) so Kaimon loads the slate.* tools automatically — no hand-wiring. Idempotent: returns false (nothing written) if Kaimon isn't installed here or the entry already exists, and true when an entry is added. Registration is consented: the slate app prompts on first run (see app.jl), and loads only self-register when spawned AS the extension (see __init__). Call it explicitly to (re)register a specific project_path or to flip auto_start.
KaimonSlate.remote_config Method
remote_config() -> Dict{String,Any}The optional "remote" object in slate.json — per-host overrides for SSH/connect/tunnel/transfer timing (dial deadlines, ssh ConnectTimeout, tunnel keepalive, blob timeouts, …). Edited by hand; installed into the engine at init. See the _ssh_*/_dial_*/… helpers in remote.jl for every key, its KAIMONSLATE_* env equivalent, and its default. Empty ⇒ every timing keeps its built-in default.
KaimonSlate.repair_registration! Method
repair_registration!() -> BoolRe-point an extension registration that an UPDATE invalidated, returning whether it wrote anything.
A Pkg-installed KaimonSlate lives at <depot>/packages/KaimonSlate/<slug>, and the slug is derived from the version's tree hash — so every upgrade lands in a NEW directory while extensions.json goes on naming the old one. The old slug usually still exists (Pkg keeps it until a gc), so nothing errors: Kaimon simply keeps loading the version that was replaced, and the update appears not to have taken effect.
This is deliberately narrow. It only acts when EVERY entry naming this package is stale — the path is gone, or it is a different slug of the same Pkg install — so a second checkout or a git worktree is never repointed. enabled/auto_start are carried across, because repairing a path must not quietly re-enable an extension the user turned off. A REMOVED entry is not damage but a choice, and is left for the app's prompt.
KaimonSlate.run_location_default Method
The machine's GLOBAL default run-location for new notebooks ("host[,transport]"; "" = local).
KaimonSlate.set_blob_chunk_mb! Method
set_blob_chunk_mb!(mb) -> Float64Persist the memo data-channel chunk size (MB per round-trip) and apply it live — no worker respawn needed (the transfer runs hub-side). Smaller chunks keep a slow uplink responsive; bigger ones amortize the RTT. 0 clears back to the env / 8 MiB default. Settings panel knob.
KaimonSlate.set_carry_max_s! Method
set_carry_max_s!(s) -> Float64Persist the boot-window memo-carry ceiling (max seconds any single entry may spend transferring before the cost gate skips it — the cell recomputes remotely instead) and apply it live. 0 clears back to the env / 30s default. Settings panel knob.
KaimonSlate.set_configured_port! Method
set_configured_port!(port) -> IntPersist the hub port to the Slate config so it survives restarts (read at hub start by both slate and the Kaimon extension subprocess). Does NOT rebind a running hub — the change applies on the next launch or a hub restart. A value ≤ 0 clears the setting (revert to the env var / 8765 default). Precedence at boot: KAIMONSLATE_PORT env > this persisted value > 8765.
KaimonSlate.set_ext_prompt_choice! Method
Persist the onboarding answer (see ext_prompt_choice). Returns the stored choice.
KaimonSlate.set_memo_cap_gb! Method
set_memo_cap_gb!(gb; respawn=true) -> Float64Persist the durable memo-store ceiling (GB) and apply it to future worker spawns; respawn=true recycles running workers so it takes effect immediately. 0 clears the override back to the adaptive default (a quarter of free disk, clamped 2–20 GB). Called by the Kaimon TUI panel.
KaimonSlate.set_parallel_default! Method
set_parallel_default!(on::Bool) -> BoolPersist whether new/re-opened notebooks run cells in parallel by default, and apply it live. The per-notebook Settings toggle still overrides for a specific notebook.
KaimonSlate.set_run_location_default! Method
set_run_location_default!(spec) -> StringPersist the global default run-location (where new notebooks run) to slate.json and apply it live. Per-notebook and per-session overrides still win. Returns the stored spec.
KaimonSlate.set_worker_extra_flags! Method
set_worker_extra_flags!(spec; respawn=true) -> StringPersist extra Julia command-line flags appended to every spawned worker (e.g. "--gcthreads=4,1 --heap-size-hint=4G"), apply it to future worker spawns, and — by default — respawn every running notebook's worker so it takes effect immediately. Returns the stored spec. Called by the Kaimon TUI panel via ctx.eval, same pattern as set_worker_threads!.
KaimonSlate.set_worker_threads! Method
set_worker_threads!(spec; respawn=true) -> StringPersist the worker Julia-thread spec (e.g. "4,1" or "auto"), apply it to future worker spawns, and — by default — respawn every running notebook's worker so it takes effect immediately. Returns the stored spec. Called by the Kaimon TUI panel via ctx.eval.
KaimonSlate.set_xfer_confirm_s! Method
Persist the transfer-preview threshold and apply it live. -1 clears to default; 0 disables.
KaimonSlate.worker_extra_flags Method
Current extra Julia flags appended to every spawned worker (e.g. "–gcthreads=4,1"); "" means none.
KaimonSlate.worker_threads Method
Current worker Julia-thread spec ("<compute>,<interactive>"); "" means the adaptive default (min(cores,8),2).
KaimonSlate.xfer_confirm_s Method
Configured transfer-preview threshold (s); -1 = unset (env / 60s default), 0 = previews off.
Extension SDK
SlateExtensionsBase is the separate, dependency-light package an extension builds against — see Writing an Extension. It is public API: unlike the submodules below, these are the names a third-party package is meant to use.
SlateExtensionsBase.SlateExtensionsBase Module
SlateExtensionsBaseA lean (Base + stdlib only) SDK for extending Kaimon Slate from an external package — custom @bind widgets, front-end output, browser↔Julia glue, and the per-cell execution context — without depending on the (heavy) KaimonSlate server.
This is the counterpart to AbstractPlutoDingetjes: KaimonSlate depends on it and provides the "meat" (the running server, concrete widgets, the injected notebook namespace), while an extension package depends only on this to build against the contract. Because a @bind spec is already reduced to (kind, params, default) on the wire, this interface is all a widget needs — the Widget struct itself never crosses a process boundary.
Extension points
Controls —
Widget,Choice,Selection; define your own type and overloadto_widgetfor a typed@bindcontrol, andregister_kind!for its value lifecycle.Output —
WebPageandregister_widget_jsship HTML/CSS/JS to the page (live and in exports).Execution context —
slate_contextand its accessors (slate_region,slate_emit,slate_effect, …) read Slate's per-cell context.
Front-end contract (JS globals; no Julia dependency)
Pair a control with window.slateRegisterWidget("<kind>", {wire, sync, destroy}). Other globals the page exposes: window.slateRegisterEditorExtension, window.slateRegisterCellAction, window.slateRegisterCommand, window.slateCall / window.slateOnStream, and Slate.runFragment / Slate.asset.
Catalog listing
An extension registered in a Slate extension registry is listed in the notebook's Extensions gallery with no work at all — name, version, repo and README blurb are harvested automatically. To enrich the listing (tagline, categories, icon, screenshots, a starter snippet), add an optional SlateExtension.toml at your package root; every key in it is optional.
SlateExtensionsBase.DTYPES Constant
DTYPESOne row per element type Slate can put on a wire as raw bytes, and the only place the set is written down. Everything else is derived from it: the streaming frame's numeric tag (_bin_dtype), the asset manifest's string tag (dtype_tag, which capture.jl's _asset_dtype delegates to), and the browser's decoders (dtype_js, injected as window.__SLATE_DTYPES and read by core.js and wscall.js). Supporting another element type is a row here and nothing else.
Each row is a NamedTuple of (T, code, tag, js):
codeis a WIRE CONTRACT: it is the frame's dtype byte, so rows may be APPENDED but never reordered, renumbered or removed.tagis the asset manifest's spelling (it crosses as JSON, so it is a string rather than a byte).jsnames the TypedArray that reads the bytes back.
SlateExtensionsBase.REPLAY_DOMAIN_CAP Constant
Ceiling on a COMBINATORIAL domain — one whose size grows faster than the control's own step count. A slider's domain is as big as the reader made it and that is the reader's business; a range slider's is quadratic in its steps and a multi-select's is exponential in its options, so the same innocuous control can ask for a domain nothing should enumerate. Past this, bind_domain reports nothing and the author gets @replay's "not finite" error while writing the cell, rather than an export that grinds. The cap is generous: 20 000 positions is a 200-stop range slider, or 14 checkboxes.
SlateExtensionsBase.CellAction Type
CellAction(id; icon, title="", show="", onclick)The wire spec for a per-cell toolbar button. id is a stable, namespaced identifier (both the dedup key and the DOM class — let auto_cell_action derive it from your type via kind_for); icon is the glyph shown (an emoji or HTML entity, like the built-in ▶/🗑 buttons); title is the hover tooltip. show and onclick are raw JavaScript the extension owns (the same trust boundary as shipping a front-end asset):
show— a boolean expression overcell(the cell's JSON:cell.kind,cell.tags, …);""⇒ always shown. e.g."cell.kind === 'code'".onclick— statement(s) run on click, withcellId,cellandeventin scope. e.g."window.myExtInsert(cellId)"(a front-end helper your extension shipped).
Build one directly, or return one from to_cell_action / auto_cell_action.
SlateExtensionsBase.Choice Type
Choice(value, label, index = 0)A labeled option's bound value: .value/.v is the real value, .label/.l the display text, .index/.i its 1-based position. Compares, hashes, prints and converts as its value.
SlateExtensionsBase.PaletteCommand Type
PaletteCommand(id; label, tag="", key="", run)The wire spec for one ⌘K command-palette entry. id is a stable, namespaced identifier and the dedup key (let auto_palette_command derive it from your type via kind_for). label is the searchable text shown in the list. tag is an optional short badge on the right (the built-ins use panel, export, recipe, …); it defaults to the package name, so a user can type your package's name to see everything it contributes. key is a display-only shortcut hint — registering a command does not bind a key.
run is raw JavaScript the extension owns (the same trust boundary as shipping a front-end asset): statement(s) executed when the command is chosen, with selectedId (the currently selected cell id, or '') in scope. e.g. "window.myExtPanel()" — a helper your extension shipped.
Build one directly, or return one from to_palette_command / auto_palette_command.
SlateExtensionsBase.ReplayArray Type
ReplayArray{T,N}An array that also records where it came from: data computed for every value of a control, plus which value produced the slice being held.
Any rank travels — a Vector per control value (one series), a Matrix (a heatmap, a surface, an image), or higher. The slices are stacked along a new trailing dimension, so in column-major order the slice for one control value is a contiguous run and a page can take it as a view rather than a gather.
It behaves exactly like the array it wraps — indexing, broadcasting and serialization are the underlying data — so ordinary Julia code, and a plotting package's trace constructors, need no awareness of it. A renderer looks for it while walking a figure and emits the routing that lets a frozen page re-index the shipped data when the control moves. That is what spares an author from naming a trace or a field: they put the value where it belongs and the walk works out the rest.
LIVE, this holds only the value the control is currently set to — computing the rest would be pure waste, since the kernel can recompute on demand and a hundred-position control would otherwise re-sweep on every edit. It carries an id instead: the export resolves that against the sweep it runs, so the shipped asset is decided where the artifact is, not where the author is typing.
SlateExtensionsBase.Selection Type
Selection(items::Vector{Choice})A multi-selection: an ordered, read-only value => label dict. keys → values, values → labels, sel[v] → label, haskey, iteration yields value => label; indices gives each pick's 1-based position in the original option list.
SlateExtensionsBase.SlateBinary Type
SlateBinary(data, meta = (;))Mark a numeric data array for BINARY streaming through slate_emit — raw little-endian bytes on the wire instead of Serialization+JSON, for high-rate frames. meta (a NamedTuple/Dict of small JSON-safe values) rides alongside as a compact header; the browser handler receives {…meta, d} with d a typed array. Element type must be one of Float32/Float64/Int32/Int16/UInt8.
A dense Array is held by REFERENCE, so the frame carries whatever it holds when it is ENCODED — emit and move on, and reusing one buffer across frames costs nothing. Mutating the array between construction and emit therefore changes what is sent; pass snapshot = true for a copy taken at construction instead. Any other AbstractArray (a view, a range, an adjoint) is copied either way, since its bytes aren't contiguous.
slate_emit("field", SlateBinary(frame; i = idx, t = time())) # frame::Matrix{Float32}SlateExtensionsBase.SlateComponentMIME Type
SlateComponentMIME · SlateHtmlMIMEThe two frozen Slate display MIMEs (IANA vendor tree; the suffix matches the payload format). The descriptor VERSION lives in the payload ({v, …}), not the MIME string — one evolving contract.
application/vnd.kaimonslate.component+json— a JSON component descriptor{v, component, props}; the front-end mounts the registered component by name. The blessed path.application/vnd.kaimonslate.html+html— a self-contained HTML fragment; the clean escape hatch that replaces a hand-rolledBase.show(::MIME"text/html").
SlateExtensionsBase.UploadedFile Type
UploadedFileA file a reader uploaded through a FileUpload control. path is a real path under the notebook's datadir() — so it opens with CSV.read, open, load, anything — while name keeps what the file was called on the reader's machine.
@bind datafile FileUpload(; accept = ".csv", label = "Data")
datafile === nothing && return # nothing has been uploaded yet
df = CSV.read(datafile.path, DataFrame)Interpolates and converts to its path, so read(datafile, String) and "$datafile" do the obvious thing. Fields: name, path, size (bytes), mime, uploaded (unix time).
SlateExtensionsBase.WebPage Type
WebPage(; html = "", css = "", js = "", obscure = false)A self-contained HTML page composed from CSS/HTML/JS strings. Renders to one text/html output — <style> + body + <script> — identical in the live notebook and a static export. Empty sections are omitted. obscure = true base64-packs the JS behind a tiny decode-and-run bootstrap (trivially reversible; the source files on disk stay plain).
Typically the pieces come from tracked files so they stay debuggable and re-run on edit — in a Slate notebook via @asset, or in an extension package via read(joinpath(pkgdir(@__MODULE__), "assets", "app.js"), String).
SlateExtensionsBase.Widget Type
Widget(kind, params, default)A @bind control's spec: its UI kind::String, display params::Dict{String,Any}, and default value. Build one directly for a custom control kind — pair it with a front-end window.slateRegisterWidget("<kind>", …) renderer — or return one from to_widget on your own type.
SlateExtensionsBase.Widget Type
Widget(T::Type, default=""; params...)Build a Widget whose kind is derived from the widget TYPE T (see kind_for) — the namespaced form. Use it in to_widget so the kind matches the one register_component! registers, with no shared string to keep in sync:
SlateExtensionsBase.to_widget(s::Stars) = Widget(Stars, s.default; max = s.max)SlateExtensionsBase.asset_dirs Method
asset_dirs() -> Dict{String,String}Every package-vendored asset directory declared by the loaded packages (pkg => absolute dir) — a copy, so callers can't mutate the registry. See extension_manifest for what Slate pulls.
SlateExtensionsBase.auto_cell_action Method
auto_cell_action(x; exclude = ()) -> CellActionBuild a CellAction by REFLECTING a struct's fields — the ergonomic to_cell_action body, mirroring auto_widget. Fields named icon, title, show and onclick map to the matching wire fields (icon and onclick are required; title and show default to "" when the struct has no such field); the id is kind_for(typeof(x)), so it's namespaced by your package and can't collide with another extension's button. exclude drops named fields.
SlateExtensionsBase.to_cell_action(a::InsertSnippetButton) = auto_cell_action(a) # id = "MyPackage.InsertSnippetButton"SlateExtensionsBase.auto_palette_command Method
auto_palette_command(x; exclude = ()) -> PaletteCommandBuild a PaletteCommand by REFLECTING a struct's fields — the ergonomic to_palette_command body, mirroring auto_cell_action. Fields named label, tag, key and run map to the matching wire fields (label and run are required; tag and key default to "" when the struct has no such field); the id is kind_for(typeof(x)), so it's namespaced by your package and can't collide with another extension's command. exclude drops named fields.
SlateExtensionsBase.auto_widget Method
auto_widget(x; value = :default, exclude = ()) -> WidgetBuild a Widget by REFLECTING a struct's fields into params — the ergonomic to_widget body when a widget's fields are its UI params. The value field (default :default) becomes the Widget's bound value (not a param); nothing-valued fields are skipped (so an unset Union{Nothing,…} option is omitted); exclude drops named fields. The kind is kind_for(typeof(x)).
struct Stars; max::Int; label::Union{Nothing,String}; default::Int; end
SlateExtensionsBase.to_widget(s::Stars) = auto_widget(s) # params = {max[, label]}, value = defaultOpt-in on purpose — Slate never reflects a struct unless you ask, so an arbitrary value isn't silently turned into a control, and you keep full control (write Widget(kind_for(T), (;…), val) by hand, or use exclude, when not every field is a param).
SlateExtensionsBase.bind_domain Method
bind_domain(w::Widget) -> Vector | NothingEvery value w can take, in the order the control presents them, or nothing when the domain is not finite (free text, an unbounded number field, a date — nothing to enumerate).
The values are WIRE values — what the registry stores and the browser sends back — not the wrapped form a cell sees. A TableSelect enumerates row indices, not row NamedTuples; a RangeSlider enumerates [lo, hi] pairs, not (lo = …, hi = …). That keeps the domain JSON-light enough to ship with the page (which is the whole point of having one) and lets the client match a control's state to a column by comparing exactly what the control reports. wrap_value turns a wire value into the one the author's expression is written against, and @replay applies it on the way in.
This is the single source of truth for a control's domain: an author never restates it, so it cannot drift from the control it belongs to.
SlateExtensionsBase.coerce_bind Method
coerce_bind(w::Widget, v)Coerce a raw browser value against w's registered kind (identity for an unregistered kind).
SlateExtensionsBase.coerce_value Method
coerce_value(::Type{T}, v) -> TCoerce a raw browser value v (arriving as a JSON number / string / bool) to a control's VALUE TYPE T — the type of its Widget's default. Slate applies this automatically, with error- fallback to the default, for any control that registers no custom coerce — so a typed widget (a Stars whose default::Int) gets Int-safe values for free, no lifecycle code. Add a method for your own value type to teach Slate how to coerce it:
SlateExtensionsBase.coerce_value(::Type{RGB}, v) = parse(RGB, string(v))The fallback passes an unrecognised type through untouched (so a Dict/NamedTuple-valued widget is unaffected). Built-in scalar coercions: Integer (rounds/parses), AbstractFloat, Bool, String, Symbol.
SlateExtensionsBase.component Method
component(kind; props...) -> Dict
component(kind, props) -> DictBuild the frozen COMPONENT DESCRIPTOR {v: 1, component: kind, props: {…}} — what slate_render returns for a value that should mount a registered front-end component (the SAME {component, props} a bound widget mounts, so returned + bound values render through identical machinery). props must be JSON-safe (Dicts/Vectors/numbers/strings/bools/nothing — the shapes the descriptor writer emits).
SlateExtensionsBase.slate_render(v::MyView) = component(kind_for(MyView); value = v.x, max = v.max)SlateExtensionsBase.dtype_js Method
dtype_js() -> StringThe dtype table as a JavaScript snippet defining window.__SLATE_DTYPES:
byCode— TypedArray constructors indexed by the binary frame's dtype byte (decoding a frame),byTag— the same, keyed by the asset manifest's string tag (decoding an asset),codeByTag— tag → dtype byte, for the browser's uplink encoder.
Served live at /assets/js/dtypes.js and inlined into static exports, so a browser cannot disagree with Julia about what a dtype means.
Constructors are looked up by NAME at run time rather than referenced directly: a row whose TypedArray the browser doesn't implement (Float16Array on an older engine) resolves to null and that one dtype declines to decode, instead of a ReferenceError taking the whole table — and with it every other dtype — down with it.
SlateExtensionsBase.dtype_tag Method
dtype_tag(T) -> String | NothingThe asset manifest's dtype spelling for element type T ("f32", "i32", …), or nothing when T is not one Slate packs as raw bytes — the caller then falls back to JSON. Derived from DTYPES.
SlateExtensionsBase.encode_binary_frame Method
encode_binary_frame(channel, x::SlateBinary) -> Vector{UInt8}Serialize a SlateBinary into the self-describing binary streaming frame (see the layout above). The channel + meta + dtype + shape are the header; the array's raw column-major bytes are the payload.
SlateExtensionsBase.ensure_module_frontend! Method
ensure_module_frontend!(m::Module, slate_on) -> BoolInvoke module m's package-global front-end hook, m.__slate_frontend(slate_on), if it defines one and it hasn't already run for this notebook-namespace generation (see ensure_module_frontends!); return whether the module has such a hook. A module without the method contributes nothing (returns false), so — like required_assets — the method's presence doubles as extension-detection. A throwing hook is isolated (caught) so one bad package can't break the manifest pull for the rest.
SlateExtensionsBase.ensure_module_frontends! Method
ensure_module_frontends!(slate_on)Invoke every loaded module's package-global front-end hook (see ensure_module_frontend!) — Slate calls this once per run drain, handing it the notebook namespace's injected slate_on so a hook can register both front-end scripts (via provide_frontend!) and JS→Julia handlers (via slate_on). Hooks must be idempotent (they run every drain).
SlateExtensionsBase.ensure_widget_assets! Method
ensure_widget_assets!(::Type{W})Lazily load W's front-end into the registry (once per process): the first time it's seen, call required_assets and, if it returns a module, register it as a component under kind_for(W). Slate calls this from the @bind/display path — a no-op for built-ins and any type without a method.
SlateExtensionsBase.ext_asset_url Function
ext_asset_url(mod_or_pkg, sub = "") -> StringThe URL a package-vendored asset (declared with provide_assets!) is served at: /ext-assets/<pkg>/<sub>. Prefer the MODULE form — pass @__MODULE__ and the package key is derived (pkg_key), so it can't drift from the string you passed to provide_assets!. The prefix is owned by the SDK (Slate rewrites it to a page-local sibling in a static export); sub="" gives the base URL for the package's tree.
_gl = ext_asset_url(@__MODULE__, "echarts-gl.min.js") # "/ext-assets/GlobeSlate/echarts-gl.min.js"SlateExtensionsBase.extension_manifest Method
extension_manifest() -> NamedTupleEverything this process's loaded packages have registered with Slate that the hub must mirror into the page — Slate pulls it once per run drain and merges it into the notebook. Fields:
frontend: the front-end scripts ((; id, js, esm, kind)each) fromprovide_frontend!/register_widget!/register_component!.esm=true⇒ inject as an ES module; a non-emptykind⇒ a component (Slate wraps itsexport defaultand registers it underkind).assets: the package-vendored asset DIRECTORIES ((; pkg, dir)each) fromprovide_assets!— Slate serves eachdirat/ext-assets/<pkg>/…while the package is loaded and copies it into a static export.imports: the ES-module import-map entries ((; spec, url)each) fromprovide_import!. Slate merges them UNDER the notebook's own@usedeclarations, live and in an export.fences: the markdown fence languages claimed viaregister_fence_renderer!. Slate does not need these to ROUTE a fence — it rewrites every tagged fence and lets the worker answer — but it does need them to INVALIDATE: a markdown cell caches its interpolation results, so a block rendered before an extension was loaded keeps its plain-code fallback until something restales the cell.
Extensible: a new kind of package registration surfaces as another field here, carried by the same query — no new transport per feature.
SlateExtensionsBase.fence_languages Method
fence_languages() -> Vector{String}Every fence language currently claimed, lower-cased and sorted. Introspection only — Slate does not consult it: it rewrites every tagged fence into an interpolation and lets render_fence answer nothing for the ones nobody claimed.
SlateExtensionsBase.fence_renderer Method
fence_renderer(lang) -> f | nothingThe renderer claiming fenced blocks tagged lang, or nothing when none does.
SlateExtensionsBase.frontend_scripts Method
frontend_scripts() -> Dict{String,String}Every front-end script declared by the loaded packages (id => js) — a copy, so callers can't mutate the registry. See extension_manifest for the full record (incl. module-ness) that Slate pulls.
SlateExtensionsBase.html_fragment Method
html_fragment(html) -> SlateHtmlWrap a self-contained HTML string as a slate_render result — the escape hatch for output that isn't a registered component. Prefer component when a front-end component exists.
SlateExtensionsBase.indices Method
indices(sel::Selection) -> Vector{Int}Each selected option's 1-based position in the widget's original option list.
SlateExtensionsBase.js_bundle Method
js_bundle(key, entry; deps, dir, minify=true) -> String | NothingBuild a JavaScript bundle from an ES-module entry and npm deps, returning the built file's path — or nothing when it can't be built, so a caller always has to have a fallback.
The point is TREE-SHAKING. A vendored front-end library is the largest thing in a self-contained export, and the published "dist" builds are deliberately everything-inclusive: plotly.js ships 3D, geo, mapbox and finance whether a notebook draws a scatter plot or not. An extension that knows which parts it actually needs can say so, and get a bundle containing only those:
js_bundle("plotly-$(join(sort(traces), '-'))", """
import Plotly from 'plotly.js/lib/core';
import scatter from 'plotly.js/lib/scatter';
Plotly.register([scatter]);
window.Plotly = Plotly;
""";
deps = Dict("plotly.js" => "2.35.2"), dir = my_cache_dir())Nothing about this is specific to any library: the caller supplies the entry module and the packages it imports, and gets back whatever the bundler produces.
Requires node and npx on PATH; the bundler is esbuild, fetched by npx on first use. Every failure returns nothing — no node, no network, a bad entry, a bundler error. Building a smaller asset is an optimisation, and an export must never fail because an optimisation was unavailable; the caller falls back to whatever it vendors normally.
Cached under dir keyed by a hash of key, the entry source and the deps, so a rebuild happens only when one of those actually changes. key is a readable prefix on the filename, nothing more.
SlateExtensionsBase.kind_for Method
kind_for(T::Type) -> StringThe wire kind derived for a widget TYPE — its module-qualified name (e.g. "StarRating.Stars"). Because it's namespaced by the defining package, two packages can each ship a Stars widget without their kinds colliding. Used by Widget(T, …) and register_component! so a type-based widget never hand-types (and so never clashes on) a bare kind string.
SlateExtensionsBase.on_live_reset Method
on_live_reset(f)Register a zero-arg callback f run right before live outputs are re-rendered for a freshly-connected browser page (see [slate_live_render]). Use it to reset per-page runtime state so the re-render starts clean. Deduped by identity — safe to call every time an extension is enabled.
SlateExtensionsBase.on_worker_reset Method
on_worker_reset(f)Register a zero-arg callback f run when this notebook's worker has been replaced — restarted, or its namespace rebuilt. Anything the extension established in the OLD worker is gone: sessions it owned, handlers it registered in the namespace, resources tied to that process. Use it to rebuild or drop that state.
This is the Julia half; its browser counterpart is slateOnWorkerReset (see assets/js/panels.js), for state the extension put in the PAGE on the old worker's behalf — which only the page can discard, since the process that owned it is the one that died. Deduped by identity, so re-registering on enable is harmless.
SlateExtensionsBase.package_imports Method
package_imports() -> Dict{String,String}Every package-declared ES-module import (specifier => url) — a copy, so callers can't mutate the registry. Carried in extension_manifest; see provide_import!.
SlateExtensionsBase.pkg_key Method
pkg_key(m::Module) -> StringThe stable, package-scoped key for a MODULE — its package root's name (e.g. "GlobeSlate"). The asset analogue of kind_for for a widget type: a module-derived identity an author never hand-types (and so never drifts on, nor clashes with another package's bare string). It's what a package's vendored assets are served under — /ext-assets/<pkg_key>/…. Two packages can't share a name in one session, so the key is unique per process. Pass @__MODULE__ to provide_assets! / ext_asset_url.
SlateExtensionsBase.provide_assets! Method
provide_assets!(mod_or_pkg, dir) -> StringDeclare a directory of front-end assets that Slate should SERVE while this package is loaded — call it from your module's __slate_frontend hook (or __init__). Prefer the MODULE form: pass @__MODULE__ and the package key is derived (pkg_key) — the same module-scoped identity kind_for gives a widget type — so there's no hand-typed string to keep in sync with your ext_asset_url calls. The files are served at /ext-assets/<pkg>/<subpath> live and copied into a static export; dir is an absolute directory, typically @pkg_dir. Returns the package's base URL:
function __slate_frontend(slate_on)
provide_assets!(@__MODULE__, @pkg_dir("assets"))
# build urls with ext_asset_url(@__MODULE__, "echarts-gl/echarts-gl.min.js")
endFor a front-end LIBRARY too large or multi-file to inline as a provide_frontend! string (Cesium, echarts-gl, anything shipping fonts/workers/wasm). A re-declaration of the same package replaces the dir. The served files are pinned + offline-capable and travel in a static export. To inject a single script, prefer provide_frontend!/register_component!; use this to serve the files that script (or a widget) then fetches / imports / <script src=>s from /ext-assets/<pkg>/….
SlateExtensionsBase.provide_frontend! Method
provide_frontend!(js; id="", esm=false, kind="")Declare a front-end <script> to be injected into the page whenever this package is active in a notebook — call it from your module's __init__. id dedups re-registration (a reload replaces the entry rather than stacking duplicates); omit it to key on the script's content. esm=true marks js an ES module. kind (non-empty) marks js a COMPONENT module: Slate wraps its export default and registers it under kind (see register_component!); "" ⇒ inject the script as-is (it self-registers). The general form behind register_widget! / register_component!. Live and in a static export; no boot cell, no ordering.
SlateExtensionsBase.provide_import! Method
provide_import!(spec, url) -> urlDeclare a browser ES-module import for every notebook that loads this package — the package-level counterpart of a notebook's @use. Front-end code the extension ships (a component module, a provide_frontend! script) can then import the bare spec both live and in an export, with nothing declared in the notebook.
Last-wins and idempotent, so it belongs in __init__ or the per-notebook __slate_frontend hook. A notebook's own @use of the same specifier WINS, so an author can always override the build.
A specifier declared here works in the session that loaded the package — the page extends its import map with any specifier it doesn't yet declare, the same way it injects a front-end script. So a using YourExtension in an open notebook makes import "spec" resolve without a reload. Re-pointing a specifier the page ALREADY declares is the one case that needs one, since a document can't redefine a specifier something may already have resolved.
Pin an exact version rather than a range: an export resolves the URL to bytes, and a moving target means two exports of the same notebook can ship different libraries.
provide_import!("mermaid", "https://esm.sh/mermaid@11.4.1")SlateExtensionsBase.provide_served_asset! Method
provide_served_asset!(bytes; mime="application/octet-stream") -> StringRegister bytes to be served by Slate at a stable, content-addressed, immutably-cacheable URL, and return that URL PATH (root-relative, e.g. /served/<hash>). Registering the same bytes again returns the same path (dedup). The bytes live in the worker; the hub fetches them lazily by hash and caches — so this is safe to call from a hot render path (it's just a Dict insert). Use for a large shared runtime a page loads once (a WASM blob, a JS bundle) rather than re-inlining it per output.
Live only — no export path
That URL is answered by the RUNNING hub, out of the LIVE worker's memory. A static export has neither, so a served URL 404s in an exported or published page — and unlike provide_assets! (page-local siblings for a site, data: for a standalone) nothing rewrites it.
This suits its intended user, a session-bound output (slate_live_render), which is re-rendered per browser connection and never replayed into a static page regardless. For bytes a SELF-CONTAINED output needs to keep after export, use provide_assets! instead.
Note also that this registry is filled at RENDER time, not at load time: unlike the __init__ / __slate_frontend registrations, it does not survive a worker restart until the cell that produced the bytes runs again.
SlateExtensionsBase.reconcile_bind Method
reconcile_bind(oldw::Widget, oldv, neww::Widget)The persistence policy when a bind cell re-runs: a changed widget kind always resets to the new default; otherwise keep the user's value unless the new widget's kind rejects it (per-kind reconciler, or keep-as-is for an unregistered kind).
SlateExtensionsBase.register_cell_action! Method
register_cell_action!(x)Register a per-cell toolbar button contributed by this package — call it from your module's __slate_frontend(slate_on) hook (where you also register editor extensions and RPC handlers). x is any to_cell_action-convertible value: a CellAction, or your own typed button. Slate injects a small script that calls the front-end seam window.slateRegisterCellAction, so the button appears in every cell's action strip (gated by the action's show), clicking it runs the action's onclick. Idempotent — deduped by the action's id (a re-run replaces rather than stacks duplicates), no boot cell. A cell action is an EDITING affordance: it shows in the live editor; a read-only static export renders no cell toolbar, so the button simply doesn't appear there.
SlateExtensionsBase.register_component! Method
register_component!(T::Type, js)
register_component!(kind::AbstractString, js)Auto-register a widget's front-end as a signals-based component module — the blessed authoring pattern. Prefer the type form: the kind is derived from T (SlateExtensionsBase.kind_for), so it's namespaced by your package and can't collide with another package's widget — and there's no kind string to keep in sync with to_widget. js is a module that just export defaults the component:
import { html, useSignal } from "@slate/widget";
export default ({ value, set, params }) => html`…`; // no kind string anywhereShip it as a file and read it with @pkg_asset so the JS lives in a real .js:
SlateExtensionsBase.to_widget(s::Stars) = Widget(Stars, s.default; max = s.max)
function __init__()
register_component!(Stars, @pkg_asset("assets/stars.js"))
endSlate injects <script type="module">, imports the module, and registers its default export under the kind; imports resolve against the page's import map (Preact/htm/signals + @slate/widget are served, offline-pinned). Live and in a static export. The string form takes an explicit (un-namespaced) kind — an escape hatch.
SlateExtensionsBase.register_fence_renderer! Method
register_fence_renderer!(lang, f)Claim markdown fenced code blocks tagged lang ($```lang$). f is called as f(source, info) — the block's body and its full info string — or as f(source) when that is all it accepts, and returns the VALUE the block should render as (displayed via slate_render, exactly like a value a cell returned), or nothing to decline and leave an ordinary code block.
Last-wins and idempotent, so it is safe from __init__ or from the per-notebook __slate_frontend hook, which runs every drain. lang matches case-insensitively.
register_fence_renderer!("mermaid", src -> MermaidDiagram(src))SlateExtensionsBase.register_kind! Method
register_kind!(kind; coerce, reconcile, wrap, domain)Register the value-lifecycle hooks for a control kind. Everything is optional — with none, Slate uses a type-driven default: it coerces the browser value to the type of the widget's default (see coerce_value, with error-fallback) and keeps the value across a re-run. So a typed widget often needs no call at all. Pass:
domain = w -> 0:w_max— a numeric range or allowed-value collection; Slate derivescoerce(coerce to the domain's type, then clamp/restrict into it) andreconcile(reset when out of domain).coerce/reconcile/wrap— raw closures, for a fully custom value lifecycle (e.g. a labeled option's index →Choice). An explicit hook wins overdomain.
register_kind!("stars"; domain = w -> 0:Int(get(w.params, "max", 5))) # bounds; or omit entirelySlateExtensionsBase.register_palette_command! Method
register_palette_command!(x)Register a ⌘K command-palette entry contributed by this package — call it from your module's __slate_frontend(slate_on) hook (where you also register cell actions, editor extensions and RPC handlers). x is any to_palette_command-convertible value: a PaletteCommand, or your own typed command. Slate injects a small script that calls the front-end global window.slateRegisterCommand, so the command appears in the palette alongside the built-ins, badged with your package name. Idempotent — deduped by the command's id (a re-run replaces rather than stacks duplicates), no boot cell.
A palette command is an EDITING affordance: it shows in the live editor. A read-only static export renders no palette, so the command simply doesn't appear there.
function __slate_frontend(slate_on)
provide_frontend!(@pkg_asset("assets/globe_tools.js"); id = "GlobeSlate.tools")
register_palette_command!(PaletteCommand("GlobeSlate.OpenPanel";
label = "Globe: open the layer panel", run = "window.globeSlateOpenPanel()"))
endSlateExtensionsBase.register_widget! Method
register_widget!(kind, js)Auto-register a CLASSIC-script front-end renderer for a widget kind — call it from your module's __init__; js should call window.slateRegisterWidget("<kind>", …). For the higher-level, signals- based component pattern, prefer register_component!.
SlateExtensionsBase.register_widget_js Method
register_widget_js(kind, js) -> WebPageA one-time front-end registration for a custom widget kind: wraps js (which should call window.slateRegisterWidget("<kind>", …)) in a WebPage so returning it from a cell installs the renderer. Put it in a cell above any @bind of that kind. This replaces the hand-rolled struct …; Base.show(MIME"text/html") … end boilerplate an extension would otherwise write.
register_widget_js("mathfield", read(joinpath(pkgdir(@__MODULE__), "assets", "mathfield.js"), String))SlateExtensionsBase.render_fence Function
render_fence(lang, source, info = lang) -> AnyThe value a $```lang$ block renders as — nothing when no extension claims lang, the renderer declines, or it throws. Containing a throw is deliberate: a broken extension degrades that one block to plain code instead of taking down the markdown cell around it.
Slate calls this in the WORKER, where extensions are loaded, while evaluating a markdown cell.
SlateExtensionsBase.replay_stack Method
replay_stack(slices) -> ArrayStack one slice per control value along a NEW trailing dimension, checking that they can travel as packed numeric data. Every slice must have the same shape — they become one array, and a page reads a slice by offset — so a ragged result is refused here rather than silently producing a misaligned page.
Widens an integer element type to Float64 unless it is one the packer handles natively: Int (i.e. Int64) is the common Julia case and is NOT packable, so it would otherwise fall through to JSON and quietly ship the slow representation.
SlateExtensionsBase.required_assets Method
required_assets(::Type{W}) -> js | nothingThe front-end a widget TYPE W needs — return its component module JS (typically @pkg_asset("assets/x.js")), or nothing (the default) for a type with no front-end. Slate calls this lazily, the first time a W is bound or displayed, and registers the module under kind_for(W):
SlateExtensionsBase.required_assets(::Type{Stars}) = @pkg_asset("assets/stars.js")So a package needs no __init__ — declaring the widget is pure dispatch (to_widget + required_assets), and only widgets a notebook actually uses load their JS. A type with no method contributes nothing, so this doubles as extension-detection.
SlateExtensionsBase.run_live_resets Method
Run every on_live_reset callback (isolated). Called by the worker before re-rendering live outputs.
SlateExtensionsBase.run_worker_resets Method
Run every on_worker_reset callback (isolated). Called by Slate when the namespace is re-established.
SlateExtensionsBase.served_asset Method
served_asset(hash) -> (; mime, bytes) | nothingLook up bytes registered by provide_served_asset! by content hash — the hub calls this (over the gate) to fetch a served asset the first time a browser requests it. nothing if unknown.
SlateExtensionsBase.slate_context Method
slate_context() -> NamedTuple | NothingSlate's per-cell execution context, or nothing outside a Slate eval (e.g. local unit tests / a plain include). See the typed accessors below rather than reading fields directly.
SlateExtensionsBase.slate_effect Method
slate_effect(kind::Symbol; names = Symbol[], data...) -> nothingDeclare a cell EFFECT to Slate over the code→Slate channel — e.g. slate_effect(:everywhere; names = [:my_op]) asks Slate to re-establish those names on every region worker (the analogue of Distributed.@everywhere for process-global state a package registers). A no-op outside a Slate cell.
SlateExtensionsBase.slate_emit Method
slate_emit(channel, value) -> nothingPush value to the front end on channel (received by window.slateOnStream(channel, …)), routed through the current context's emitter — a worker PUBs on the gate stream, the in-process kernel pushes over SSE. A no-op outside a Slate cell, so package code can call it unconditionally.
SlateExtensionsBase.slate_everywhere Method
slate_everywhere(names::Symbol...) -> nothingSugar for slate_effect(:everywhere; names = names) — mark process-global state (a custom op, a global config) registered by the current statement so Slate re-establishes it on every worker.
SlateExtensionsBase.slate_live_render Method
slate_live_render(x) -> BoolWhether x is a SESSION-BOUND (live) output — one whose rendered content lives in a per-browser runtime session (e.g. a WGLMakie figure whose scene + interaction handlers run in a live Bonito session), rather than being fully self-contained in the captured HTML. Default false.
Slate uses this to know a cell's output must be RE-RENDERED for each browser page that connects (a reload, a second tab, a reconnect) instead of replaying the stored fragment — the same way a Bonito server serves a fresh session per page load. An extension opts a value in by adding a method:
SlateExtensionsBase.slate_live_render(::MyLiveThing) = trueSlateExtensionsBase.slate_notebook Method
The current notebook id ("" outside a cell).
SlateExtensionsBase.slate_off Method
slate_off(channel) -> nothingDrop the JS→Julia handler registered for channel — the symmetric counterpart to slate_on. Use it to remove a TRANSIENT per-cell handler (e.g. a Bonito session's inbox feed, keyed by its session id) on teardown so a re-run/close doesn't leak dead closures. A no-op outside a Slate cell.
SlateExtensionsBase.slate_on Method
slate_on(channel, f) -> nothingRegister a JS→Julia RPC handler for channel from PACKAGE code — the browser's window.slateCall(channel, payload) invokes f. Routed through the current cell's context (the notebook's __slate_handlers), so a package can wire an interactive widget's actions itself instead of the notebook hand-calling the injected slate_on. The symmetric counterpart to slate_emit (push) — slate_emit is Julia→JS, this is JS→Julia. A no-op outside a Slate cell.
SlateExtensionsBase.slate_on_cleanup Method
slate_on_cleanup(f) -> nothingRegister a zero-arg callback f to run when the CURRENT cell is torn down — before it RE-EVALUATES, when it is DELETED, and before a namespace rebuild. Use it to release a live per-cell resource a package sets up during eval — a Bonito Session, a subscription, a spawned task — so a re-run or a delete doesn't leak it. The callback runs LATER, possibly outside any cell eval (a delete fires it off a worker teardown call), so it must be self-contained — close over the resource and any captured emit/off it needs, not slate_context (which is unset then). A no-op outside a Slate cell.
SlateExtensionsBase.slate_region Method
slate_region() -> Symbol | NothingThe region this eval runs on (nothing = the main side).
SlateExtensionsBase.slate_regions Method
slate_regions() -> Vector{Symbol}Regions declared for the current cell (empty outside a cell or when none are declared).
SlateExtensionsBase.slate_render Method
slate_render(x) -> Dict | SlateHtml | NothingAn extension's rich-output hook: return a component descriptor, an html_fragment, or nothing (the default — x isn't Slate-renderable). Its presence is the detection: showable reports the Slate MIME iff a non-nothing method exists, so Slate's display capture picks it over text/html / text/plain.
SlateExtensionsBase.slate_side Method
The current side as a string ("" = main); "" outside a cell.
SlateExtensionsBase.to_cell_action Method
to_cell_action(x) -> CellActionTurn x into a CellAction. register_cell_action! calls this, so — exactly like to_widget for a @bind control — an extension defines its own type and overloads to_cell_action for a typed, documented button (with auto_cell_action for the common reflect-the-struct case):
Base.@kwdef struct InsertSnippetButton
icon::String = "➕"
title::String = "insert a snippet"
show::String = "cell.kind === 'code'"
onclick::String = "window.myExtInsert(cellId)" # a helper this extension shipped
end
SlateExtensionsBase.to_cell_action(a::InsertSnippetButton) = auto_cell_action(a)The identity method means an existing CellAction passes through unchanged.
SlateExtensionsBase.to_palette_command Method
to_palette_command(x) -> PaletteCommandTurn x into a PaletteCommand. register_palette_command! calls this, so — exactly like to_cell_action for a toolbar button — an extension defines its own type and overloads to_palette_command for a typed, documented command (with auto_palette_command for the common reflect-the-struct case):
Base.@kwdef struct OpenGlobePanel
label::String = "Globe: open the layer panel"
run::String = "window.globeSlateOpenPanel()"
end
SlateExtensionsBase.to_palette_command(c::OpenGlobePanel) = auto_palette_command(c)The identity method means an existing PaletteCommand passes through unchanged.
SlateExtensionsBase.to_widget Method
to_widget(x) -> WidgetTurn x into a Widget. @bind name x calls this, so an extension can define its own type and overload to_widget to get a typed, documented constructor (instead of a bare Widget("kind", …)):
struct Mathfield; label::String; end
SlateExtensionsBase.to_widget(m::Mathfield) = Widget("mathfield", ""; label = m.label)The identity method means an existing Widget (or a built-in constructor's result) passes through unchanged.
SlateExtensionsBase.widget_kinds Method
Registered control kinds (built-ins once core is wired on, plus any extension kinds).
SlateExtensionsBase.with_render_memo Method
with_render_memo(f)Run f, and make slate_render run ONE time for each value that f shows through a Slate MIME.
Slate's display capture wraps the showable + show calls of one value in this. The memo is task-local, so parallel cells do not share it, and finally always clears it — a value that is shown again, or is changed between two displays, gets a fresh render.
This is host plumbing. An extension defines slate_render and never calls this.
SlateExtensionsBase.wrap_value Method
wrap_value(w::Widget, v)The registry value → the user-facing value (e.g. a Choice for a labeled option). Identity for an unregistered kind.
SlateExtensionsBase.@ext_asset_url Macro
@ext_asset_url(sub = "") -> Stringext_asset_url for the CALLING package — the key is derived from the enclosing module, so a package builds its served-asset URLs with just the subpath, no key at all:
@ext_asset_url("echarts-gl/echarts-gl.min.js") # "/ext-assets/GlobeSlate/echarts-gl/echarts-gl.min.js"SlateExtensionsBase.@pkg_asset Macro
@pkg_asset(path) -> StringRead a file bundled in the CALLING package, resolved relative to its package root (pkgdir), as a String. For shipping a front-end asset from __init__ without embedding it in a Julia string: register_component!("stars", @pkg_asset("assets/stars.js")).
SlateExtensionsBase.@pkg_dir Macro
@pkg_dir(path) -> StringAbsolute path to a directory bundled in the CALLING package, resolved against its package root (pkgdir) — the directory analogue of @pkg_asset. For declaring a vendored asset tree: provide_assets!("GlobeSlate", @pkg_dir("assets/echarts-gl")).
SlateExtensionsBase.@provide_assets! Macro
@provide_assets!(dir) -> Stringprovide_assets! for the CALLING package — the key is derived from the enclosing module (pkg_key), so there's no @__MODULE__ to write and no string to keep in sync. Pair it with @pkg_dir (or an Artifacts/Scratch dir):
@provide_assets!(@pkg_dir("assets"))The sections below document the internal submodules. These are not part of the stable public API — they're listed for contributors and the curious. Each @autodocs block picks up every remaining docstring in its module (the entry points above are not repeated).
Notebook server
The HTTP/WebSocket serving layer, live-notebook state, history, and agent integration.
KaimonSlate.NotebookServer Module
NotebookServerThe live, interactive notebook backend (interactivity layer 1). Holds a notebook bound to a .jl file and serves a browser SPA plus a small JSON API. Editing a cell reconciles → reactively recomputes only stale cells → persists back to the .jl (so the agent and the browser share one source). Runs CLI-side, wrapping the engine (ReportEngine) and per-cell renderer (ReportRender).
KaimonSlate.NotebookServer.CloudflarePagesTarget Type
Deploy to a Cloudflare Pages project via wrangler pages deploy (token = CLOUDFLARE_API_TOKEN).
KaimonSlate.NotebookServer.EchartsDoc Type
One documented ECharts option path: path (e.g. yAxis.type) and a markdown doc (meaning + values/default + the Slate DSL form).
KaimonSlate.NotebookServer.GithubPagesTarget Type
Deploy to a repo's gh-pages-style branch via [publish_site] (gh + Actions Pages deploy).
KaimonSlate.NotebookServer.NetlifyTarget Type
Deploy to a Netlify site via netlify deploy --prod (token = NETLIFY_AUTH_TOKEN).
KaimonSlate.NotebookServer.ParCell Type
Minimal per-cell info the scheduler needs (built from a Cell's deps/reads/writes/flags).
KaimonSlate.NotebookServer.PublishResult Type
The outcome of deploying one document to one target — the raw material for a ledger Event.
KaimonSlate.NotebookServer.PublishTarget Type
A deploy adapter. Implement publish(::T, nb; …)::PublishResult and preflight(::T)::NamedTuple.
KaimonSlate.NotebookServer.SlateApiEntry Type
One documented Slate helper.
summary + keywords drive the cheap INDEX surfaces (the slate.api table of contents and the cheatsheet inlined in the agent prompt); signature + doc (or docbinding) drive the full per-helper detail. The split is deliberate: an index line says a helper EXISTS and what it is for, and is lossy on purpose so an agent drills in rather than guessing from a summary.
keywords are ROUTING terms — the words someone would search for who doesn't know the helper's name ("log axis" → echart, "clickable row" → TableSelect). They need not appear in the docs.
KaimonSlate.NotebookServer.ZenodoClient Type
HTTP operations for the Zenodo deposition API, as an interface so tests can inject a fake.
KaimonSlate.NotebookServer.ZenodoHttp Type
Real Zenodo client. sandbox=true targets sandbox.zenodo.org for dry runs.
KaimonSlate.NotebookServer.ZenodoTarget Type
Archive a document to Zenodo as a versioned, citable DOI.
KaimonSlate.NotebookServer._http_get_catalog Method
GET the catalog, honouring etag. Returns (:notmodified | Vector | nothing, error_message).
KaimonSlate.NotebookServer._inscope_modules Method
The package/module names in scope for nb: its project deps ∪ the packages its cells using, plus the universal Base/Core. Drives module-scoped doc search so the SHARED index only surfaces THIS notebook's packages, not another notebook's. Error-tolerant — a failure yields the universals.
KaimonSlate.NotebookServer._installed_extension_registry Method
The extension registry among the reachable registries, or nothing if it isn't installed. Matched on NAME or on repo url — a registry cloned under a different name is still the same one.
KaimonSlate.NotebookServer._is_older Method
_is_older(installed, latest) -> BoolIs installed a strictly earlier release than latest? Both are version strings that may be empty or non-semver (a dev checkout reports no version at all). Anything that doesn't parse as a pair of version numbers is NOT reported as out of date: offering to "update" a local dev checkout to a registry release would quietly replace the user's working copy.
KaimonSlate.NotebookServer._localize_imports Function
Localize an import map for an export. :cdn passes remote urls through; the offline modes resolve each one to bytes and REWRITE the whole module graph, because a CDN commonly answers with a stub that re-exports by relative path — inlining just the entry file yields a page that silently loads nothing.
An import that can't be closed is an ERROR in an offline mode, not a warning: the export was asked to stand alone, and quietly emitting a network-dependent page defers the failure to whoever opens it, possibly years later.
:inline returns the entry specifiers PLUS one synthetic @slate-mod/… entry per module of the graph (see _import_mod_spec); :sibling puts the modules in files and returns page-relative paths.
KaimonSlate.NotebookServer._parse_catalog Method
Validate and unwrap a catalog document. nothing when it isn't one — a 404 HTML page reaching the cache would otherwise poison the gallery until the TTL expired.
KaimonSlate.NotebookServer._resolve_asset_urls! Method
_resolve_asset_urls!(entries) -> entriesMake every image/video reference absolute, resolved against the catalog's own URL.
The build mirrors card imagery into the published artifact and records it as an artifact-relative path (assets/StarRating/ab12cd.png) so the gallery loads everything from one origin. Left as-is, the browser would resolve that against the NOTEBOOK's origin and 404. Entries that already carry an absolute URL (an author hot-linking a CDN, or an asset too big to mirror) pass through untouched.
KaimonSlate.NotebookServer._run_bg Method
_run_bg(work, nb, label; grace) -> (; done, jobid, text)Race a blocking piece of agent work against grace seconds: finished in time → its result; still going → a job id, with work continuing in the background (poll slate.check_eval).
Same machinery and registry as agent_scratch_eval_bg!, for the same reason: an agent must not be held open — or cut off by a transport idle timeout — by a cell that turns out to be slow, and it cannot reliably predict which cells those are. grace <= 0 promotes immediately.
Note this races the RUN, not the structural edit: callers commit the cell (so its id and existence are certain) and pass only the wait-and-format step here.
KaimonSlate.NotebookServer._same_registry Method
_same_registry(reg, url) -> BoolDoes an installed registry point at url? Compared on repo, which is the only field carrying it: a RegistryInstance has NO url field, and reading one throws a FieldError that surfaces to the user as "could not add the registry". That failure appears only on a machine which does not already have the registry — precisely the machine the add path exists for.
Trailing .git, a trailing slash and case are all ignored, so the spelling a user pastes matches.
worker.jl carries a mirror of this: it runs in the worker process and cannot import from here.
KaimonSlate.NotebookServer._stale_note Method
What a run=false write left behind, for its return string.
An agent that never sees its debt doesn't pay it: run=false returns before anything evaluates, so this string is the only place the outstanding work can surface. Report the whole stale set (the edit's cascade plus anything already outstanding), not just the cell touched — the point is the running total, which is what a caller deciding "reconcile now or keep editing" actually needs.
KaimonSlate.NotebookServer._status_alive Method
Is this worker running? The evidence differs by KIND, and asking the wrong question is worse than not asking: proc_up reads a LOCAL process handle, which a remote worker does not have, so a remote worker judged that way reports "not running" on a card that is simultaneously showing its live CPU and memory. An operator page that contradicts itself is worse than one that says less.
A remote worker's evidence is its wire, or failing that a sample recent enough to have come from a live process. A local one is a process we own, so ask the OS — a process that has exited is not running however recently it spoke.
KaimonSlate.NotebookServer._stop_watchers! Method
Stop a notebook's file/@asset watchers, heartbeat and periodic history snapshot. Teardown only.
KaimonSlate.NotebookServer._userprog_note Method
What the notebook's cells last reported through slate_progress, as a short suffix (or "").
Once a run is PROMOTED the caller is no longer on the other end of a gate request, so in-run progress has nowhere to stream to — the poll is where it can still reach them. Without this a poll can only say how long it's been, which doesn't distinguish "70% through" from "wedged".
KaimonSlate.NotebookServer._zenodo_deposit Method
_zenodo_deposit(client, depositionId, file, metadata) -> PublishResultThe four-step deposition flow over an already-written bundle file and a ready metadata block: create (or new-version from depositionId) → upload to the bucket → set metadata → publish. Returns a PublishResult carrying the minted doi and the new depositionId in meta. Notebook-free, so the orchestration is unit-testable with a fake ZenodoClient.
KaimonSlate.NotebookServer.agent_add_cell! Method
Add a cell (default code) after after (end if empty) WITH source, run it, return id + result. One file write (build the cell with its source up front) so the async file-watcher can't race the intermediate empty-cell state. run=false lands the cell STALE without evaluating (see agent_edit_cell! for when that is actually wanted — it is the exception, not the cheap default).
KaimonSlate.NotebookServer.agent_delete_cell! Method
Delete a cell.
KaimonSlate.NotebookServer.agent_edit_cell! Method
Replace a cell's source, run it, return its result.
run=false writes the source and leaves the cell (and its dependents) STALE without evaluating. It is NOT the cheap version of an edit: it returns no result, so the caller learns nothing about whether the new source even works, and the notebook is left in a state no one has verified. Two cases genuinely want it — a BULK refactor (renaming a binding across ten cells, repointing cache paths), where running after every edit means N reactive cascades and N chances for a caller on a transport with an idle timeout to be cut off mid-cascade; and editing a notebook whose upstream cells are mid-computation. Both end the same way: agent_run!(nb) to reconcile. Editing cells one at a time is the DEFAULT, and wanting the result back sooner is not a reason to skip the run.
KaimonSlate.NotebookServer.agent_rename_cell! Method
Rename a cell's id (its label). Ids must be unique + #%%-header-safe; returns a status string.
KaimonSlate.NotebookServer.agent_run! Function
Run one cell (or recompute all stale if id empty); return the result(s).
KaimonSlate.NotebookServer.agent_scratch_eval_bg! Method
agent_scratch_eval_bg!(nb, source; ephemeral=false, grace=_scratch_grace(), memo_*…)
-> (; done::Bool, jobid::String, text::String)Non-blocking scratch eval. Runs agent_scratch_eval! on a background task and races it against grace seconds: finishes in time → (done=true, text=<result>); still running → (done=false, jobid=<id>, text=<hint>), the eval continuing on the worker (poll slate.check_eval). The tool call thus never blocks past grace, so it can't hit the session-tool timeout.
KaimonSlate.NotebookServer.agent_surface_controls! Method
agent_surface_controls!(nb, id, controls; caller="")Surface @bind controls onto cell id's control strip — the agent-facing form of drag-to-host. Presentation only (rewrites the .jl, no re-eval). controls uses the header layout grammar: a,b,c = a row of single controls; [a,b],c = a stacked column [a,b] then a column c; "" clears the strip. Names must be @bind variables defined somewhere in the notebook (validated, so a typo is rejected with the available names rather than silently dropped).
KaimonSlate.NotebookServer.app_defaults Method
app_defaults(; theme, fullwidth, pagewidth, figwidth, scrollzoom, wrapoutput) -> Dict{String,Any}The presentation an app's visitor gets before expressing a preference of their own. Pass the result as appdefaults to export_app or start_server.
These are defaults, not enforcement: a visitor who has chosen keeps their choice, because the app's settings popover offers the same reader-facing subset and localStorage still wins.
theme— a Slate palette name ("daylight","midnight","nord", …)fullwidth—truespans the window instead of a constrained reading columnpagewidth/figwidth— px: the reading column, and a cap on rendered figuresscrollzoom— percent;0means the wheel never zooms a chartwrapoutput—truewraps wide text output instead of scrolling it
An unrecognised name is dropped rather than guessed at, so a typo silently has no effect rather than being applied to the wrong setting.
export_app(nb, "dist/myapp"; appdefaults = app_defaults(theme = "midnight", pagewidth = 1400))KaimonSlate.NotebookServer.archive_target_names Method
Names of the configured targets that are ARCHIVES (see _ARCHIVE_KINDS), from the live ledger.
KaimonSlate.NotebookServer.build_site! Method
build_site!(dir, nb; site_url="", slug="", bundle=false, kwargs...) -> NamedTupleAssemble/merge nb into the static site at dir (created if absent) WITHOUT any git — build the doc's <slug>/ (or the home front page), upsert slate-site.json, and (re)write the client-side index. The deploy-only building block: render locally, commit dir, let CI just push it. Point several notebooks at the SAME dir to accrete a multi-doc site. site_url is the eventual base URL (for absolute OG tags / bundle fetch); "" ⇒ page-relative. Returns (; home, slug, docUrl, ...).
KaimonSlate.NotebookServer.catalog_add_registry! Method
catalog_add_registry!(nb) -> DictAdd the extension registry to the depot the notebook's worker runs in. Separate from installing a package, and separately consented in the UI: a registry is depot-global, so it affects every project on that machine, not just this notebook.
It runs on the WORKER, not the hub: a notebook on a remote region resolves packages against the remote depot, and adding the registry here would silently do nothing for it.
KaimonSlate.NotebookServer.catalog_install! Method
catalog_install!(nb, name; target = "notebook") -> DictInstall a catalog extension into the notebook's environment. Adds the registry first if it's missing — an install that fails with "package not found" because the registry was never added is a dead end the user can't diagnose from the error.
Returns the notebook_pkg_op! result, plus registryAdded when this call had to add the registry.
KaimonSlate.NotebookServer.catalog_registry_installed Method
Is the extension registry installed in this depot? The gallery shows a one-time consent prompt when it isn't, since adding a registry is depot-global rather than notebook-local.
KaimonSlate.NotebookServer.catalog_update! Method
catalog_update!(nb, name; target = "notebook") -> DictUpgrade an already-installed extension to the newest version its environment allows. Distinct from catalog_install! because adding a package that is already a dependency keeps the resolved version — the version shown as out of date in the gallery would simply stay put.
target = "project" updates it in the enclosing project instead, for an extension the notebook inherits rather than owns.
KaimonSlate.NotebookServer.catalog_view Method
catalog_view(nb; force = false) -> DictThe full gallery payload: every catalog entry annotated with this notebook's state (installed, installedVersion, inParent), plus the categories present and where the data came from. The annotation is what lets one list serve both "browse" and "manage" — an installed extension is the same card with a different action.
KaimonSlate.NotebookServer.cell_image Method
cell_image(nb, cell) -> Vector{UInt8} | nothingA PNG of the cell's rendered figure, regardless of where it was drawn: the server-side raster (CairoMakie image/png) if present, else the latest client-captured snapshot (ECharts). nothing if the cell has no viewable figure.
KaimonSlate.NotebookServer.cell_image_fresh Method
cell_image_fresh(nb, cell) -> Vector{UInt8} | nothingLike cell_image, but first refreshes the client-rendered raster from the open browser tab so slate.view is never stale. ECharts cells push a fresh PNG on every render and CairoMakie cells carry an authoritative server-side raster, but markdown / tables / plain-value cells only get a raster on demand — so without this, view returns whatever the last inspect happened to capture. We ask the open tab to recapture (the same round-trip inspect uses; for native-figure cells it's a no-op that leaves the higher-fidelity snapshot intact), then read the freshly-stored image. Skips the round-trip for server-side rasters, and falls back to the last snapshot when no tab is open or the capture times out.
KaimonSlate.NotebookServer.cell_inspect Method
cell_inspect(nb, cellid) -> StringEverything about one cell for the agent's build loop: state (kind/state/deps/reads/writes/ duration/flags), source, the canonical result, and the cell's edit history. The live rendered DOM + optional raster come from the open browser via a separate path (see slate.inspect).
KaimonSlate.NotebookServer.clear_scratch! Method
Empty the notebook's scratchpad and tell the browser to clear its panel.
KaimonSlate.NotebookServer.close_notebook! Method
Remove a notebook from the hub: drain its SSE connections and drop it.
KaimonSlate.NotebookServer.deploy_dir_to_gh_pages Method
deploy_dir_to_gh_pages(repo, dir; private=false, create=true, wait_deploy=true) -> (; ok, url, commit, error)Force-push an already-built site directory dir to repo's gh-pages in one shot (the whole site — no per-doc clone/merge). The "deploy a prebuilt dir" primitive a SITE uses to push its one canonical build to GitHub, mirroring what the S3/Cloudflare/Netlify upload adapters do with the same dir. Creates the repo + enables Pages if needed. Operates on a copy so the canonical local build stays git-free.
KaimonSlate.NotebookServer.doc_summary Method
doc_summary(doc; lines = 4) -> StringThe part of a docstring worth showing in a RESULT LIST: its prose, not its signature.
A Julia docstring conventionally opens with its signature, which @doc renders as a fenced block. In a result list the signature is the least useful part: it restates what the reader typed, while the prose is what tells them whether this is the symbol they want.
Skips one leading fenced block, then takes the first lines non-blank lines of what follows. A docstring that is only a signature falls back to that signature rather than to nothing.
KaimonSlate.NotebookServer.ensure_docs_fts! Method
Mirror the docs collection's text + metadata payloads into Kaimon's FTS index (the plain upsert path doesn't), so lexical name/substring search AND module filters work. Idempotent; best-effort if FTS is unavailable. The auto-index path calls this; the manual index_docs tool too.
KaimonSlate.NotebookServer.expand Method
expand(jl_path; target="") -> StringReinflate a standalone .jl (one carrying a Slate.bundle footer) into a project directory at target (default: <jl>.expanded/). A REPO-ROOTED bundle expands to a real git checkout of the original project (its src/, notebooks/, …) with the LIVE notebook cells in place, wired to origin (branch & PR with matching SHAs). A FLAT bundle writes Project + Manifest, any local/ package source, and the notebook at the root. Returns the target dir.
KaimonSlate.NotebookServer.export_app Method
export_app(nb, dir; appdefaults=Dict(), port=0, agent=false, history=false) -> StringWrite a self-contained application for nb into dir (created if absent), and return dir.
The folder holds the notebook's reproducible bundle plus launchers. Running julia run.jl inside it — here, or on any machine you copy it to, needing only Julia 1.10+ — installs the environment, reconstructs the notebook's exact packages, and serves it as an app: prose, results, figures and live controls, with the authoring API refused server-side. Windows users double-click run.bat.
appdefaults sets the presentation a visitor gets before expressing a preference of their own (build it with app_defaults). port pins the port (0 = pick a free one; SLATE_PORT overrides at run time). agent=false by default — an app's readers are not authoring, so the in-notebook agent is off unless you ask for it.
What ships: by default the project's git-TRACKED files — the right default, since it carries the source and leaves out build output, scratch and stray artifacts. include names extra project-relative files or directories to carry anyway. Reach for it when part of the app is deliberately untracked: reference data under the notebook's datadir() is git-ignored by construction (that directory self-ignores so a stray database is never committed), so an app whose samples live there arrives unable to load them —
export_app(nb, dir; include = ["assets/spectra"])A project with no commits has no tracked files at all, so the bundle falls back to a partial copy; export_app warns when it sees that, because the result looks fine until it is deployed.
There is no authentication. Anything that can reach the port can drive the app and read its results; treat "who can reach this port" as the entire access-control story.
KaimonSlate.NotebookServer.export_gist Method
export_gist(nb; kwargs...) -> NamedTupleCreate a secret GitHub gist holding this notebook's self-contained HTML export, via the authenticated gh CLI. kwargs are the export_html options (theme/charttheme/override/code/ outputs/width/include_source). The gist carries the <slug>.html page plus a README.md that signposts it as a downloadable HTML page (a raw gist shows source, not the rendered page). Returns (; ok, url, preview, raw, curl, error) — preview renders it via gistpreview.github.io, raw is the file's raw URL, curl a ready-to-share download one-liner. Best-effort and never throws: a missing/unauthenticated gh comes back as ok=false with a human-readable error.
KaimonSlate.NotebookServer.export_markdown Method
export_markdown(nb; include_source=true, outputs="all") -> StringSerialize the notebook to GitHub-flavored Markdown for copy-paste (Discourse / Slack / GitHub / Obsidian / docs). Prose rides verbatim; [@cite] and [@fig:label] render to their in-text form (per the notebook's bibstyle) with a trailing References section; code cells become fenced ```julia blocks; text outputs are fenced; figures / frozen charts embed as<img src="data:image/…;base64,…" alt="Figure N"> ; tables become GFM tables. Data-URI images are self-contained but not every host renders them (GitHub strips them) — for those, upload the standalone.jl (+ a PNG/SVG) alongside.
KaimonSlate.NotebookServer.export_pdf Method
export_pdf(nb; include_source=true, style="article", columns=1,
theme="light", code="normal", body="normal") -> Vector{UInt8}Render the notebook to a publication-quality PDF via Typst and return the bytes. style ∈ ("article", "report") picks a layout preset; columns ∈ (1, 2) lays the body out single- or two-column; theme ∈ ("light", "dark") sets the colour scheme (dark matches the live UI and Makie-dark figures). code ∈ ("normal","small","smaller","tiny") sets the code-listing font size, or "hidden" to omit source entirely (also honoured via include_source). body ∈ ("large","normal","compact","small") sets the prose font size (defaults to "compact" for two-column). Figures use vector data when available (CairoMakie PDF, ECharts SVG). @bind controls are omitted by default (a PDF is a static snapshot); set include_params=true to show them frozen to their current values as a parameter strip.
Document metadata is authored as role-tagged cells (#%% md id=… title / abstract / bibliography): the title/abstract are hoisted into an academic title block. With no title cell the document title falls back to the first markdown H1 (then the notebook filename).
KaimonSlate.NotebookServer.export_site Method
export_site(nb; kwargs...) -> Vector{UInt8}Build a self-contained, publishable website for the notebook — index.html (the HTML export, wired to an og-image.png sidecar so a shared link unfurls with a preview) plus that image — as a gzip-compressed tarball. Unpack it into a gh-pages branch / any static host. HTML options (theme, code, outputs, include_source) pass through to the page.
KaimonSlate.NotebookServer.export_to_site Method
export_to_site(nb, name; slug="", bundle=false, base_url="", kwargs...) -> NamedTupleExport nb into the persistent LOCAL site name (created if new), served by the hub at /sites/<name>/. A home notebook becomes the front page; any other notebook gets its <slug>/. The local mirror of [publish_site] — same build_site!, no git/GitHub. Returns (; url, site, slug, home, docCount, dir) where url is the hub-relative path to open. base_url (the site's hub URL) is baked into absolute OG/bundle links when given; "" ⇒ page-relative (fine for local).
KaimonSlate.NotebookServer.export_typst_bundle Method
export_typst_bundle(nb; <same options as export_pdf>) -> Vector{UInt8}The complete Typst PROJECT — doc.typ plus every figure / markdown / code-listing asset it references — as a gzip-compressed tarball (.tar.gz). Unpack it and typst compile doc.typ reproduces the PDF, so the layout and preamble can be tweaked by hand.
KaimonSlate.NotebookServer.fetch_catalog Method
fetch_catalog(; force = false) -> (entries, meta)The published catalog, cached under the cache home and revalidated with an ETag. Returns the entry vector and a meta Dict describing where it came from (source, fetched, error).
Never throws: a network failure falls back to the cache, and an absent cache falls back to the local registry clone. The gallery must open even with no network, because the packages it lists may already be installed.
KaimonSlate.NotebookServer.figure_index Method
figure_index(report) -> (; numbers, labels, capfor)Resolve figure numbering from caption-tagged cells (document order):
numbers:: caption-cell-id → Figure number (Int)labels:: cross-ref label → (num, anchor) (label =label=attr, else the caption cell id; anchor = the bound figure cell's id when known, else the caption cell id). Each label is also registered under afig:prefix, so both[@x]and[@fig:x]resolve.capfor:: caption-cell-id → bound figure cell id ("" if none resolved)
KaimonSlate.NotebookServer.index_docs! Method
Embed + upsert harvested doc records into the search index. Returns the count indexed.
KaimonSlate.NotebookServer.local_registry_entries Method
local_registry_entries() -> VectorCatalog entries built from the extension registry's clone in the depot: name, uuid, version, repo. No prose — that lives in the published artifact — but always accurate about what can be installed, which is what makes this a usable fallback rather than an empty screen.
KaimonSlate.NotebookServer.note_external_tool! Method
note_external_tool!(nb, agent_id, toolname, args, result; ok=true)Gate on caller identity, then surface the write if it came from outside this notebook's crew. agent_id is KaimonGate.current_agent_id() for the call: "" for an outside MCP client (always external), or the owning Kaimon agent's id — external only when it is NOT one of THIS notebook's crew agents, whose calls the agent:<id> relay already streams into the pane (surfacing those here would double them).
KaimonSlate.NotebookServer.notebook_docid Method
notebook_docid(nb) -> (; docId, sourceRepo, sourcePath)The document's stable ledger identity. It is a one-time id embedded in the notebook (meta["docid"], persisted to the .jl footer), so it never changes when the file moves or the repo gains/loses an origin — the failure that used to split one notebook into two ledger entries. Generated + persisted on first use. sourceRepo/sourcePath are derived from git purely for DISPLAY.
KaimonSlate.NotebookServer.og_image Method
og_image(nb) -> Vector{UInt8} | nothingThe social-preview image for the notebook. In priority order: the figure of a cell explicitly tagged thumbnail (or og); else the first real figure in the notebook (a CairoMakie raster, chart, or animation — never a snapshot of text/value output); else a generated title card; else nothing. Used as the og:image for a published page.
KaimonSlate.NotebookServer.open_notebook! Method
open_notebook!(hub, path) -> idLoad the notebook at path into the hub (reusing the existing entry if already open) and start its file watcher. Returns the hub id (its /n/<id> route).
KaimonSlate.NotebookServer.publish_doc_set_targets! Method
Assign/replace the set of target names on this notebook's document (persisted).
KaimonSlate.NotebookServer.publish_document! Method
publish_document!(nb, ledger, docId, store; target_names=nothing, on_event=nothing, kwargs...)
-> Vector{PublishResult}The top-level publish action: deploy docId to each of its ledger targets (or the given target_names) concurrently, append one Event per target to ledger, persist through store (load-merge-save), and return the per-target results. on_event streams per-target progress.
KaimonSlate.NotebookServer.publish_ledger_view Method
The whole ledger as the manager's view model (loads via the default store — may hit the network for gist).
KaimonSlate.NotebookServer.publish_preflight Method
publish_preflight(repo) -> NamedTupleInspect (read-only, no mutation) what publishing to repo would do, so the UI can warn before acting: whether gh is available, the repo exists, its visibility, and whether it already has a gh-pages branch / live Pages site that a publish would overwrite.
KaimonSlate.NotebookServer.publish_secret_set! Method
Set (empty value ⇒ delete) a secret by ref; returns the sorted list of ref NAMES (never values).
KaimonSlate.NotebookServer.publish_set_home! Method
Set/clear whether this notebook is site's front page. Front page is driven by the home tag (model A): this toggles the tag and rebuilds the notebook into the site so its home reflects the change. Setting it as home for one site clears it as home elsewhere it's built (the tag is notebook-global for now).
KaimonSlate.NotebookServer.publish_set_membership! Method
Associate (build into) or disassociate (remove from) this notebook and a site's canonical local build. Local only — a subsequent Publish/Sync deploys. Returns the refreshed publish_sites_info.
KaimonSlate.NotebookServer.publish_site Method
publish_site(nb, repo; private=false, create=true, kwargs...) -> (; url, repo, created, pagesEnabled, pagesError)Publish the notebook as a GitHub Pages site to repo ("owner/name"), using the user's installed + authenticated gh CLI. If the repo is missing and create is set, it's created with the requested visibility (private); the built site is force-pushed to gh-pages, Pages is enabled, and the URL returned. An EXISTING repo's visibility is left untouched. Idempotent — re-runs just update the branch. Requires gh on PATH and gh auth login. (Pages needs a PUBLIC repo on the free plan.)
KaimonSlate.NotebookServer.publish_site_delete! Method
Delete a site. Local removal always: the ledger definition AND the local canonical build dir go away. purge=true additionally tears down deployed content on each of the site's targets where feasible (see purge_deployed!). The view gains a purgeLog entry when a purge ran.
KaimonSlate.NotebookServer.publish_site_set! Function
Create/update a site: its destination targets, home doc, display title ("" ⇒ the site name), and optional per-target subpaths (target → path within that target; "" ⇒ its root). Refuses a (target, subpath) location already claimed by another site — they'd overwrite each other. Membership/order/sections live in its local build.
KaimonSlate.NotebookServer.publish_sites_info Method
For THIS notebook: every site with {member, isHome, targets, url}, plus the known targets — the read model the notebook's Publish panel paints. Membership is matched by the site build's recorded source path (slug as a fallback for pre-migration builds); front page is the site's built home (site_frontpage).
KaimonSlate.NotebookServer.publish_target_delete! Method
Delete a target definition. Removal is LOCAL by default: the ledger entry goes away and every reference (documents AND sites) is detached — deployed content stays live. purge=true also tears down the deployed side where feasible (see purge_deployed!; rsync-serve stops its remote server and removes the served dir). The view gains a purgeLog entry when a purge ran.
KaimonSlate.NotebookServer.publish_to_site! Method
publish_to_site!(nb, siteName; on_event=nothing, deploy=true, slug="", kwargs...) -> summaryBuild nb into the site's canonical local copy (staging it as a member — stamping its source + id), and when deploy=true also Sync the whole build to every destination. deploy=false STAGES only (the local copy is built, nothing is deployed) — the "add a notebook" path under the Stage→Sync model. kwargs are the site-build options (bundle/history/…).
KaimonSlate.NotebookServer.publish_to_targets Method
publish_to_targets(nb, targets; on_event=nothing, kwargs...) -> Vector{PublishResult}Deploy nb to every PublishTarget concurrently, preserving input order in the result vector. on_event, if given, is called on_event(i, :start, target) then on_event(i, :done, result) for each target — the seam the manager UI streams over SSE. Targets are isolated: a throwing/failing target yields an ok=false result and never aborts its siblings.
KaimonSlate.NotebookServer.purge_deployed! Method
purge_deployed!(t::PublishTarget) -> (; ok, log)Tear down what a target DEPLOYED, where that's feasible — the opt-in purge of the removal flows. Only self-hosted kinds can genuinely undeploy; for static hosts (GitHub Pages / Cloudflare / Netlify / buckets) this is a documented no-op — remove the deployed content from the host's own console (or push an empty site) instead.
KaimonSlate.NotebookServer.record_publish_site! Method
record_publish_site!(nb, repo, result) -> StringReflect a successful publish_site to repo in the ledger: ensure a github-pages target for the repo (auto-named, reusing an existing one), ensure this notebook's document (assigned to that target), and append a publish event (live URL, commit SHA, deploy status). Returns the target name. Best-effort — a ledger failure is logged and never fails the publish itself.
KaimonSlate.NotebookServer.relay_agent_event Method
relay_agent_event(channel, data)Gate-bus callback for an agent:<id> event: forward the raw {kind,turn,data} JSON onto the bound notebook's SSE, prefixed agent: so the SPA's live-event handler routes it to the chat pane. data already rides the bus as a JSON string.
KaimonSlate.NotebookServer.reorder_published_site Method
reorder_published_site(repo, ordering) -> (; ok, changed, url, commit)Apply a new section/order to repo's published-site docs and re-push ONLY the manifest + regenerated index to gh-pages (no doc rebuild). ordering is an iterable of dicts {slug, section, order}.
KaimonSlate.NotebookServer.reorder_site! Method
reorder_site!(name, ordering) -> (; ok, docCount)Apply a new section/order to the LOCAL site name's docs and regenerate its index — no git, no deploy (Sync pushes the result to the destinations). ordering is an iterable of {slug, section, order}.
KaimonSlate.NotebookServer.run_publish Method
run_publish(nb, target_names; archive=false, on_event=nothing, build opts…) -> summary::DictLoad the ledger, ensure this notebook's document + its targets, resolve secrets from the config home, fan out the publish concurrently (forwarding the site-build options to each adapter), record one event per target, and persist. on_event(i, phase, payload) streams progress. Throws if a named target isn't configured.
Publishing and archiving are DIFFERENT VERBS on the same store: with archive=false (default) every named target must be a re-pushable live destination — an archive kind (Zenodo) is refused, because a deposit mints a permanent immutable version and must never ride along with a site push. With archive=true the run is a deliberate archival: every named target must be an archive kind.
KaimonSlate.NotebookServer.scratch_check Method
Poll a background scratch job: its result if finished (and forget it), else a still-running note.
KaimonSlate.NotebookServer.search_docs Method
Hybrid docs search over the slate_docs index. ONE search_code call now does the query embed, the semantic+lexical fusion, and span-dedup — replacing the old _embed + _semantic_docs + _fts_docs + hand-rolled fusion (per Kaimon's SEARCH_INTEGRATION_NOTES two-tool model). modules (when non-empty) scopes to those packages via a metadata.module any-of filters on BOTH engines — pass the notebook's in-scope set (_inscope_modules) so a query can't surface another notebook's packages from the shared index. Notes: collection is required (the service endpoint has no workspace binding); embedding_model must match index_docs! (qwen3-embedding:0.6b) or the semantic arm degrades to lexical-only — which still returns name/substring hits.
KaimonSlate.NotebookServer.secret_refs Method
The configured secret ref NAMES (values never leave the process).
KaimonSlate.NotebookServer.serve_notebook Method
serve_notebook(path; host="127.0.0.1", port=8765, quiet=true, app=false, appdefaults=Dict())Open the notebook at path in a hub and serve it. Blocks until stopped (Ctrl-C shuts the hub and its workers down cleanly). Once the hub is answering HTTP, prints a framed banner with the openable notebook URL (so a launcher like run.jl surfaces a ready, clickable link rather than a bare port). With quiet=true (default) the console stays clean after the banner: the hub's log detail (worker spawns, connects, warnings) goes to a file in the same tmp dir as the worker logs — the banner shows the path; only errors still print.
KaimonSlate.NotebookServer.set_bind_by_name! Method
set_bind_by_name!(nb, name, value) -> nbSet a @bind by NAME — what set_bind(:name, value) in cell code resolves to. No-op if nothing declares it, so a stale name in a handler can't break a run.
The cell id comes from ReportEngine.bind_owner: the browser and the agent both already know which cell holds a control, but cell code names only the variable, which is the point — a notebook shouldn't have to know which of its own cells happens to declare a control in order to move it.
KaimonSlate.NotebookServer.slate_api_reference Function
slate_api_reference(topic = "") -> StringThe slate.api tool's text.
""→ the INDEX (slate_api_toc): one line per helper. Cheap; the default on purpose."all"/"full"→ every entry in full (what the index replaced as the default)."name"/"name1 name2 …"→ those entries in full — BATCHED, so a cell needing three helpers costs one call rather than three.a CATEGORY (
"Widgets","charts") → every entry in it.anything else → entries whose name/category/keywords/doc contain every word of the topic; failing that, a "did you mean" list of the nearest entries rather than a dead end.
KaimonSlate.NotebookServer.stage_site! Method
stage_site!(name; hub=nothing) -> {ok,url,buildDir}STAGE the site: (re)build the local copy that is EXACTLY what Sync will deploy. Re-exports every member whose notebook is open (fresh from its live state) and keeps the existing build for the rest (a closed notebook can't be rebuilt without its kernel). Deploys NOWHERE — the staged copy is local + unexposed, viewable at /sites/<slug>/. Sync later just COPIES this artifact to the destinations. Leaves the site "unsynced" (the sync stamp is untouched), since nothing was deployed.
KaimonSlate.NotebookServer.start_hub Method
start_hub(; host = "127.0.0.1", port = 8765, app = false, appdefaults = Dict()) -> HubStart one HTTP server that hosts many notebooks, and return the Hub. Notebooks are added and removed while it runs with open_notebook! and close_notebook!; stop_hub shuts it down. This is the layer the slate app itself runs on — reach for it when you are embedding Slate in your own script rather than opening a single notebook with serve_notebook.
host defaults to loopback, so the hub is reachable only from this machine; bind "0.0.0.0" to serve a network. There is no authentication at any bind address — whatever can reach the port can drive every notebook on it.
app = true serves in application mode: prose, results, figures and live controls, with the authoring routes refused server-side rather than merely hidden. appdefaults (build it with app_defaults) sets what a visitor sees before choosing for themselves.
KaimonSlate.NotebookServer.start_server Method
start_server(path; host="127.0.0.1", port=8765, app=false, appdefaults=Dict()) -> HubStart a hub and open the single notebook at path. Non-blocking; returns the Hub (stop it with stop_hub). The notebook is served at /n/<id> (printed); / is the index. For a blocking launcher use serve_notebook.
app=true serves the notebook as an application: the reading view (markdown, output, figures and live @bind controls — no code, no cell chrome) with the authoring API refused server-side. Presentation defaults for visitors go in appdefaults; build it with app_defaults. See server_app.jl for what app mode does and does not guarantee.
KaimonSlate.NotebookServer.stop_hub Method
Stop the hub: drain every notebook's SSE connections, then close the server.
KaimonSlate.NotebookServer.stop_server Method
Stop a hub started by start_server (drains SSE, frees the port).
KaimonSlate.NotebookServer.sync_site! Method
sync_site!(name; on_event=nothing, hub=nothing) -> summaryDeploy a site's ONE canonical local build (_site_dir(name)) to every one of its destination targets, concurrently and identically. on_event(i, phase, payload) streams per-target progress. Throws if the site has no local build yet or no configured destinations. Zenodo/non-host targets report an error row.
KaimonSlate.NotebookServer.target_from_ledger Method
target_from_ledger(t::PublishLedger.Target; secrets=Dict()) -> PublishTargetConstruct a runtime adapter from a ledger target config. secrets (a Dict or a ref -> value callable) resolves the target's secretRef for backends that need a token (e.g. Zenodo). CLI-based backends (github-pages via gh, s3/rsync) read their creds from the environment and don't carry a secret on the adapter.
KaimonSlate.NotebookServer.target_name Method
The target's name (its ledger key, e.g. gh:portfolio) — used to attribute the event.
KaimonSlate.NotebookServer.zenodo_request Method
zenodo_request(client, method, url; json=nothing, file=nothing) -> (status::Int, body)The single HTTP primitive the deposition flow is built on. url is either a path under the client's base or an absolute URL (bucket uploads use the deposition's absolute bucket link). json sends a JSON body; file streams a file's bytes (for bucket uploads). Returns the status and parsed-JSON body (or {} on a non-JSON/empty body); never raises on an HTTP error status.
KaimonSlate.NotebookServer.@report_op Macro
@report_op nb report begin … endGuarded with_report: take nb.lock, bind report = nb.report, run the body — and at macro-expansion REJECT any direct kernel round-trip / blocking call inside the locked region (see _KERNEL_BOUNDARY), so the notebook-lock invariant can't be broken by a direct call (it would have caught the post-drain refine_usings! bug). Indirect calls through a helper still rely on the protocol + review.
KaimonSlate.NotebookServer.SlateHistory.Doc Type
Doc(key, path)A notebook's STORAGE IDENTITY. The store deliberately does not decide what makes two files the same document — the caller owns that (see NotebookServer.doc_key, which prefers the notebook's file-carried docid so history survives a move or a rename). path is provenance only: it is recorded in meta.json and nothing keys off it.
Doc(path) is the legacy identity, hashing the absolute path, and is what a notebook with no docid still resolves to — so existing stores keep loading untouched.
KaimonSlate.NotebookServer.SlateHistory.fork! Method
Copy a document's store to a new key, so a fork keeps its lineage up to the split.
KaimonSlate.NotebookServer.SlateHistory.known_paths Method
Every path this document has been recorded at, OLDEST FIRST — so the head of the list is where the document originated and everything after it is somewhere it was later copied or moved to. Empty when it has no store yet.
KaimonSlate.NotebookServer.SlateHistory.quiet_paths Method
Paths that have chosen to stop being told this document is shared. Recorded PER PATH, not per document, so one copy going quiet never silences the notice for another.
KaimonSlate.NotebookServer.SlateHistory.record! Method
record!(path, source; source_label="browser", kind="checkpoint", cells=nothing, label="") -> entry | nothingRecord a notebook snapshot. Deduped by content hash — returns nothing (and writes nothing) when source equals the latest recorded state. Otherwise appends a delta log entry, stores the (zstd) content object, and refreshes the head keyframe. cells is an iterable of (id, kind, source).
KaimonSlate.NotebookServer.SlateHistory.relocate! Method
Move a document's store to a new key (a no-op when the source is absent or the target exists).
KaimonSlate.NotebookServer.SlateHistory.silence! Method
Stop warning doc.path that this document is shared.
Report engine
The reactive evaluation core — parsing .jl notebooks, the dependency graph, kernels, cells, binds, and paged tables.
KaimonSlate.ReportEngine Module
ReportEngineSession-side engine for the notebook-like report builder (see PLAN-report-builder.md). This is the engine half of the engine/renderer split (§15.1): it owns the cell/document model, parsing the Literate-style .jl source, and (later) isolated-module evaluation + dependency inference. It runs inside the warm gate session and depends only on light, session-safe packages.
This first slice implements just the model + parse/serialize round-trip — no evaluation yet — so it is testable with Base alone.
KaimonSlate.ReportEngine.Animation Type
A precomputed animation: a quantized frame stack + colormap LUT + a small display manifest.
KaimonSlate.ReportEngine.BindSpec Type
A reactive input widget bound to a variable (@bind name Slider(0:100), §Layer 3).
KaimonSlate.ReportEngine.Cell Type
A single report cell. id is the persistent identity (survives edits/moves); src_hash answers "did the source change". Inference/eval fields are populated later by the dependency + eval passes.
KaimonSlate.ReportEngine.Cell Method
Construct a fresh cell, hashing its source and marking it stale (never-run).
KaimonSlate.ReportEngine.CellOutput Type
Captured result of evaluating a code cell.
KaimonSlate.ReportEngine.FollowUp Type
One call a tool's reply names as the next step: poll marks the one that tracks the work.
KaimonSlate.ReportEngine.GateKernel Type
GateKernel(project; parent="", envdir="") <: KernelEvaluate cells in a SlateWorker subprocess pinned to the single environment project.
Environment model (fork-and-extend, never LOAD_PATH-stacked):
Base mode (
project == parent): the notebook has no packages of its own, so it runs directly in the enclosingparentproject — zero overhead, exactly like a plain script.Forked mode (
project == envdir): once the notebook adds a package, it gets its OWN env (envdir) seeded from the parent (parent package dev'd in, parent deps + Manifest copied) and resolved as ONE consistent environment — so the notebook can override the base and there are never two versions of a shared dep. Adds never touch the parent.Detached (
parent == ""): no enclosing project; the notebook env is everything.
envdir is the fork target (the per-notebook env dir); parent is recorded for provenance and re-seeding. Lazily spawns + connects on first use (prepare!).
KaimonSlate.ReportEngine.InMemoryPagedProvider Type
Pages/sorts/filters a column-major dataset in-process (the paged form of slate_table).
KaimonSlate.ReportEngine.InProcessKernel Type
InProcessKernel <: KernelEvaluate cells in the report's own in-process Module. Stateless — the namespace lives on report.mod, managed by report_module / reset_module!.
KaimonSlate.ReportEngine.LocalTarget Type
Run the worker on this machine (the default — unchanged behaviour).
KaimonSlate.ReportEngine.MimeChunk Type
One representation of a cell's output (MIME-generic display bundle, §7).
KaimonSlate.ReportEngine.PageRequest Type
A request for one page: 1-based page, sort_col (0 = none), direction, global search.
KaimonSlate.ReportEngine.PageResult Type
One page of rows plus the total row count of the (filtered) result.
KaimonSlate.ReportEngine.PagedProvider Type
A data source that serves pages on demand. Implement page_columns + fetch_page.
KaimonSlate.ReportEngine.PendingKernel Type
PendingKernel <: KernelPlaceholder installed on LiveNotebook.kernel while the real kernel is booting (a worker spawn) or being reconstructed (a standalone bundle's environment). Every dispatched call BLOCKS until _resolve!/_reject! fires, then forwards to the real kernel — so a run/edit request that races the boot window queues transparently instead of silently evaluating in-process against the wrong (extension's own) environment, or erroring on a worker that doesn't exist yet. Mirrors GateKernel's own lock-guarded prepare! (which blocks concurrent callers on a reconnect), generalized to "any kernel op, before the real kernel is known."
KaimonSlate.ReportEngine.RemoteTarget Type
RemoteTarget(ssh_host; transport=:tunnel, project="~/.cache/kaimonslate/remote")Run the worker on ssh_host — an SSH target you have ALREADY set up (a Host in ~/.ssh/config, key-based auth). We piggyback on that: no password prompts, no host-key negotiation here. transport is :tunnel (firewall-safe, SSH-encrypted) or :direct (CURVE-encrypted straight dial). project is the remote path the notebook's parent project is provisioned into (kept in sync from local).
KaimonSlate.ReportEngine.SlatePagedTable Type
A captured paged table: the registered provider id, columns, total, and page 1.
KaimonSlate.ReportEngine.SlateTable Type
A captured tabular result: typed columns + JSON-safe row cells, rendered client-side.
KaimonSlate.ReportEngine.SqlPagedProvider Type
Browses a SQL relation with sort/filter/paging pushed into the query (slate_query).
KaimonSlate.ReportEngine.ToolCall Type
ToolCallOne invocation of a session tool: what was called, with what, what came back, and how long it took. Returned by slate_tool and rendered as a panel rather than a string, so the call and its schema stay legible in the document after the fact.
KaimonSlate.ReportEngine.Tunnel Type
Tunnel — a supervised `ssh -L` forward set. Respawns the SSH process if it drops
(autossh-lite), so the ZMQ client's reconnect survives a network blip.KaimonSlate.ReportEngine.FileUpload Method
FileUpload(; accept="", label=nothing, maxbytes=0) -> WidgetA file the READER supplies. The browser sends the bytes to the server, which stores them under the notebook's datadir() and binds an UploadedFile — so downstream cells get a real path and recompute exactly as they would for a moved slider. Before anything is uploaded the value is nothing.
accept is the file-picker filter, in the HTML accept vocabulary (".csv", ".csv,.txt", "text/*"); it is a convenience for the reader, not a guarantee — validate what you actually got. maxbytes rejects anything larger (0 = the server default).
@bind datafile FileUpload(; accept = ".csv", label = "Data")
if datafile === nothing
md"Upload a file to begin."
else
CSV.read(datafile.path, DataFrame)
endKaimonSlate.ReportEngine.RangeSlider Method
RangeSlider(range; default=nothing, label=nothing) -> Widget
RangeSlider(min, max; step=1, default=nothing, label=nothing) -> WidgetA slider with two thumbs, binding an interval as a (lo, hi) NamedTuple.
For choosing a span — a region of a signal, a date window, an axis limit pair — where the two ends are one decision. Two separate sliders make the reader hold "lo must stay below hi" in their head and let them cross; one control with two thumbs cannot be put into that state.
@bind span RangeSlider(400:4000; default = (1500, 1800), label = "region")
lo, hi = span # destructures
span.lo, span.hi # or by nameKaimonSlate.ReportEngine._active_project_deps Method
The in-process kernel's own active project's direct deps as {name, version, uuid} — the same project pkg_op mutates. Mirrors the gate worker's __slate_project_deps.
KaimonSlate.ReportEngine._arg_control Method
The control for one parameter: a select where the type enumerates its values, a number field where it is numeric, a text input otherwise. An empty selection means "not supplied", which is how a call omits an optional parameter and lets the tool's own default stand.
KaimonSlate.ReportEngine._auto_id Method
Deterministic short id from a cell's content + position (used when none given).
KaimonSlate.ReportEngine._cell_source Method
Emit one cell as a header line plus its body.
KaimonSlate.ReportEngine._download_button Method
download_button(name, data; label=nothing, mime="") -> HTML
download_button(ref::AssetRef; label=nothing) -> HTMLA button that saves a generated result to the reader's disk. name is the filename they get.
Reading a table on screen is not the same as leaving with it. A notebook author can always tell someone to copy the output, but an app's reader has no cell to run and no filesystem to look in — so anything they are meant to keep needs an explicit way out. This is that way out: data goes through save_asset, and the button hands the reader those exact bytes.
data is whatever save_asset accepts (a String, Vector{UInt8}, numeric array, or a JSON-able value); pass mime when the extension doesn't imply it. Works live, in a standalone export, and on a published page — the export inlines the bytes, so a frozen page still downloads.
io = IOBuffer(); CSV.write(io, results)
download_button("results.csv", String(take!(io)); label = "Download the results")KaimonSlate.ReportEngine._expand_cell_statements Method
Expand src's top-level statements in mod (recursively, NEVER evaluating) → the expanded exprs. A macro may return Expr(:toplevel, …) (@enum does) whose sub-statements still carry unresolved hygienic-scope nodes — re-expanding each one resolves them.
KaimonSlate.ReportEngine._expanded_bindings_of Method
ExpressionExplorer analysis of expanded statements → (reads, writes)::Tuple{Set{Symbol},Set{Symbol}}, or nothing (nothing expanded / analysis threw → the caller keeps its conservative scan). ee is the ExpressionExplorer module — each side passes its own import, same pinned version. Hygiene: gensyms ('#' anywhere) and EE's synthetic anonymous-fn names are never notebook bindings and are dropped; a qualified ref on the raw AST surfaces as its ROOT symbol (:Base) — a harmless extra read (no cell ever writes Base), so it passes through.
KaimonSlate.ReportEngine._followups Method
Every backticked call a result advertises, in the order stated, deduplicated.
KaimonSlate.ReportEngine._free_local_port Method
An OS-assigned free local TCP port (bind :0, read it, release — small race window).
KaimonSlate.ReportEngine._graphics_export_names Method
Union of exports of every RESOLVED Makie-family module (empty until one resolves). Compute once per pass and reuse — don't call per cell.
KaimonSlate.ReportEngine._is_graphics_cell Method
True when c touches Makie's shared global state: lexical match, or provenance — any of its reads/provides is an export of a resolved Makie-family module.
KaimonSlate.ReportEngine._is_md_line Method
True for a Literate-style markdown line: # alone or # … (hash + space).
KaimonSlate.ReportEngine._parse_header Method
Parse a header line's trailing tokens into (kind, id, controls, tags::Vector{Symbol}). Every token that isn't id=/controls=/code/md becomes a tag flag (known ones drive behaviour; the rest are free-form metadata that round-trips).
KaimonSlate.ReportEngine._register_invoke! Method
Register the panel's call-back path and return its channel ("" when there is nowhere to register, which renders an inert panel).
The browser calls back on this channel with the edited parameters, so re-running a tool never needs the CELL to re-run, which matters because a cell re-run would also re-execute everything downstream of it. One handler serves the panel's whole surface: Invoke re-fires this tool, and a follow-up button fires the tool the reply named by passing __tool, so a tracked run stays on the channel the cell already registered.
KaimonSlate.ReportEngine._reject! Method
Unblock every waiter on k with a boot failure — forwarded calls raise err instead of hanging forever.
KaimonSlate.ReportEngine._replicate_env! Method
_replicate_env!(t::RemoteTarget) -> nothingReproduce t.origin_env (the notebook's local project) on the remote at t.project: rsync it wholesale (Project.toml + Manifest.toml + any /src), rsync each dev'd dep's source into devsrc/<name> and rewrite BOTH the Manifest path and Project.toml's [sources] path (Julia ≥1.11 resolves dev deps from the latter) to point there, then instantiate. The Manifest makes registry versions exact and clones git deps from their recorded urls; the dev-source rsync makes local checkouts resolve on the host.
KaimonSlate.ReportEngine._resolve! Method
Unblock every waiter on k — subsequent (and in-flight) calls forward to real.
KaimonSlate.ReportEngine._save_asset Method
save_asset(name, data; mime="", dtype=nothing) -> AssetRefRegister data as a named front-end asset produced by this cell — the write-side dual of @asset. Returns an AssetRef that interpolates to a stable, page-local path; load it client-side with Slate.asset(ref). Handles Julia values by type:
a numeric array/matrix → packed as raw column-major binary +
{dtype, shape, order}, so a client reads it straight into aFloat32Array(Slate.assetyields{data, dtype, shape, order}).dtype=downcasts (e.g.dtype=Float32);a
String→ raw text, aVector{UInt8}→ raw bytes (givemime=);any other value (Dict, NamedTuple, …) → JSON (encoded server-side; must be JSON-able).
The bytes ride with the cell's memo, are served live, and are inlined (standalone) or published as a sibling (site) — so a widget/chart works live, offline, and hosted alike.
KaimonSlate.ReportEngine._session_tools Method
Every gate tool registered in this session, or an empty vector when no gate is loaded.
KaimonSlate.ReportEngine._slate_json Method
Minimal stdlib-only JSON encoding of a value, safe to embed inside a <script>.
KaimonSlate.ReportEngine._strip_blank_edges Method
Trim a leading and trailing run of blank lines, preserving interior blanks.
KaimonSlate.ReportEngine._strip_md Method
Strip the # / # prefix from a Literate markdown line.
KaimonSlate.ReportEngine._tool_expand Method
_tool_expand(ex) -> ExprRewrite @tool name(arg = value, …) into slate_tool("name"; arg = value, …).
A plain function rather than the macro itself, because the macro is built INSIDE each notebook namespace by _populate_notebook_ns! (the same shape @trace uses), so the transform has to be callable from there.
It emits STRING-keyed pairs rather than keyword syntax. The macro is built inside each notebook module, so Julia's hygiene pass qualifies every un-escaped symbol it returns to that module, and a keyword NAME cannot survive that: run_id = x comes out as (thismodule).run_id = x, which does not parse. A name cannot be escaped either, since that asks for its value. Passing the arguments as ["run_id" => x] sidesteps hygiene entirely, because a string is not a symbol.
KaimonSlate.ReportEngine._tool_meta Method
Reflected metadata for one tool: its description and full declared parameter list.
KaimonSlate.ReportEngine.animate Method
animate(frames; kind=:heatmap, fps=30, colormap=:auto, clim=:global, transform=nothing,
dither=true, bits=8, x=nothing, y=nothing, title="", colorbar=true,
loop=true, autoplay=false, overlay=nothing, maxbytes=128_000_000,
height=nothing, width=nothing, maxheight=560) -> AnimationPrecompute a stack of frames ONCE, then play it back entirely in the browser on a WebGL canvas — nothing touches Julia during playback, so a slow simulation still plays at 60 fps. The heavy compute is yours and runs once; animate only quantizes + packages.
kind=:heatmap (default) takes a vector of 2-D scalar matrices, colormapped via colormap/clim, where clim is :global (comparable frames) | :symmetric (signed fields → diverging map; skips transform) | :perframe | (lo, hi). kind=:image takes real color frames — a vector of H×W color matrices (e.g. Matrix{RGB} from VideoIO.jl/Images.jl) or H×W×3 arrays — played back true color, no colormap.
height / width set the player's displayed size in CSS pixels, preserving the frame aspect and never exceeding the cell width; give either one and the other follows. Without them the canvas takes the full cell width and derives its height from the aspect, which is why maxheight (560 px) caps it: a frame stack taller than it is wide would otherwise render as a wall.
overlay (either kind) draws frame-synced markers on top: a vector with one entry per frame, each a list of (x, y[, id]) points in frame pixel space; id keeps a point's color/trail stable across frames, e.g. a tracked object's identity. Pair with playhead to react to the current frame.
frames = [density(t) for t in times] # heavy compute, once (cache it)
animate(frames; clim=:symmetric, x=r, y=r, title="ψ(t)", autoplay=true)
vidframes = [read(reader) for _ in 1:n] # Matrix{RGB{N0f8}} from VideoIO.jl
tracks = [[(x1,y1,1), (x2,y2,2)] for _ in 1:n] # per-frame (x,y,id) detections
animate(vidframes; kind=:image, overlay=tracks, fps=25, title="tracked beetles")KaimonSlate.ReportEngine.animate Method
animate(f, nframes; …) — generate frame i with f(i) (sugar for animate([f(i) for i in 1:n])).
KaimonSlate.ReportEngine.assign_bind! Method
Set a @bind control's value from the browser: coerce it against the widget, update the per-notebook registry (so a later re-run preserves it), and assign the global so readers see it. Returns the coerced value. Routed through the namespace's injected __slate_set_bind so the logic lives in exactly one place (widgets.jl).
KaimonSlate.ReportEngine.attach_gate_kernel Method
attach_gate_kernel(port, stream_port; project=".") -> GateKernelA kernel bound to an ALREADY-RUNNING SlateWorker reachable at 127.0.0.1:port (+ stream_port) — e.g. a worker on another machine forwarded here over an SSH tunnel (ssh -N -L port:localhost:port -L stream:localhost:stream host). prepare! CONNECTS instead of spawning: no local process, no env reconstruction — the worker owns its process + environment. The transport is unchanged (the hub always connects to 127.0.0.1:port), so the tunnel is transparent. Remote execution for a notebook is then: start the worker there, forward the two ports, hand the notebook this kernel.
KaimonSlate.ReportEngine.bind_owner Method
bind_owner(report, name) -> StringThe id of the cell that DECLARES @bind name, or "" if none does.
Cell code driving a control (set_bind(:name, value)) names only the VARIABLE — a notebook shouldn't have to know which of its own cells happens to hold a control in order to move it. The browser and the agent both already carry the cell id; this is how the third caller finds it.
KaimonSlate.ReportEngine.build_dependencies! Method
build_dependencies!(report) -> reportRe-infer bindings and compute each code cell's upstream deps (ids). A cell depends on the most recent prior writer of each name it reads, plus any earlier cells its needs= tag names (user-asserted effect edges). An :opaque cell depends on all prior code cells, and all later code cells depend on it (barrier).
KaimonSlate.ReportEngine.cancel_cells Method
cancel_cells(kernel, report, ids) -> IntBest-effort interrupt of the named cells' IN-FLIGHT evaluator tasks (superseded-edit preemption — see _preempt_superseded!). Never a correctness dependency: the src-hash version guard still discards a stale result on completion. In-process evals are synchronous with their caller — nothing to preempt — so the base method no-ops; the gate kernel forwards to its worker.
KaimonSlate.ReportEngine.complete Method
complete(kernel, report, code, pos) -> (; items, from, to)Completion candidates for code at byte offset pos, resolved WHERE the kernel's bindings live — so using'd packages and evaluated-cell bindings complete, not just Base. items is a Vector{Tuple{String,String}} of (text, kind); from/to are 0-based byte offsets of the replaced range. The in-process kernel completes locally.
KaimonSlate.ReportEngine.dependents_of Method
dependents_of(report, ids) -> Set{String}Transitive closure: ids plus every cell that (transitively) depends on one of them. This is the staleness blast radius of changing ids.
KaimonSlate.ReportEngine.echart Method
Build an interactive ECharts chart from an option dict.
KaimonSlate.ReportEngine.ensure_notebook_file! Method
ensure_notebook_file!(path) -> pathThe file a notebook is opened from, created if it isn't there yet — including the directories leading to it.
Opening a path that doesn't exist has always meant "make me this notebook", and every entry point (the slate.open tool, the CLI, the /api/open route) agreed on that for the FILE while failing on a missing parent. Callers reach for notebooks/<name>.jl in a project that has no notebooks/ yet, and refusing over the directory when we were about to create the file inside it anyway is a distinction without a difference to whoever is on the other end. One definition here so those three can't drift apart on what "open a new path" means.
KaimonSlate.ReportEngine.env_add_code Method
env_add_code(delta) -> StringPkg code that re-adds the notebook's OWN packages (its Slate.env footer) after a re-seed.
Seeding rebuilds a fork from its PARENT, which knows nothing about what the notebook added. So without this, any upstream Project.toml edit stales the fork, the rebuild re-seeds, and every package the notebook installed disappears — surfacing much later as "Package X not found" at the notebook's first using, with nothing to connect it to the upstream change.
Packages are added by name and uuid and left to resolve. The footer records a version too, but pinning it would fight the parent's fresh resolution, which is the thing that just changed.
KaimonSlate.ReportEngine.env_stale Method
env_stale(envdir, parent) -> BoolA forked env is STALE if the parent has changed since it was seeded (or it was never stamped) — the trigger to rebuild it before booting a worker. Always false for a parentless (detached) notebook.
KaimonSlate.ReportEngine.eval_capture Function
Evaluate source in the kernel and capture stdout + rich output → CellOutput. region/regions seed the task-local Slate execution context (see _build_slate_ctx); region="" ⇒ the main kernel.
KaimonSlate.ReportEngine.eval_report! Method
eval_report!(report; reset=false, kernel=InProcessKernel()) -> ReportEvaluate all code cells in document order through kernel. reset=true does a full rebuild in a fresh namespace first. (No dependency pruning here — that's eval_stale!; this runs every code cell.)
KaimonSlate.ReportEngine.eval_stale! Function
eval_stale!(report, kernel=InProcessKernel()) -> reportEvaluate only STALE code cells, in document order, through kernel. Unchanged (FRESH) cells keep their cached output — their effects already live in the kernel's namespace from the prior eval. (First run: all cells stale ⇒ full eval.)
KaimonSlate.ReportEngine.fetch_page Function
fetch_page(provider, ::PageRequest) -> PageResult — one page, sorted/filtered/paged.
KaimonSlate.ReportEngine.fetch_remote_worker_log Method
fetch_remote_worker_log(k; maxbytes) -> StringTail the SSH host's own worker-<port>.log (the remote Julia process's stdout/stderr: KaimonGate load, serve() banner, eval output, crashes). This is the factorio-side record — fetched over the same authenticated SSH channel we spawned it on, so it's visible locally in the browser worker-log.
KaimonSlate.ReportEngine.gate_available Method
True when running inside the Kaimon extension (gate client available).
KaimonSlate.ReportEngine.harvest_docs Method
harvest_docs(kernel, report, mod_names) -> Vector{Dict}Harvest {module, name, doc} for documented exported bindings of the named modules, resolved WHERE cells evaluate (so the modules must be using'd in the notebook). The gate kernel forwards to its worker, where the notebook's packages live.
KaimonSlate.ReportEngine.interpolate Method
interpolate(kernel, report, exprs) -> Vector{CellOutput}Capture each markdown in the kernel (rich output, like a mini code cell). The gate kernel forwards to its worker.
KaimonSlate.ReportEngine.is_code_kind Method
A cell whose source is Julia the engine evaluates. TOOL joins CODE here: everything about evaluation, dependencies and capture is identical, and only presentation and WHEN it runs differ.
KaimonSlate.ReportEngine.latex_symbol Method
latex_symbol(name) -> StringResolve a LaTeX/emoji completion command ("\alpha", "\:smile:") to its character; "" if unknown. A PARTIAL latex query (\alph) comes back from REPLCompletions as the NAME, not the symbol — so the UI displays the name (it must, to filter by what the user typed) but resolves the symbol via this to APPLY in one step (else accepting inserts the literal \alpha).
KaimonSlate.ReportEngine.list_remote_workers Method
list_remote_workers(host) -> Vector{Dict}Enumerate the workers Slate has spawned on host, each: port, alive (process running), lastActivity (unix mtime of its log = last computation), logBytes, state/stateSince (lifecycle sidecar: "attached"/"idle" + unix ts; "" for a pre-sidecar worker — advisory, a hub crash leaves a stale "attached"), manifest (raw JSON string — the browser parses it for who/what/when), and stats (the worker's latest 2s telemetry sample: cpu/rss/gc_ms/evals/memo_bytes/ts as raw JSON; "" for a pre-telemetry worker). Reads the on-host manifests over one ssh call. [] if unreachable/none.
KaimonSlate.ReportEngine.macroexpand_cells Method
macroexpand_cells(kernel, report, srcs) -> Dict{String,Tuple{Set{Symbol},Set{Symbol}}}Macro-expand each cell source (id => source) in the namespace where cells evaluate — recursively, NEVER evaluating — and ANALYZE the expansion there, returning id => (reads, writes). Cells whose expansion/analysis fails are omitted (the caller keeps its conservative static analysis). The gate kernel forwards to its worker, where the notebook's macros are actually defined; only name lists cross the wire (see macroexpand.jl).
KaimonSlate.ReportEngine.memo_pin! Method
memo_pin!(kernel, report, key::AbstractString, pin::Bool) -> nothingPin (pin=true) or release (pin=false) a memo entry against gc eviction — the locked cell tag's durability guarantee (§ set_cell_tags!). The in-process kernel has no durable memo store, so this is a no-op there.
KaimonSlate.ReportEngine.module_help Method
module_help(kernel, report, name) -> DictLive help lookup for name (a binding or module), resolved WHERE cells evaluate — {name, module, doc, kind, exports}. Powers the docs palette's ?Module drill-down (list a package's exports) + cross-reference links. The gate kernel forwards to its worker, where the notebook's packages live.
KaimonSlate.ReportEngine.module_help Method
module_help(where, name) -> DictResolve name in module where (the package must already be using'd / imported there) and return a help record: {name, module, doc, kind, exports}. kind is "module", "function", "type", "const", or "unknown". For a Module, exports lists its exported bindings as {name, kind} (sorted) for drill-down; empty otherwise. doc is the raw @doc text (markdown). Pure (Base.Docs + reflection only) so it loads into the dependency-light worker, exactly like harvest_module_docs.
A bare identifier that doesn't resolve is retried case-insensitively (exact case still wins), so regionplan finds RegionPlan — see _ci_resolve_name.
KaimonSlate.ReportEngine.page_columns Function
page_columns(provider) -> Vector{ColumnDef} — the provider's columns.
KaimonSlate.ReportEngine.parent_manifest Method
parent_manifest(parent) -> StringAbsolute path of the manifest that RESOLVES the project in directory parent, or "" when there is none. For a workspace member that is the workspace root's shared manifest; otherwise the manifest beside the project, honouring the versioned (Manifest-v1.12.toml) names and an explicit manifest = key — the same order Julia's loader uses.
KaimonSlate.ReportEngine.parse_report Method
parse_report(text; id="r", title="") -> ReportParse hybrid source into a Report. Two interchangeable conventions are accepted — liberal in, canonical out (serialize_report always emits the explicit #%% form):
Explicit percent cells: a
#%% [code|md] [id=…]header introduces a cell whose body runs verbatim until the next header.Literate-style (implicit): before any header, a run of comment lines becomes a markdown cell only when it reads as prose (a heading/list/rule marker, a blank line before the code, or no code after it); an ordinary comment that hugs the code below it stays an inline comment on that code cell. Bare code runs are code cells. See [
_append_implicit_cells!].
Pure-Literate files, pure-percent files, and Literate-then-percent mixes all parse. (Once a #%% header appears, subsequent cells should also use headers — implicit content after a header is taken as that explicit cell's verbatim body.)
KaimonSlate.ReportEngine.pending_macro_cells Method
Flagged cells whose macro bindings are still unrecovered (no cache entry, not marked tried).
KaimonSlate.ReportEngine.pkg_op Method
pkg_op(kernel, report, op, name) -> Dict{String,Any}Add (op="add") or remove (op="rm") a package in the kernel's active project — the notebook's own dependency environment. The gate kernel mutates its worker's project; the in-process kernel has no separate worker to fork an env in, so cells already run in the process's own active project — same "env IS the whole world" semantics as a GateKernel's detached notebook (server.jl's parent == "" case). target is accepted for API parity with GateKernel but has no separate object to select (no parent to add to instead). Returns {ok, message}.
KaimonSlate.ReportEngine.preflight_remote Method
preflight_remote(host; transport=:tunnel, on_step=nothing) -> DictTest + prime host: ssh reachability, Julia presence (+version), env provisioning, KaimonGate load, CURVE key (for :direct), then a real spawn → connect → round-trip eval → clean teardown. Returns Dict("host","transport","ok",steps=>[{name,status,detail,ms}…]). Idempotent and self-cleaning: it leaves the primed env behind (so the first real run is fast) but no worker or tunnel running. on_step(::PreflightStep), if given, is called as each step STARTS (status "run") and COMPLETES — so the SSE endpoint can stream progress live instead of blocking for the whole (minutes-long) run.
KaimonSlate.ReportEngine.prepare! Method
Ensure the kernel's namespace exists and is ready to evaluate into.
KaimonSlate.ReportEngine.prewarm_macros! Function
prewarm_macros!(report, kernel=InProcessKernel()) -> BoolPre-eval macro expansion (peer of prewarm_usings!): recover unknown-macro bindings BEFORE a run so the graph — and the memo keys derived from it — is precise from the first eval. Package macros (Base.@kwdef, @enum, DataFrames' @chain, …) expand here because prewarm_usings! already imported their modules; notebook-defined macros resolve post-drain in refine_macros!. Failures are NOT marked tried — the macro may get defined during the run. Returns true iff something newly resolved (deps rebuilt).
KaimonSlate.ReportEngine.prewarm_usings! Function
prewarm_usings!(report, kernel=InProcessKernel()) -> BoolPre-eval counterpart of refine_usings!: resolve bare-using exports BEFORE a run, so the dependency graph — and every memo key derived from its upstream closures — is computed in precise form from the very first eval of a session. Without this, a fresh session analysed using X as an :opaque barrier, keyed the first run's memo entries against that conservative graph, then flipped to the precise graph post-drain (refine_usings!) — so every cell below a using changed memo keys between the first and second run of each session and MISSED the durable cache exactly when it mattered most (cold open). Returns true iff a module was newly resolved (deps rebuilt).
KaimonSlate.ReportEngine.project_deps Method
project_deps(kernel, report) -> Vector{Dict}The notebook project's direct dependencies as {name, version} (for eager docs auto-indexing — everything reachable is worth indexing, so this is intentionally unfiltered). The gate kernel reads its worker's active project; the in-process kernel has no separate worker, so it reads the host's own active project — the same one pkg_op adds/removes into.
KaimonSlate.ReportEngine.project_file_in Method
The Project.toml/JuliaProject.toml in dir, or "".
KaimonSlate.ReportEngine.provision_remote! Method
provision_remote!(t::RemoteTarget, parent_project) -> nothingIdempotent. Ensure the host can run a SlateWorker: (1) rsync Slate's worker payload, (2) materialise a KaimonGate worker env (added from the registry) + Revise, instantiate, (3) rsync the notebook's parent project (Project.toml + /src) and instantiate it. Cheap on reruns (rsync only ships deltas; the env instantiate is skipped once .ready exists).
KaimonSlate.ReportEngine.pull_blob! Method
pull_blob!(host_ip, data_port, hash; server_key="", timeout_ms=20_000) -> IntPULL one content-addressed blob from a worker's store over the data channel ('G' chunks) into the LOCAL memo CAS — the reverse of push_memo_blobs!; how remote results flow back (region runner: a remote cell's writes that local cells read). Streams into a tmp file, sha-verifies against the address, atomic-renames — a truncated/corrupt transfer never lands. Dedup first: an already-present blob costs nothing. Returns bytes moved over the network (0 = deduped).
KaimonSlate.ReportEngine.push_blob! Method
push_blob!(host_ip, data_port, hash; server_key="", timeout_ms=20_000) -> IntShip ONE blob from the LOCAL CAS to a worker's store over its data channel (dedup-aware: the 'H' query makes an already-present blob cost one round-trip). The region runner's local→remote half; pull_blob! is the reverse. Returns bytes actually sent (0 = deduped).
KaimonSlate.ReportEngine.reap_remote_worker Method
reap_remote_worker(host, port) -> BoolExplicitly kill the worker on host:port and remove its script/log/manifest. Manual only — Slate never auto-reaps (a worker may hold results worth keeping).
REMOTE only: it reaches the worker over ssh and matches its worker-<port>.jl script, neither of which exists for a local worker (spawned inline with -e and owned by the hub as a process). Returns whether the kill reached something, so the caller can report the outcome rather than assume it.
KaimonSlate.ReportEngine.recorded_toolcall Method
recorded_toolcall(name, args, ok, text, seconds, at; handlers) -> ToolCallThe panel for a call that ALREADY happened, without dispatching it again.
An agent's call is observed after the fact, so there is nothing left to run: the outcome is handed in and only the tool's declared schema is looked up. What this buys is that a recorded call renders as the same panel a @tool cell does, rather than as a transcript of its reply, so it carries the handle, the parameter surface, and the follow-up the reply named. That is what lets a recorded call that started background work track it, instead of freezing on the sentence that started it.
KaimonSlate.ReportEngine.refine_macros! Function
refine_macros!(report, kernel=InProcessKernel()) -> BoolPost-drain macro expansion (peer of refine_usings!): by now every macro a cell could define or import has had its chance to exist, so expand the still-pending cells and mark failures tried (attempt-once per source; an edit to the cell — or to a macro-defining cell, see update_source! — clears the way for a retry).
Unlike refine_usings! (which only NARROWS), recovering a macro-hidden WRITE adds an edge. A PARALLEL drain was scheduled without it, so a reader may have raced its producer (errored on the not-yet-defined name, or silently consumed the previous run's value) — with restale_racers = true (the parallel server path) everything downstream of a newly-recovered writer is restaled once ("staleness never under-invalidates") and the caller's runner re-arms. A serial drain executes in document order — a valid topological order even without the edge — so the default skips the restale. Returns true iff something newly resolved (deps rebuilt).
KaimonSlate.ReportEngine.refine_usings! Function
refine_usings!(report, kernel=InProcessKernel()) -> BoolPost-eval progressive precision: for each code cell still an :opaque barrier because of a bare using X that has now SUCCESSFULLY run, resolve X's exports and cache them, then rebuild the dependency graph so the barrier becomes a precise import. No cell is restaled — this runs after a drain, and narrowing a cell's dependents can only shrink the blast radius (see [build_dependencies!]). Idempotent: each module path is attempted at most once per session. Returns true iff a module was newly resolved (and deps were rebuilt).
KaimonSlate.ReportEngine.registry_add Method
registry_add(kernel, report, url) -> Dict{String,Any}Install a package registry into the depot the kernel's code runs in. Unlike pkg_op this is depot-GLOBAL — it affects every project on that machine — which is why it's a separate call with its own consent in the UI rather than something an install quietly does.
It has to run kernel-side rather than in the hub: a notebook on a remote region resolves against the REMOTE depot, so adding the registry in the hub's depot would leave the install still failing. Adding a registry that's already present is a no-op, not an error. Returns {ok, message}.
KaimonSlate.ReportEngine.remote_log Method
Tail the durable remote-orchestration log (last maxbytes).
KaimonSlate.ReportEngine.report_module Method
Get (creating if needed) the report's execution namespace.
KaimonSlate.ReportEngine.reset! Method
Discard the kernel's namespace (full rebuild); cells are marked stale by the caller.
KaimonSlate.ReportEngine.reset_module! Method
reset_module!(report) -> ModuleDiscard the report's namespace and mark every cell stale — the basis of a full rebuild (ground truth, §6). Returns the fresh module.
KaimonSlate.ReportEngine.resolve_macros! Method
resolve_macros!(report, kernel, cells; mark_tried=false) -> BoolRound-trip cells' sources to the kernel for macro expansion (ONE batched call), re-analyze each expanded form, and cache the recovered (reads, writes). Returns true iff anything newly resolved (the caller rebuilds the graph). With mark_tried, a cell whose expansion failed is recorded so it isn't round-tripped again (post-drain semantics — its macros had their chance to be defined); the pre-run pass leaves failures unmarked so the post-drain pass can retry them.
KaimonSlate.ReportEngine.revise_apply! Method
Apply pending parent-/src revisions in the worker (Revise) → the changed top-level def-names.
KaimonSlate.ReportEngine.run_cleanups! Method
Fire the given cells' cleanup callbacks in the report's in-process namespace (deleted-cell teardown).
KaimonSlate.ReportEngine.run_cleanups! Method
run_cleanups!(kernel, report, ids)Run the slate_on_cleanup callbacks that the given cells registered, in the namespace where they live (this kernel's) — used to tear down a DELETED cell's live per-cell resources (a Bonito Session). The gate kernel dispatches to its worker; in-process fires directly. A no-op for a base kernel / unknown ids.
KaimonSlate.ReportEngine.runs_automatically Method
Kinds excluded from automatic runs. A tool call reaches outside the notebook, so opening a document, or recomputing a stale neighbour, must never fire one on the reader's behalf.
KaimonSlate.ReportEngine.seed_env_project! Method
seed_env_project!(envdir, parent) -> parent_pkg_nameWrite a forked env's Project.toml (the parent's [deps]+[compat]+[sources], with dev paths made absolute, plus anything it inherits as a workspace member) and copy the manifest that resolves the parent as the resolution baseline (path deps absolutised). PURE files — the caller does the Pkg.develop(parent) + Pkg.instantiate(). Returns the parent package name ("" when the parent isn't a package).
KaimonSlate.ReportEngine.series Method
series(kind, args...; name=nothing, kwargs...) -> EChartSeriesBuild one series for the composable echart(series(…), series(…); …) form — combine different kinds and axes in a single chart. kind is an ECharts series type:
:line/:bar/:area(x, y)— string x → category axis, numeric x → value axis:scatter(x, y):pie(labels, values):heatmap(z::Matrix)or(xlabels, ylabels, z)— adds category axes + a visualMap:candlestick(dates, ohlc)—ohlc[i] = [open, close, low, high]:radar(indicators, values)—indicators = ["Sales" => 6500, …]; values a vector, or["Allocated" => […], "Actual" => […]]for several rings:boxplot(categories, data)— eachdata[i]is[min,Q1,med,Q3,max]or raw samples:sankey(links)or(nodes, links)— flows; each link(source, target, value)orsrc => tgt => val:graph(edges)or(nodes, edges)— a network; each edge(source, target)orsrc => tgt(force layout):treemap/:sunburst(tree)— a hierarchy:name => value(leaf),name => [children…](branch), or NamedTuple/Dict nodes; pass one root or a vector of roots:lines(from, to)or(flows)— geo trajectories/flows; coords(lon, lat); bindscoordinateSystem="geo"(passgeo=(map="world",…)+registerMap):calendar(dates, values)— a calendar heatmap; brings the calendar component + a visualMap
Any other kind falls back to data = args[1]. name= labels the series for the legend; every extra kwarg (smooth, stack, symbolSize, yAxisIndex, areaStyle, markLine, lineStyle, …) splices into the series option verbatim.
echart(series(:bar, x, a; name="obs", stack="t"),
series(:line, x, b; name="fit", smooth=true, yAxisIndex=1); legend=true,
yAxis=[(name="obs",), (name="fit", type=:log)])KaimonSlate.ReportEngine.set_bind_value! Function
Convenience: set the sole bind of a single-control cell (no-op unless exactly one).
KaimonSlate.ReportEngine.set_bind_value! Function
set_bind_value!(report, cell, name, value, kernel=InProcessKernel()) -> cellApply a browser value change for bound variable name (one of cell.binds): route it through the kernel (assign_bind! coerces against the widget, updates the per-notebook registry, and assigns the global), then mirror the coerced value into the host-side BindSpec. No-op if the cell has no such bind.
KaimonSlate.ReportEngine.shutdown! Method
Shut the kernel down and clear gate state. A LOCAL worker is killed (clean exit request + SIGTERM/SIGKILL backstop). A spawned-remote worker is DETACHED by default — tunnel/sync closed, process left running warm (namespace + packages + memo store) with its state sidecar flipped to idle, so reopening the notebook reattaches instantly. kill_remote=true is for the paths where a surviving worker would be wrong: an explicit restart (reattach would make it a no-op), the preflight probe, and reap. An ATTACHED worker (k.remote) is never ours to kill either way.
KaimonSlate.ReportEngine.shutdown! Method
Release a kernel's resources (kill a local gate worker; detach a spawned-remote one unless kill_remote=true — see the GateKernel method). No-op for in-process.
KaimonSlate.ReportEngine.slate_completions Method
slate_completions(mod, code, pos) -> (; items, from, to)REPLCompletions against mod at byte offset pos. items is a Vector{Tuple{String,String}} of (text, kind); from/to are 0-based byte offsets of the range the completion replaces (CodeMirror-ready). Returns a NamedTuple so it rides the gate wire to the server unchanged.
KaimonSlate.ReportEngine.slate_fingerprint Method
slate_fingerprint(xs...) -> StringA canonical, session-stable content hash (SHA-256 hex) of the given value(s) — isequal-style semantics: Dicts/Sets are order-independent, NaN ≡ NaN, integer widths widen, missing and nothing are distinct. The robust way to assert a restored/recomputed/transferred value is REALLY the same — unlike hash, which leaks Dict order and session state. slate_fingerprint(df, params) in a cell gives one comparable line across runs, sessions, and worker restarts.
KaimonSlate.ReportEngine.slate_matrix Method
slate_matrix(M::AbstractMatrix; kind=:auto, rows=nothing, cols=nothing, max_cells=200*200,
downsample=true, colors=["transparent","#569cd6","#ffd700"],
blockrows=nothing, blockcols=nothing, digits=3)Render M as whichever form suits its size and structure: an exact KaTeX bmatrix (small), symbolic dotted notation (large + a recognized banded type — Diagonal/Tridiagonal/ SymTridiagonal/Bidiagonal/triangular), or a downsampled ECharts heatmap (large + anything else, including sparse). Any bare AbstractMatrix returned from a cell renders this way automatically; call slate_matrix explicitly to override the choice or its defaults.
kind— force:katex/:dotted/:heatmapinstead of auto-picking.rows/cols— crop to a sub-region BEFORE rendering (e.g. one tile of a periodic or block-repeating large matrix), as ranges.max_cells— the heatmap's downsample target (default 200×200 cells).downsample=falseforces full resolution — only safe for a matrix that already fits (pair withrows/colsto crop a large one down first; forcing full resolution on a large matrix directly can be extremely slow/memory-heavy).colors— the heatmap'svisualMapcolor ramp, low → high.blockrows/blockcols— block sizes for divider lines in the KaTeX form (e.g.[2,2]for a 4×4 matrix drawn as four 2×2 blocks).digits— rounding for the KaTeX forms.
KaimonSlate.ReportEngine.slate_memo_entries Method
slate_memo_entries(; name = "") -> Vector{NamedTuple}The durable memo store's entries, newest first — one row per cached cell result: (; key, names, bytes, blobs, created) where names are the globals the entry restores and blobs their content hashes (shared hash across rows = deduped storage). name = "x" filters to entries carrying a binding named x. Return it from a cell (or wrap in slate_table) to see exactly what a cold open will restore.
KaimonSlate.ReportEngine.slate_memo_stats Method
slate_memo_stats() -> (; manifests, blobs, bytes, root)Shape of the durable memo store backing this notebook's cache tags: entry count, unique content blobs (identical values dedup to one), total on-disk bytes, and the store root. In-process kernels have no durable store — all zeros.
KaimonSlate.ReportEngine.slate_query Method
slate_query(conn, sql; page_size=50) -> SlatePagedTableBrowse the result of sql (run against DBInterface connection conn, e.g. a DuckDB.DB or SQLite.DB) as a server-paged table: sorting, global search, and paging are pushed into SQL, so the browser only ever holds one page. The whole result set is never materialized.
KaimonSlate.ReportEngine.slate_table Method
slate_table(data; format, align, coltype, viz, default_format, paged, page_size, export_rows) -> SlateTable
slate_table(columns, rows; format, align, coltype, viz, default_format, export_rows) -> SlateTableBuild an interactive, sortable, filterable, paged table. RETURN it from a cell to render it — a bare DataFrame / Tables.jl source already auto-renders, so you only call slate_table explicitly to pass options. data may be a DataFrame / any Tables.jl source, a Vector of NamedTuple rows, or a Dict/NamedTuple of equal-length column vectors. The two-argument form takes explicit columns (names) plus rows (a vector of row vectors/tuples, or an AbstractMatrix). Cells are reduced to JSON-safe scalars; numbers stay numeric so the browser sorts them numerically.
Each column's physical type (:int/:float/:bool/:date/:string) and default alignment are inferred. The overlay options each take a NamedTuple/Dict keyed by column NAME — a ONE-entry NamedTuple needs its trailing comma: (Revenue = :currency,):
• format — per-column display formatting. A value is a preset Symbol, or a NamedTuple/Dict naming a kind plus overrides (digits, sep, prefix, suffix). Presets: :currency ($, 2 dp, thousands-grouped) · :percent (1 dp) · :integer (grouped) · :fixed (2 dp) · :scientific (3 sig figs) · :bytes (KB/MB/GB…). • align — :left / :right / :center, overriding the type-inferred default. • coltype — override the inferred physical type (e.g. force an id column to :string). • viz — an in-cell visualization for a NUMERIC column, scaled over its min→max: :bar (a proportional bar behind the value) or :heat (a background shaded by magnitude). • default_format — one format spec (same DSL as a format value) applied to EVERY numeric column that doesn't have an explicit entry in format. Handy for a blanket default_format = :integer (round-to-nearest-int) instead of listing every column.
Example combining several:
slate_table(df; format = (Revenue = :currency, Margin = (kind = :percent, digits = 1)),
align = (Product = :left,),
viz = (Revenue = :bar, Margin = :heat))paged = true builds a SERVER-paged table: the provider stays where cells evaluate and the browser fetches one page_size-row page at a time, so the full result set never crosses the wire (use it for large data — slate_query(conn, sql) does the same for a SQL source). export_rows = n caps the rows shown in FIXED exports (PDF / markdown / static HTML) to the first n (with a "showing n of N" note); the live table stays fully paginated.
KaimonSlate.ReportEngine.slate_theme Method
slate_theme(; theme="") -> Makie.ThemeThe shared Slate look as a Makie Theme, built from the same brand palette the interactive ECharts figures use — transparent background, the Slate series colours, palette-toned grid/axes, and a default figure size that matches the ECharts cell height. Needs Makie loaded (using CairoMakie).
theme selects a palette by name ("midnight", "nord", "daylight", …); the default "" follows the ACTIVE UI theme, so a rendered plot matches whatever Slate theme is on. Compose extra styling on top with Makie's own update_theme!/set_theme!(base, overlay).
using CairoMakie
set_theme!(slate_theme()) # follow the active UI theme
set_theme!(slate_theme(theme="nord")) # or pin a specific palette
# or just: use_slate_theme!()KaimonSlate.ReportEngine.slate_tool Method
slate_tool(name; kwargs...) -> ToolCallCall a gate tool registered in this session by name, and return the call as a value.
The tools are the same ones an agent sees over MCP, running in this process, so a notebook and an agent driving it act on one session rather than two copies of it. Arguments are coerced against the handler's signature by the gate's own dispatcher, so a wrong name or an unconvertible value is a clear error rather than a MethodError.
slate_tool("start_job"; target = "Main.NB.Widget", size = 4)@tool is the same thing in call syntax. slate_tools() lists what is available.
KaimonSlate.ReportEngine.slate_tools Method
slate_tools(; filter = "") -> tableEvery gate tool this session exposes, with its parameter count and first documentation line. These are the tools an agent can call; filter keeps only names containing that substring.
KaimonSlate.ReportEngine.source_text Method
Alias kept for callers that think in terms of a cell's raw text.
KaimonSlate.ReportEngine.spawn_and_connect_remote! Method
spawn_and_connect_remote!(k, t::RemoteTarget, parent_project) -> (conn, tunnel|nothing)Provision (idempotent) + start a SlateWorker on the host + connect the hub's kernel to it, CURVE-pinned (direct) or over a supervised SSH tunnel. k is the GateKernel (its .project is the REMOTE project path; .port/.stream_port are set here). Returns the REPLConnection and the Tunnel (or nothing). Also starts the continuous /src sync.
KaimonSlate.ReportEngine.stamp_env! Method
Record the parent fingerprint envdir was seeded from (for later env_stale checks).
KaimonSlate.ReportEngine.standalone! Function
standalone!(m::Module = @__MODULE__; dir = nothing) -> ModuleInject the notebook-namespace contract into m so a Slate .jl runs as plain Julia (julia notebook.jl / include), Pluto-style. This is the single lever that makes a notebook runnable outside the Slate server: the same _populate_notebook_ns! contract is installed, but the live-only features degrade to no-ops.
@bind x W(…)→x = W's default (empty registry, no browser wiring) — the real bind path, so it works with no special-casing.echart/slate_table/slate_querybuild their display objects as usual (pure constructors); they render viashow/MIME if the run is display-capable, else are inert.slate_refresh/slate_progress/slate_emit/ reactive fires → no-op.@asset/readfile/datadir/@sfileresolve againstdir(the notebook file's directory), defaulting topwd().
Idempotent: a second call — or the Slate engine re-populating the same module — is a no-op (guarded on the __slate_standalone marker), so the emitted preamble never double-injects.
KaimonSlate.ReportEngine.table_page Method
table_page(kernel, report, table_id, request) -> (rows, total)Fetch one page of a server-paged table (a slate_table(…; paged=true) / slate_query result), routing to the provider registered where cells eval. The request is the frontend's JSON body (page / page_size / sort_col / sort_desc / search). In-process providers live here; the gate kernel forwards to its worker.
KaimonSlate.ReportEngine.tool_handle Method
tool_handle(tc::ToolCall) -> Union{String, Nothing}The identifier a tool handed back, recovered from the follow-up call its reply names.
Work that runs in the background returns a handle rather than an outcome, and states the call that reads it. This picks that handle out, so a cell can thread it onward instead of copying it by eye:
job = @tool start_job(size = 12)
@tool job_status(job_id = tool_handle(job))nothing when the reply names no follow-up carrying an id.
KaimonSlate.ReportEngine.toolcall_source Method
Render one recorded call as the source of a TOOL cell: the @tool form an author would have written, so the cell is re-runnable rather than a transcript of something that happened.
KaimonSlate.ReportEngine.update_source! Method
update_source!(report, new_source) -> reportReparse new_source, reconcile cells by id (carrying over cached output for cells whose source is unchanged), rebuild the graph, and mark changed cells + their transitive dependents STALE. Removed cells invalidate their former readers. Does not evaluate — call eval_stale! next.
KaimonSlate.ReportEngine.use_slate_theme! Method
use_slate_theme!(; theme="") -> nothingApply the slate_theme globally (Makie.set_theme!) so every Makie figure in the notebook matches the interactive ECharts look. theme names a palette; "" follows the active UI theme. Needs Makie loaded. Call it once in a setup cell (re-run it after switching the UI theme to re-render).
KaimonSlate.ReportEngine.watch_session_tools! Method
Start publishing every session tool call an agent makes, for the hub to record as a cell.
Registers a gate OBSERVER rather than wrapping handlers. Wrapping was the obvious approach and is wrong: a handler's signature IS its MCP schema (_reflect_tool reads it), so a wrapper with (args...; kwargs...) silently strips a tool's parameters and the agent can no longer call it. Observing leaves the tool untouched.
Idempotent — safe to call after every cell, which is what catches the tools a package registers when a cell first loads it.
handlers is a thunk returning the notebook's JS→Julia handler registry (the namespace is replaced on reset, so it cannot be captured once), used to make the recorded panel callable.
KaimonSlate.ReportEngine.workspace_chain Method
workspace_chain(projectfile) -> Vector{String}The workspace roots above projectfile, nearest first (empty when it isn't a member). Nested workspaces are followed, so a member's inherited [sources]/[compat] can come from any level.
KaimonSlate.ReportEngine.workspace_parent Method
The ancestor project file whose [workspace] projects lists projectfile's dir, or "".
Report rendering
Turning evaluated cells into HTML/markdown output.
KaimonSlate.ReportRender Module
ReportRenderThe renderer half of the engine/renderer split (§15.1): turns an evaluated Report into a self-contained HTML artifact. Runs CLI-side, where the heavier template deps already live (OteraEngine, CommonMark). It only consumes the engine's Report/Cell data — no live module needed.
Escaping is correct by construction: one OteraEngine template with autoescape=true HTML-escapes all code/stdout/value text automatically; CommonMark-rendered markdown (already safe HTML) is injected via the |> safe filter. No hand-escaping (§8).
KaimonSlate.ReportRender.markdown_html Method
Markdown source → HTML (live notebook + md cells). interps are the captured outputs of the cell's blocks, spliced in document order.
KaimonSlate.ReportRender.output_html Method
output_html(cell) -> StringThe output fragment for a code cell (stdout / value / rich display / error), escaped and embedded. Used by the live notebook server to update a cell in place.
KaimonSlate.ReportRender.render_html Method
render_html(report) -> StringRender an (already-evaluated) Report to a self-contained HTML string.
KaimonSlate.ReportRender.render_report_file Method
render_report_file(path; out=..., title="", reset=false) -> out_pathEnd-to-end convenience: read a hybrid .jl, parse → evaluate → render → write HTML. Returns the output path.