cells
DaisyUI + app setup
CDN headers for DaisyUI + Tailwind, plus KatexMarkdownJS() (renders .marked elements as markdown + KaTeX) and the command-mode hotkey listener.
Authentication
A ?token= query param (printed at startup) or the /login form gates every route except health-check/login itself – see _check_auth, the Beforeware wired into app below. Every code cell already runs with this process’s own OS permissions, so an unauthenticated boopiter listening on a shared network is equivalent to an open shell; boopiter launch --no_auth opts out explicitly for a fully trusted network.
login
def login(
token:str=None, session:NoneType=None
):*Token-gate entry point. GET (no token field submitted) shows the sign-in form; POST checks it against BOOPITER_TOKEN and, on match, stamps the session so _check_auth lets every other route through. The other way in is a ?token= query param on any URL – what the server prints at startup – this form is the fallback for typing the token in by hand.*
Code execution
A single IPython shell backs every code cell (like the lesson’s ex), with errors returned as text instead of raised.
run_code_poll
def run_code_poll(
id:int, poll_n:int=0
)->fastcore.xml.FT | tuple:*Poll a running code cell’s execution. While still running, returns just the small output div (re-triggering itself, with an escalating delay – see _POLL_SCHEDULE) so the code block above it never flickers. Once done, replaces the whole cell out-of-band – the primary target (#run-out-N) is about to be destroyed along with it, so the primary response body is empty.*
Cell + Notebook model
A Cell carries its type, source, optional output, and a visible flag (the eye toggle — whether the LLM sees it). Notebook is the in-memory store of cells, the composer’s selected type, and the currently selected cell (for hotkeys).
pending_code_cell
def pending_code_cell(
c:Cell, text:str='', scroll:bool=False
)->FT:*Placeholder for a code cell whose execution is still running in the background: a static code view plus a self-polling output area (_run_output_div) that swaps itself out for the real render_cell() once done. scroll=True (set only by Run All) tags the cell div so the client scrolls it into view as it starts – see boopScrollRunAll() in edit.js.*
run_code_cell
def run_code_cell(
c:Cell, scroll:bool=False
)->FT:Tell one code cell to run: stamp it with the current time, then kick off its streaming background execution. The single shared entry point for the play button, Shift-Enter, and Run All (which passes scroll=True so each cell scrolls into view as it starts). A solo run (scroll=False) cancels any in-flight or leftover Run All, so it can never chain into unrelated cells.
LLM Interaction
The whole point of the visibility toggle: context is only the visible cells. The stub proves the plumbing by reporting what it can see; swap stub_reply for a real model call later.
pending_prompt_cell
def pending_prompt_cell(
prompt_id:int, text:str='', oob_swap:str=None
)->FT:*Placeholder shown while a Prompt’s LLM reply streams in the background: a pulsing ‘Tricky…’ indicator plus whatever text has accumulated so far, shown as plain preformatted text (not markdown-rendered – mid-stream markdown is often invalid, e.g. an unclosed code fence; only the finished reply gets the full ‘.marked’ treatment). hx-trigger=load polls run_prompt_poll() every 300ms until the reply is complete. oob_swap, if given, delivers this placeholder out-of-band (see _oob()) instead of being the response’s main swap target.*
run_prompt_poll
def run_prompt_poll(
id:int
)->fastcore.xml.FT | str:Poll a streaming Prompt reply – returns the current partial text while still running (re-triggering itself), or finalizes into the real Assistant cell once done.
add_tool
def add_tool(
fn:callable
)->callable:Register fn as a tool the LLM can call on future Prompt-cell runs. Also usable as a decorator: @add_tool.
llm_context
def llm_context(
nb:Notebook, cur_id:int | None=None
)->str:*Exactly what a real model would receive: the visible cells up through cur_id (default: all), in order, with each code cell’s outputs appended (via _output_text) so ‘run this, then ask about the result’ actually works. The cur_id cell itself is always included even if toggled invisible – it’s the question being asked, and dropping it would silently send a context with no prompt at the end.*
stub_reply
def stub_reply(
nb:Notebook, prompt:str
)->str:Fake/placeholder LLM reply used when no model is available (lets you still test the GUI).
ensure_models
def ensure_models()->None:Populate nb.models (info dicts, see get_model_list()) plus default picks for both models, tolerating an unreachable local LLM server. Run once at startup; refresh_models() takes over from there, so an Ollama that wasn’t running yet when boopiter started is picked up on the next brain-menu hover rather than needing a server restart.
Rendering
Each type gets a colored left border (matching the SolveIt screenshot: raw=yellow, code=blue, note=green, prompt/assistant=red). The selected cell gets a ring; hidden-from-LLM cells are dimmed. Note cells render as markdown via the .marked class (KaTeX, images, HTML).
Icon
def Icon(
name:str, cls:str='size-4', filled:bool=False
)->FT:*A heroicons SVG by name (see ICONS), inlined so stroke='currentColor' (and, if filled, fill='currentColor' too) matches the button’s text color. Thin wrapper over _svg_icon().*
IconBtn
def IconBtn(
name:str, title:str, **kw
)->FT:A small ghost-style button showing heroicon name, with a hover tooltip of title.
cell_toolbar
def cell_toolbar(
c:Cell
)->FT:The row of icon buttons (copy, export toggle, visibility, run, move, delete) shown in a cell’s header.
type_dropdown
def type_dropdown(
c:Cell
)->FT:Click the cell-type word to switch it (code/note/prompt/raw). Scoped to just this cell.
cell_header
def cell_header(
c:Cell, running:bool=False
)->FT:The top row of a cell: type dropdown, id/timestamp, and the toolbar. running=True (used by pending_code_cell() while a background execution is still in flight) adds a pulsing ‘Running…’ indicator that disappears once the cell finishes, whether it succeeded or errored. flex-wrap lets the toolbar drop to its own line on narrow (mobile) viewports instead of squeezing/overlapping.
cell_body
def cell_body(
c:Cell
)->FT:block above the reply.*
render_output_blocks
def render_output_blocks(
blocks:list
)->list:for stream/error text.*
code_view
def code_view(
c:Cell
)->FT:Static, syntax-highlighted (no live CodeMirror) view of a code cell – click to load the real editor. Keeping non-focused cells static is what makes theme switches etc. fast on notebooks with many code cells.
code_editor
def code_editor(
c:Cell
)->FT:The live CodeMirror editor for a code cell – only rendered for the cell currently being edited. Shift/Ctrl/Cmd+Enter or the play button runs. c.source never contains the ‘#| export’ pragma – see Cell.export / the bookmark toggle in cell_toolbar.
render_cell
def render_cell(
c:Cell, oob:NoneType=None
)->FT:Every cell type renders statically and opens its editor on click; only the actively-edited cell gets a live widget (CodeMirror for code, a plain textarea otherwise). oob is forwarded to _cell_outer – see there.
render_cell_edit
def render_cell_edit(
c:Cell
)->FT:Inline editor. Code cells use CodeMirror (Python highlight, no wrap); notes/raw use a textarea. Shift/Ctrl/Cmd+Enter saves.
render_nb
def render_nb()->FT:Render every cell in the notebook, in order, inside the #notebook container div.
Composer
The bottom bar: type tabs, a textarea, Submit. Picking a tab sets the type server-side; Submit creates the cell (running it, if code; spawning an Assistant reply, if prompt).
composer
def composer(
draft:str='', oob:bool=False
)->FT:The bottom-of-page input bar: type tabs, a source textarea, and a Boop (submit) button.
render_app
def render_app(
draft:str=''
)->FT:The whole notebook view: all cells plus the composer, wrapped in one container div.
Routes
Composer/toolbar routes plus the command-mode routes driven by hotkeys (select, select_delta, insert, del_selected, settype_selected).
theme_swap
def theme_swap()->FT:DaisyUI sun/moon swap; drives boopApplyTheme (default dark).
fname_display
def fname_display()->FT:The clickable filename shown in the top bar; click to rename.
rename_form
def rename_form()->FT:Swap the filename display for a text input, focused and pre-selected, to rename the notebook.
set_standard_model
def set_standard_model(
model:str
)->FT:*Change the Standard-model pick in the brain menu, then re-render the menu so the new pick’s checkmark shows (see _model_select). Only affects Prompt answers directly if the reasoning toggle is currently off – see nb.active_model().*
set_reasoning_model
def set_reasoning_model(
model:str
)->FT:*Change the Reasoning-model pick in the brain menu (restricted there to ‘thinking’-capable models), then re-render the menu so the new pick’s checkmark shows (see _model_select). Only affects Prompt answers directly if the reasoning toggle is currently on – see nb.active_model().*
toggle_reasoning
def toggle_reasoning()->FT:The brain-icon click: flip whether the reasoning or standard model answers Prompt cells. Returns the whole re-rendered brain_menu(), since the button itself needs to pick up its new highlighted state.
refresh_models
def refresh_models():Re-list the available models and re-render the brain menu if anything actually changed – wired to mouseenter on the menu itself (see brain_menu()), so starting Ollama after* boopiter no longer means restarting the server to see its models; hovering the brain icon is enough. Returns 204 No Content whenever nothing changed, which tells htmx to swap nothing at all: re-rendering an open dropdown on every hover would make the menu visibly flicker, and re-rendering it while the pointer is inside it risks disturbing the CSS :hover state keeping it open. Cheap enough to run on hover – ~8ms for the /api/tags listing, with the expensive per-model capability probes served from cache (see _ollama_caps) – and it runs alongside the CSS-driven dropdown rather than gating it, so the menu opens instantly either way. An unreachable Ollama does NOT clear the list (that’s what strict=True distinguishes: ‘Ollama is down’ vs ‘Ollama has no models’); it flips nb.ollama_ok so the existing entries render greyed and unclickable, keeping your current pick intact through a restart or a network hiccup. Picks are otherwise repaired, not reset (see _default_picks): a model that has genuinely vanished gets replaced, one you chose is left alone.*
set_keep_alive
def set_keep_alive(
minutes:int=None
)->str:Persist a new keep-alive from the spinner. Clamped at -1, the lowest value Ollama gives a distinct meaning: any negative keep_alive means ‘keep this model loaded indefinitely’, so -2 and -1 do the same thing and there’s no reason to let the box go lower. Takes effect on the next model call; nothing to re-render, hence hx-swap=none.