# cells


<div class="boopcell boop-raw">

</div>

<div class="boopcell boop-note">

<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

</div>

<div class="boopcell boop-note">

## DaisyUI + app setup

CDN headers for DaisyUI + Tailwind, plus `KatexMarkdownJS()` (renders
`.marked` elements as markdown + KaTeX) and the command-mode hotkey
listener.

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L51"
target="_blank" style="float:right; font-size:smaller">source</a>

### read_file_content

``` python
def read_file_content(
    file_path
):
```

*Call self as a function.*

</div>

<div class="boopcell boop-note">

## Authentication

A `?token=` query param (printed at startup) or the `/login` form gates
every route except health-check/login itself – see
[`_check_auth`](https://drscotthawley.github.io/boopiter/cells.html#_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.

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L160"
target="_blank" style="float:right; font-size:smaller">source</a>

### login

``` python
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.\*

</div>

<div class="boopcell boop-note">

## Code execution

A single IPython shell backs every code cell (like the lesson’s `ex`),
with errors returned as text instead of raised.

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L192"
target="_blank" style="float:right; font-size:smaller">source</a>

### run_code_poll

``` python
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.\*

</div>

<div class="boopcell boop-note">

## Cell + Notebook model

A [`Cell`](https://drscotthawley.github.io/boopiter/notebook.html#cell)
carries its type, source, optional output, and a `visible` flag (the eye
toggle — whether the LLM sees it).
[`Notebook`](https://drscotthawley.github.io/boopiter/notebook.html#notebook)
is the in-memory store of cells, the composer’s selected type, and the
currently `selected` cell (for hotkeys).

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L217"
target="_blank" style="float:right; font-size:smaller">source</a>

### pending_code_cell

``` python
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.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L236"
target="_blank" style="float:right; font-size:smaller">source</a>

### run_code_cell

``` python
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.*

</div>

<div class="boopcell boop-note">

## 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`](https://drscotthawley.github.io/boopiter/cells.html#stub_reply)
for a real model call later.

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L302"
target="_blank" style="float:right; font-size:smaller">source</a>

### pending_prompt_cell

``` python
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.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L347"
target="_blank" style="float:right; font-size:smaller">source</a>

### run_prompt_poll

``` python
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.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L361"
target="_blank" style="float:right; font-size:smaller">source</a>

### add_tool

``` python
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`.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L385"
target="_blank" style="float:right; font-size:smaller">source</a>

### llm_context

``` python
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.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L402"
target="_blank" style="float:right; font-size:smaller">source</a>

### stub_reply

``` python
def stub_reply(
    nb:Notebook, prompt:str
)->str:
```

*Fake/placeholder LLM reply used when no model is available (lets you
still test the GUI).*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L421"
target="_blank" style="float:right; font-size:smaller">source</a>

### ensure_models

``` python
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.*

</div>

<div class="boopcell boop-note">

## 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).

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L490"
target="_blank" style="float:right; font-size:smaller">source</a>

### Icon

``` python
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().\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L495"
target="_blank" style="float:right; font-size:smaller">source</a>

### IconBtn

``` python
def IconBtn(
    name:str, title:str, **kw
)->FT:
```

*A small ghost-style button showing heroicon `name`, with a hover
tooltip of `title`.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L501"
target="_blank" style="float:right; font-size:smaller">source</a>

### cell_toolbar

``` python
def cell_toolbar(
    c:Cell
)->FT:
```

*The row of icon buttons (copy, export toggle, visibility, run, move,
delete) shown in a cell’s header.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L556"
target="_blank" style="float:right; font-size:smaller">source</a>

### type_dropdown

``` python
def type_dropdown(
    c:Cell
)->FT:
```

*Click the cell-type word to switch it (code/note/prompt/raw). Scoped to
just this cell.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L571"
target="_blank" style="float:right; font-size:smaller">source</a>

### cell_header

``` python
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.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L587"
target="_blank" style="float:right; font-size:smaller">source</a>

### cell_body

``` python
def cell_body(
    c:Cell
)->FT:
```

\*Note/prompt/assistant render as markdown; raw is bare text. Code cells
never reach here – render_cell() routes them to
code_view()/code_editor(). An Assistant cell with call metadata
(Cell.details) shows it in a collapsed
<details>

block above the reply.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L611"
target="_blank" style="float:right; font-size:smaller">source</a>

### render_output_blocks

``` python
def render_output_blocks(
    blocks:list
)->list:
```

\*Render each of a code cell’s output blocks (see Cell.output /
run_code()) to its appropriate FT: an <img> for images, raw markup for
HTML/SVG, client-side-rendered markdown for text/markdown (same
‘.marked’ pipeline as note cells), pretty-printed for JSON, and a plain
(ANSI-aware)
<pre>

for stream/error text.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L632"
target="_blank" style="float:right; font-size:smaller">source</a>

### code_view

``` python
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.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L644"
target="_blank" style="float:right; font-size:smaller">source</a>

### code_editor

``` python
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.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L657"
target="_blank" style="float:right; font-size:smaller">source</a>

### render_cell

``` python
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`](https://drscotthawley.github.io/boopiter/cells.html#_cell_outer)
– see there.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L673"
target="_blank" style="float:right; font-size:smaller">source</a>

### render_cell_edit

``` python
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.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L691"
target="_blank" style="float:right; font-size:smaller">source</a>

### render_nb

``` python
def render_nb()->FT:
```

*Render every cell in the notebook, in order, inside the \#notebook
container div.*

</div>

<div class="boopcell boop-note">

## 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).

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L696"
target="_blank" style="float:right; font-size:smaller">source</a>

### composer

``` python
def composer(
    draft:str='', oob:bool=False
)->FT:
```

*The bottom-of-page input bar: type tabs, a source textarea, and a Boop
(submit) button.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L722"
target="_blank" style="float:right; font-size:smaller">source</a>

### render_app

``` python
def render_app(
    draft:str=''
)->FT:
```

*The whole notebook view: all cells plus the composer, wrapped in one
container div.*

</div>

<div class="boopcell boop-note">

## Routes

Composer/toolbar routes plus the command-mode routes driven by hotkeys
([`select`](https://drscotthawley.github.io/boopiter/cells.html#select),
[`select_delta`](https://drscotthawley.github.io/boopiter/cells.html#select_delta),
[`insert`](https://drscotthawley.github.io/boopiter/cells.html#insert),
[`del_selected`](https://drscotthawley.github.io/boopiter/cells.html#del_selected),
[`settype_selected`](https://drscotthawley.github.io/boopiter/cells.html#settype_selected)).

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L728"
target="_blank" style="float:right; font-size:smaller">source</a>

### theme_swap

``` python
def theme_swap()->FT:
```

*DaisyUI sun/moon swap; drives boopApplyTheme (default dark).*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L741"
target="_blank" style="float:right; font-size:smaller">source</a>

### fname_display

``` python
def fname_display()->FT:
```

*The clickable filename shown in the top bar; click to rename.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L750"
target="_blank" style="float:right; font-size:smaller">source</a>

### rename_form

``` python
def rename_form()->FT:
```

*Swap the filename display for a text input, focused and pre-selected,
to rename the notebook.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L792"
target="_blank" style="float:right; font-size:smaller">source</a>

### brain_menu

``` python
def brain_menu(
    icon_cls:str
)->FT:
```

*Hover menu (styled like tools_menu) for picking the Standard and
Reasoning models, SolveIt-style – the Reasoning picker only lists models
advertising Ollama’s ‘thinking’ capability (see
get_ollama_list()/ensure_models()), with an L/M/H effort control
alongside it (passed through as Chat(…)(think=…) – see
stream_llm_reply()). The brain icon itself doubles as a toggle: click it
to switch whether the reasoning or standard model actually answers
Prompt cells (nb.use_reasoning/active_model()) – its SVG strokes turn
cyan (matching the ‘Tricky…’ streaming indicator’s color, our existing
‘this is thinking’ cue) while on. Re-renders itself wholesale on toggle
(hx_target=self) since the button’s own color has to change along with
the menu.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L847"
target="_blank" style="float:right; font-size:smaller">source</a>

### set_standard_model

``` python
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().\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L856"
target="_blank" style="float:right; font-size:smaller">source</a>

### set_reasoning_model

``` python
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().\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L865"
target="_blank" style="float:right; font-size:smaller">source</a>

### toggle_reasoning

``` python
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.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L873"
target="_blank" style="float:right; font-size:smaller">source</a>

### refresh_models

``` python
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.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L900"
target="_blank" style="float:right; font-size:smaller">source</a>

### set_keep_alive

``` python
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.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1004"
target="_blank" style="float:right; font-size:smaller">source</a>

### share_dismiss

``` python
def share_dismiss()->FT:
```

*Close the share notice, emptying its slot. Doesn’t cancel anything – an
in-flight publish finishes regardless; this only stops showing it.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L996"
target="_blank" style="float:right; font-size:smaller">source</a>

### share_poll

``` python
def share_poll()->fastcore.xml.FT | starlette.responses.Response:
```

*Re-render the share notice, but only when its state has actually
changed – otherwise 204 No Content, which tells htmx to swap nothing.
With the notice polling on a repeating trigger, that means the element
is left completely untouched between state changes, instead of being
rebuilt every couple of seconds: rebuilding restarted the spinner and
made the whole notice blink.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L987"
target="_blank" style="float:right; font-size:smaller">source</a>

### share_nb

``` python
def share_nb(
    vis:str='public'
)->FT:
```

*Share the current notebook, listed on the boops index or not (see
share_menu). Saves it to disk first – the render reads the file, and the
published page should match what’s on screen – then publishes on a
background thread, since a Quarto render plus a push takes far too long
to hold an HTTP response open. Returns the notice immediately so the
click visibly does something; it polls itself from there.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L957"
target="_blank" style="float:right; font-size:smaller">source</a>

### share_panel

``` python
def share_panel()->FT:
```

*The share notice: progress while rendering and publishing, then the
published URL with a button to copy it. Persistent – it stays until
dismissed, because its whole job is to leave the link on screen long
enough to paste somewhere.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L909"
target="_blank" style="float:right; font-size:smaller">source</a>

### share_menu

``` python
def share_menu(
    icon_cls:str
)->FT:
```

\*The share button: a hover menu (same idiom as tools_menu/brain_menu)
offering Public or Unlisted, rather than a plain button. Both publish
the same page to the same URL; the choice only decides whether the
site’s index links to it, and re-sharing with the other choice flips
that – so this is a state toggle, not a one-way door. A hover menu
rather than a modal or a native <select>, because a real select renders
its options as OS chrome outside the page and collapses the popup the
moment the pointer moves onto them (see \_model_select).\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1022"
target="_blank" style="float:right; font-size:smaller">source</a>

### set_num_ctx

``` python
def set_num_ctx(
    n:int
)->str:
```

*Pick a context window from the brain menu. Ignores anything not in
NUM_CTX_CHOICES – the value goes straight into an Ollama request, and
there’s no reason for this route to accept arbitrary sizes. Takes effect
on the next model call; Ollama reloads the model whenever the requested
window differs from the loaded one, which is also when the RAM actually
changes.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1029"
target="_blank" style="float:right; font-size:smaller">source</a>

### set_reasoning_effort

``` python
def set_reasoning_effort(
    effort:str
)->str:
```

*Set the L/M/H reasoning-effort radio in the brain menu – only
meaningful while the reasoning model is in use (see
nb.reasoning_effort/stream_llm_reply).*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1050"
target="_blank" style="float:right; font-size:smaller">source</a>

### file_menu

``` python
def file_menu()->FT:
```

*Hamburger dropdown: New / Open (file browser) / Save / Download /
Restart Server.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1058"
target="_blank" style="float:right; font-size:smaller">source</a>

### context_menu

``` python
def context_menu()->FT:
```

*The same New/Open/Save/Download/Restart Server menu as file_menu(), but
shown wherever you right-click anywhere on the page (see boopContextMenu
in theme.js), for people who reach for a right-click before the
hamburger or the ‘s’ hotkey.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1065"
target="_blank" style="float:right; font-size:smaller">source</a>

### file_browser_modal

``` python
def file_browser_modal()->FT:
```

*The (initially empty/hidden) dialog that hosts the file-browser
listing, opened by file_menu()’s Open item.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1081"
target="_blank" style="float:right; font-size:smaller">source</a>

### help_modal

``` python
def help_modal()->FT:
```

*Keyboard-shortcuts cheat sheet, opened by the ‘?’ button in the top
bar. Same modal/backdrop pattern as file_browser_modal().*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1122"
target="_blank" style="float:right; font-size:smaller">source</a>

### tools_menu

``` python
def tools_menu(
    icon_cls:str
)->FT:
```

\*Wrench-icon ‘Tools’ menu: hover to see checkboxes toggling which tool
sources (see \_TOOL_SOURCE_LABELS/nb.tool_selection) get offered to the
LLM on Prompt-cell runs – see get_tool_list(). DaisyUI’s dropdown-hover
opens/closes purely via CSS on mouseenter/mouseleave, so no close button
is needed.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1158"
target="_blank" style="float:right; font-size:smaller">source</a>

### plugin_panel_poll

``` python
def plugin_panel_poll(
    name:str
)->FT:
```

\*Poll target for \_plugin_panel – just re-renders the same self-polling
wrapper, which is what keeps the sparklines visibly scrolling while a
plugin’s panel is open.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1180"
target="_blank" style="float:right; font-size:smaller">source</a>

### top_bar

``` python
def top_bar()->FT:
```

*The whole navbar: file menu + logo + filename on the left, model
pickers + kernel/theme controls on the right.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1226"
target="_blank" style="float:right; font-size:smaller">source</a>

### boopiter_ping

``` python
def boopiter_ping()->str:
```

*Identity check so `boopiter launch` can tell a live boopiter instance
apart from something else on the port.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1232"
target="_blank" style="float:right; font-size:smaller">source</a>

### logo_png

``` python
def logo_png()->FileResponse:
```

*Serve the boopiter logo, used both as favicon and in the top bar.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1257"
target="_blank" style="float:right; font-size:smaller">source</a>

### boopimg

``` python
def boopimg(
    fname:str
):
```

\*Serve a previously pasted image from \_IMAGES_DIR. Names are generated
server-side at paste time (token_hex + a short extension), so a strict
fullmatch on that shape is cheap and closes path traversal without any
path canonicalization.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1240"
target="_blank" style="float:right; font-size:smaller">source</a>

### paste_image

``` python
def paste_image(
    data:str
)->str:
```

\*Receive a pasted clipboard image as a base64 data URL (see the paste
handler in static/edit.js), save it under \_IMAGES_DIR with a random
collision-proof name, and return the markdown to insert at the caret.
The markdown points at the /boopimg/ serving route (below), not the
filesystem path – the browser needs a URL it can actually fetch for the
image to render in the note’s preview, and the same string is what rides
along in Cell.source to the LLM.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1265"
target="_blank" style="float:right; font-size:smaller">source</a>

### tailwind_css

``` python
def tailwind_css()->FileResponse:
```

\*Serve the precompiled Tailwind CSS built by \_build_tailwind() at
launch.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1271"
target="_blank" style="float:right; font-size:smaller">source</a>

### index

``` python
def index()->tuple:
```

*The full page: title, top bar, the notebook+composer, and the
save-toast slot.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1302"
target="_blank" style="float:right; font-size:smaller">source</a>

### save_now

``` python
def save_now()->FT:
```

*Save the notebook to disk and show a toast confirming success (or
failure).*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1320"
target="_blank" style="float:right; font-size:smaller">source</a>

### browse

``` python
def browse(
    path:str | None=None
)->FT:
```

*Jupyter-tree-style directory listing for the file-browser modal, rooted
at BROWSE_ROOT.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1350"
target="_blank" style="float:right; font-size:smaller">source</a>

### open_file

``` python
def open_file(
    path:str
)->RedirectResponse:
```

*Open a notebook found by the file browser. Redirects to / afterward so
the address bar (and thus a later page reload) doesn’t stay pinned to
this action – reloading /open_file?path=… would silently re-run the load
and discard any unsaved edits made since.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1359"
target="_blank" style="float:right; font-size:smaller">source</a>

### new_notebook

``` python
def new_notebook()->RedirectResponse:
```

*Discard the current notebook and start a blank one. Redirects to /
afterward – same reasoning as open_file().*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1366"
target="_blank" style="float:right; font-size:smaller">source</a>

### restart_server

``` python
def restart_server()->str:
```

*nbdev-export the notebooks as a real (blocking) subprocess – so we get
a genuine exit code instead of guessing a delay – then clear **pycache**
(WSL/Windows-mounted filesystems can have coarse mtime resolution, which
can trick Python into serving stale cached bytecode right after a fast
save-then-restart) and exec a fresh copy of this process (same PID, same
port). Aborts (leaves the current process running) if export fails. Does
NOT save the notebook first; Save manually beforehand if you want to
keep unsaved edits.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1396"
target="_blank" style="float:right; font-size:smaller">source</a>

### shutdown_server

``` python
def shutdown_server()->str:
```

*Cleanly exit boopiter: send this process SIGTERM, the same
graceful-shutdown signal Ctrl-C sends, which uvicorn catches to drain
in-flight requests and exit 0. Does NOT save the notebook first – Save
manually beforehand if you want to keep unsaved edits.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1407"
target="_blank" style="float:right; font-size:smaller">source</a>

### download

``` python
def download()->FileResponse:
```

*Save the notebook, then send it to the browser as a file download.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1414"
target="_blank" style="float:right; font-size:smaller">source</a>

### rename

``` python
def rename(
    name:str
)->FT:
```

*Rename and persist the notebook to `{new_name}.ipynb` in the server’s
cwd.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1422"
target="_blank" style="float:right; font-size:smaller">source</a>

### restart_kernel

``` python
def restart_kernel()->str:
```

\*Reset the shared IPython shell’s namespace, clearing all user-defined
variables/functions, then re-pushing nb/add_tool/the current tool
selection – \_shell.reset() wipes those along with everything else.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1431"
target="_blank" style="float:right; font-size:smaller">source</a>

### run_all

``` python
def run_all()->FT:
```

*Run every code cell top-to-bottom, one at a time – exactly like
clicking play on each in turn. Each cell stamps its own timestamp,
streams its own output, and scrolls into view as it starts; the sequence
stops at the first cell that errors (see run_code_poll’s chaining).
Prompt/Note/Raw/Assistant cells are skipped.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1443"
target="_blank" style="float:right; font-size:smaller">source</a>

### interrupt_kernel

``` python
def interrupt_kernel()->str:
```

\*Best-effort interrupt: inject a KeyboardInterrupt into whichever
background thread(s) are currently running – a code cell (\_run_state)
and/or a streaming Prompt reply (\_prompt_state), since those are
independent slots and could both be active. Uses CPython’s
PyThreadState_SetAsyncExc, the same low-level trick real kernels use for
SIGINT-based interrupts. It fires at the interrupted thread’s next
bytecode boundary, so it reliably breaks ordinary Python loops (e.g. a
tqdm-wrapped for-loop, or the token-by-token loop in \_run_prompt_bg)
but can’t preempt a single blocking C call already in flight.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1457"
target="_blank" style="float:right; font-size:smaller">source</a>

### toggle_tool_source

``` python
def toggle_tool_source(
    key:str
)->str:
```

*Toggle one entry in nb.tool_selection (the wrench-icon Tools menu – see
top_bar()). The checkbox already flips itself natively in the browser;
this just keeps the server-side selection in sync, so no DOM update is
needed in response.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1464"
target="_blank" style="float:right; font-size:smaller">source</a>

### set_type

``` python
def set_type(
    t:str
)->FT:
```

*Change the composer’s current cell type (what a new cell becomes when
you hit Boop).*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1470"
target="_blank" style="float:right; font-size:smaller">source</a>

### add_cell

``` python
def add_cell(
    t:str, source:str
)->list:
```

\*Create a cell of type `t`: run it if code, just create it if prompt
(its Assistant reply streams in asynchronously – see
\_start_prompt_run/pending_prompt_cell). Returns the new cell(s).\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1482"
target="_blank" style="float:right; font-size:smaller">source</a>

### submit_cell

``` python
def submit_cell(
    source:str
)->tuple:
```

*Append only the new cell(s) to \#notebook and reset the composer
out-of-band, so untouched cells’ editors are never re-created. A
just-submitted Prompt gets a ‘Tricky…’ placeholder that streams its own
reply.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1494"
target="_blank" style="float:right; font-size:smaller">source</a>

### split

``` python
def split(
    source:str, pos:int
)->tuple:
```

*Split the composer at the caret: head becomes a cell, tail stays in the
composer.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1507"
target="_blank" style="float:right; font-size:smaller">source</a>

### split_cell

``` python
def split_cell(
    id:int, pos:int, source:str | None=None
)->fastcore.xml.FT | tuple:
```

*Split an existing cell at caret position `pos`: it keeps the text
before the cursor; a new cell of the same type (and, for code, the same
export flag) is inserted right after it with the text after the cursor.
`source` is the editor’s live buffer, sent along by boopSplitCell()
because `pos` was measured against* it*, not against the server’s
last-saved copy – splitting a cell you’d typed into but not yet saved
used to slice the stale copy at an offset that indexed the new one.
Neither half is (re-)executed, and any existing output is cleared – it
was produced by the* whole\* original code, so it doesn’t correctly
belong to either fragment alone (e.g. a plot statement can end up in
either half after the split, stranding old output next to code that
didn’t produce it). Blank lines right at the split point (e.g. the PEP8
spacer between two functions) are trimmed off the boundary – otherwise
the new cell would start with an ugly-looking leading blank line.\*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1522"
target="_blank" style="float:right; font-size:smaller">source</a>

### run_cell

``` python
def run_cell(
    id:int
)->FT:
```

*Run this cell (Code: kick off background execution and return a
placeholder that streams its progress; Prompt: kick off the LLM call and
return a placeholder that streams the reply in), so the caller can
target just this cell instead of the whole notebook.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1534"
target="_blank" style="float:right; font-size:smaller">source</a>

### toggle_vis

``` python
def toggle_vis(
    id:int
)->str:
```

*Toggle whether this cell is visible to the LLM (shown/hidden in
llm_context). Called via a plain fetch(), not htmx – boopToggleVis()
(edit.js) already updated the eye icon and cell dimming instantly and
optimistically on the client; this just persists that same flip
server-side, no HTML response needed.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1543"
target="_blank" style="float:right; font-size:smaller">source</a>

### toggle_export

``` python
def toggle_export(
    id:int
)->str:
```

*Toggle a code cell’s ‘\#| export’ flag (the bookmark icon in
cell_toolbar). Called via a plain fetch(), not htmx – boopToggleExport()
(edit.js) already updated the icon’s look instantly and optimistically
on the client; this just persists that same flip server-side, no HTML
response needed.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1552"
target="_blank" style="float:right; font-size:smaller">source</a>

### del_cell

``` python
def del_cell(
    id:int
)->tuple:
```

*Delete this cell (or its Prompt+Assistant pair). Removes just the
affected DOM node(s) out-of-band instead of re-rendering the whole
notebook.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1563"
target="_blank" style="float:right; font-size:smaller">source</a>

### move_cell

``` python
def move_cell(
    id:int, delta:int
)->tuple:
```

*Move this cell (or its Prompt+Assistant pair) up (delta=-1) or down
(delta=1). Rather than re-rendering the whole notebook, this deletes the
moved block’s DOM node(s) out-of-band and reinserts them next to
whichever cell/pair they swapped with – the rest of the notebook’s DOM
(and the page’s scroll position) is never touched.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1589"
target="_blank" style="float:right; font-size:smaller">source</a>

### select

``` python
def select(
    id:int
)->fastcore.xml.FT | tuple | str:
```

*Select this cell (highlights it and anchors j/k navigation). Updates
just the newly- and previously-selected cells, not the whole notebook.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1600"
target="_blank" style="float:right; font-size:smaller">source</a>

### select_delta

``` python
def select_delta(
    delta:int
)->tuple:
```

*Move the selection up (delta=-1) or down (delta=1) – the j/k hotkeys.
Triggered by a global hotkey (not a specific cell’s button), so the
response carries out-of-band updates for whichever cells actually
changed rather than a full re-render.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1614"
target="_blank" style="float:right; font-size:smaller">source</a>

### insert

``` python
def insert(
    where:str
)->tuple:
```

*Insert a new blank cell above or below the current selection – the a/b
hotkeys. The new cell’s type matches the selected cell’s own type
(falling back to the composer’s current type if nothing’s selected, or
if the selection is an Assistant cell, which isn’t directly authorable).
Inserts the new cell out-of-band next to its anchor, and refreshes the
previously-selected cell (to drop its highlight ring) rather than
re-rendering the whole notebook.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1636"
target="_blank" style="float:right; font-size:smaller">source</a>

### pull_code_blocks

``` python
def pull_code_blocks()->tuple:
```

*The ‘w’ hotkey: copy every fenced code block out of the selected
Assistant reply (or its paired Prompt) into new Code cells directly
below it, in order – a low-friction way to try running code an LLM
suggested without retyping it. No-op if nothing’s selected or there’s no
Assistant reply with code fences.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1664"
target="_blank" style="float:right; font-size:smaller">source</a>

### del_selected

``` python
def del_selected()->tuple:
```

*Delete the currently-selected cell (or its Prompt+Assistant pair) – the
d-d hotkey.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1680"
target="_blank" style="float:right; font-size:smaller">source</a>

### cut_selected

``` python
def cut_selected()->tuple:
```

*Cut the currently-selected cell (or its pair) to the clipboard – the x
hotkey.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1694"
target="_blank" style="float:right; font-size:smaller">source</a>

### copy_selected

``` python
def copy_selected()->str:
```

*Copy the currently-selected cell (or its pair) to the clipboard – the c
hotkey.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1701"
target="_blank" style="float:right; font-size:smaller">source</a>

### paste_selected

``` python
def paste_selected()->tuple:
```

*Paste the clipboard after the currently-selected cell – the v hotkey.
Inserts the pasted cell(s) out-of-band, right after their anchor (or at
the end, if nothing was selected), instead of re-rendering the whole
notebook.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1723"
target="_blank" style="float:right; font-size:smaller">source</a>

### settype_selected

``` python
def settype_selected(
    t:str
)->fastcore.xml.FT | str:
```

*Change the currently-selected cell’s type – the m/y/r hotkeys.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1733"
target="_blank" style="float:right; font-size:smaller">source</a>

### set_ctype

``` python
def set_ctype(
    id:int, t:str
)->FT:
```

*Change one cell’s type in place; returns just that cell so the rest of
the notebook is untouched.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1744"
target="_blank" style="float:right; font-size:smaller">source</a>

### edit_cell

``` python
def edit_cell(
    id:int
)->FT:
```

*Switch this cell into its live editor (CodeMirror for code, a plain
textarea otherwise).*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1753"
target="_blank" style="float:right; font-size:smaller">source</a>

### view_cell

``` python
def view_cell(
    id:int
)->FT:
```

*Switch this cell back to its static (non-editing) view – used by the
Cancel button and the Escape hotkey (see boopCancelEdit() in edit.js).
Also clears nb.selected if it’s this cell – otherwise a code cell (whose
editor is shown exactly while c.id == nb.selected, not via a separate
edit flag like note/prompt/raw) would just redraw right back into edit
mode.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1761"
target="_blank" style="float:right; font-size:smaller">source</a>

### save_cell

``` python
def save_cell(
    id:int, source:str
)->fastcore.xml.FT | tuple:
```

*Commit an edited cell’s source (running it if it’s code, or
re-prompting the LLM if it’s a prompt). Targets just this cell (and, for
prompts, the paired Assistant cell) rather than the whole notebook, so
editing a cell deep in a long notebook doesn’t blow away scroll
position.*

</div>

<div class="prose" data-markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/drscotthawley/boopiter/blob/main/boopiter/cells.py#L1777"
target="_blank" style="float:right; font-size:smaller">source</a>

### sync_cell

``` python
def sync_cell(
    id:int, source:str
)->str:
```

*Update a cell’s source WITHOUT executing it – used by Save to flush any
editor content that was never explicitly run (Shift+Enter), matching
Jupyter’s WYSIWYG save behavior.*

</div>

``` python
# Run-All queue chaining, tested without any real background thread/timing (see run_all/run_code_poll):
# construct an already-finished _RunState by hand and drive run_code_poll() directly, so the test is
# fully deterministic. Covers the two behaviors run_all()'s docstring promises: advance on success,
# stop-on-error abandons the rest of the batch untouched.
nb.reset()
c1 = nb.add('code', 'a'); c2 = nb.add('code', 'b'); c3 = nb.add('code', 'c')

def _finish(cell_id, blocks):
    "Simulate a background code run finishing -- same shape _run_code_bg produces -- without a real thread."
    global _run_state
    st = _RunState(cell_id)
    st.blocks, st.done = blocks, True
    _run_state = st

# Seed the same Run-All state run_all() would for this 3-cell batch (skipping the real thread it
# would normally start for c1 -- the chaining logic under test lives entirely in run_code_poll()).
_run_all_queue, _run_all_current = [c2.id, c3.id], c1.id

_finish(c1.id, [{'type':'stream', 'mime':None, 'data':'ok'}])
run_code_poll(c1.id, poll_n=1)
assert _run_all_current == c2.id, "success should advance Run All to the next queued cell"
assert _run_all_queue == [c3.id], "the advanced-past cell should be popped off the queue"
assert c1.output == [{'type':'stream', 'mime':None, 'data':'ok'}], "the finished cell's output should be recorded"

_finish(c2.id, [{'type':'error', 'mime':None, 'data':'boom'}])
run_code_poll(c2.id, poll_n=1)
assert _run_all_queue == [] and _run_all_current is None, "an error should abandon the rest of the batch"
assert c3.output is None, "c3 must never have been touched once the batch stopped"

nb.reset()
print('Run-All queue chaining verified: advances on success, stops on error')
```

``` python
# A cell can be deleted while Run All is mid-batch (e.g. the user deletes a not-yet-reached cell
# while an earlier one is still running) -- run_code_poll's chaining loop should skip straight past
# it rather than choke on a missing id.
nb.reset()
c1 = nb.add('code', 'a'); c2 = nb.add('code', 'b'); c3 = nb.add('code', 'c')
_run_all_queue, _run_all_current = [c2.id, c3.id], c1.id
nb.remove(c2.id)  # deleted mid-run, while c1 is still "in flight"

_finish(c1.id, [{'type':'stream', 'mime':None, 'data':'ok'}])
run_code_poll(c1.id, poll_n=1)
assert _run_all_current == c3.id, "the deleted cell should be skipped, landing on c3 instead"
assert _run_all_queue == [], "nothing should be left queued after skipping straight to the last cell"

nb.reset()
print('Run-All queue skips a cell deleted mid-run')
```

<div class="boopcell boop-note">

## Run it

In a notebook, start the server and preview inline. Click a cell’s
header to select it, then use command-mode keys: `A`/`B` insert
above/below, `D D` delete, `J`/`K` (or arrows) move selection,
`M`/`Y`/`R` change type. In the composer, `Cmd/Ctrl+/` toggles comments
on the selected lines and `Cmd/Ctrl+Shift+-` splits at the caret. On WSL
see the lesson’s port notes for reaching it from Windows.

</div>

``` python
srv = JupyUvi(app)
p(index())
```

    NameError: name 'app' is not defined
    NameError: name 'app' is not defined

    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    File <ipython-input-1-db8d6d5e6a19>:2
          1 #| eval: false
    ----> 2 srv = JupyUvi(app)
          3 p(index())

    NameError: name 'app' is not defined

</div>

</div>
