(adaptive-content-height rows requested)Clamp a dialog's REQUESTED content height so the box sizes to its own content instead of the shared footprint.
nil requested -> the shared full-height footprint (default-content-height).
Spacious logo / welcome screens and long browsers opt in this way.min-adaptive-content-h and the terminal-bounded
dialog-max-height, so short dialogs are compact and long ones still scroll.Clamp a dialog's REQUESTED content height so the box sizes to its own content instead of the shared footprint. - `nil` requested -> the shared full-height footprint (`default-content-height`). Spacious logo / welcome screens and long browsers opt in this way. - a number -> clamped between `min-adaptive-content-h` and the terminal-bounded `dialog-max-height`, so short dialogs are compact and long ones still scroll.
(clear-screen! screen)Fill the entire screen with terminal background. Call before sub-dialogs to cleanly replace the current dialog (wizard step pattern).
Fill the entire screen with terminal background. Call before sub-dialogs to cleanly replace the current dialog (wizard step pattern).
(command-palette! screen)(command-palette! screen extra-commands)Show the searchable command palette. Returns the FULL chosen command map
(so the caller's run-command! can read :id and any slash keys), or nil
on Esc. extra-commands are the engine slash roots appended after the
built-ins. Opened with C-x C-p (Emacs C-x prefix + Ctrl+P).
Show the searchable command palette. Returns the FULL chosen command map (so the caller's `run-command!` can read `:id` and any slash keys), or nil on Esc. `extra-commands` are the engine slash roots appended after the built-ins. Opened with C-x C-p (Emacs C-x prefix + Ctrl+P).
(confirm-dialog! screen title message)Show Y/N confirmation with side-by-side buttons. Returns true/false, nil on Esc.
Show Y/N confirmation with side-by-side buttons. Returns true/false, nil on Esc.
(copy-dialog! screen messages)Show copy dialog for chat messages. Space toggles, A toggles all, Enter copies selected, Esc cancels.
Show copy dialog for chat messages. Space toggles, A toggles all, Enter copies selected, Esc cancels.
(default-content-height rows)Shared content height every dialog uses, derived from rows.
Clamped to a common modal footprint so dialogs keep equal height.
Shared content height every dialog uses, derived from `rows`. Clamped to a common modal footprint so dialogs keep equal height.
(default-content-width cols)Shared content width every dialog uses, derived from cols. Clamped
between the theme's dialog min/max widths and bounded by the terminal so
the box never paints off-screen.
Shared content width every dialog uses, derived from `cols`. Clamped between the theme's dialog min/max widths and bounded by the terminal so the box never paints off-screen.
(dialog-layout bounds)(dialog-layout {:keys [top bottom]} content-count)Compute content area layout. When content-count is provided and smaller than
the available height, content is vertically centered within the frame.
Layout: border -> title bar -> top separator -> CONTENT -> bottom separator -> hint -> border.
Compute content area layout. When `content-count` is provided and smaller than the available height, content is vertically centered within the frame. Layout: border -> title bar -> top separator -> CONTENT -> bottom separator -> hint -> border.
(directory-picker-dialog! screen
start-path
&
{:keys [db-info workspace-id session-state-id
purpose]})Browse the filesystem like a file explorer and manage this session's filesystem permissions. Returns the chosen absolute path (string) to OPEN IN A NEW TAB, or nil on Esc.
The picker has TWO clearly separated zones. The read-only HEADER shows the
current path, the session's filesystem roots (when :db-info + :workspace-id
are supplied), and whether the current folder is itself a root. The navigable
LIST below holds ONLY filesystem entries: .., subfolders (navigable), and
files (dimmed, for context). Nothing in the list is an "action" — actions
are modifier keys, exactly like the session navigator:
Up/Down move Enter / → open folder (·· ascends)
← up a level Tab open current folder in a new tab
Ctrl+A add the HIGHLIGHTED subfolder as a filesystem root (or the
current folder when a file / .. is highlighted); removes it
if already a root (no-op without a session)
Ctrl+R make the HIGHLIGHTED folder (or the current one) the
session's ROOT — shell, file tools, and search work there
from the next turn (needs :session-state-id)
Ctrl+N create a new folder here, then enter it
type / Bksp filter the list incrementally Esc close
:purpose only sets the dialog title (:add-root → "Add a filesystem
directory", :new-folder → "Create a directory", :remove → "Remove a
filesystem directory"); the controls are identical regardless.
Browse the filesystem like a file explorer and manage this session's
filesystem permissions. Returns the chosen absolute path (string) to OPEN
IN A NEW TAB, or nil on Esc.
The picker has TWO clearly separated zones. The read-only HEADER shows the
current path, the session's filesystem roots (when `:db-info` + `:workspace-id`
are supplied), and whether the current folder is itself a root. The navigable
LIST below holds ONLY filesystem entries: `..`, subfolders (navigable), and
files (dimmed, for context). Nothing in the list is an "action" — actions
are modifier keys, exactly like the session navigator:
Up/Down move Enter / → open folder (·· ascends)
← up a level Tab open current folder in a new tab
Ctrl+A add the HIGHLIGHTED subfolder as a filesystem root (or the
current folder when a file / `..` is highlighted); removes it
if already a root (no-op without a session)
Ctrl+R make the HIGHLIGHTED folder (or the current one) the
session's ROOT — shell, file tools, and search work there
from the next turn (needs `:session-state-id`)
Ctrl+N create a new folder here, then enter it
type / Bksp filter the list incrementally Esc close
`:purpose` only sets the dialog title (`:add-root` → "Add a filesystem
directory", `:new-folder` → "Create a directory", `:remove` → "Remove a
filesystem directory"); the controls are identical regardless.(drain-modal-paste! screen)After a bracketed-paste START keystroke is seen, drain screen until
PASTE_END and return the pasted text (PUA markers stripped). Lets any
modal text input accept clipboard paste without re-implementing the
paste state machine. Returns "" on a clipboard that yields no chars.
After a bracketed-paste START keystroke is seen, drain `screen` until PASTE_END and return the pasted text (PUA markers stripped). Lets any modal text input accept clipboard paste without re-implementing the paste state machine. Returns "" on a clipboard that yields no chars.
(draw-dialog-chrome! g cols rows title content-h)(draw-dialog-chrome! g cols rows title content-w content-h)Draw dialog background, shadow, border, and title.
Three arities:
(g cols rows title content-h) - shared default width; the box HEIGHT is
sized to content-h via adaptive-content-height. Pass nil as
content-h for the shared full-height footprint.(g cols rows title content-w content-h) - fully explicit. Use
only when a dialog genuinely needs a non-default width.Returns {:left :top :right :bottom :inner-w :inner-h}.
Draw dialog background, shadow, border, and title.
Three arities:
- `(g cols rows title content-h)` - shared default width; the box HEIGHT is
sized to `content-h` via `adaptive-content-height`. Pass `nil` as
`content-h` for the shared full-height footprint.
- `(g cols rows title content-w content-h)` - fully explicit. Use
only when a dialog genuinely needs a non-default width.
Returns {:left :top :right :bottom :inner-w :inner-h}.(draw-dialog-close-button! g box-right title-row)Paint a clickable X close button at a dialog's top-right title row and
record its click bounds (thread-local) so read-modal-input! can turn a
click into Escape. Every dialog inherits it via draw-dialog-chrome!.
Lights up to the red pill (close-button-hover-fg + bold) when the
thread-local close-hover flag is set - the same affordance the header and
help/tasks overlay close buttons use - so modal X buttons are no longer
static.
Paint a clickable X close button at a dialog's top-right title row and record its click bounds (thread-local) so `read-modal-input!` can turn a click into Escape. Every dialog inherits it via `draw-dialog-chrome!`. Lights up to the red pill (`close-button-hover-fg` + bold) when the thread-local close-hover flag is set - the same affordance the header and help/tasks overlay close buttons use - so modal X buttons are no longer static.
(draw-flat-dialog-chrome! g cols rows title)Flat variant of draw-dialog-chrome!: no drop shadow, no accent title
stripe, no separators - one thin-bordered rect on the dialog background
with the title inline on the top border. Same default footprint and the
same bounds map as the boxed chrome, so dialog-layout works unchanged.
Flat variant of `draw-dialog-chrome!`: no drop shadow, no accent title stripe, no separators - one thin-bordered rect on the dialog background with the title inline on the top border. Same default footprint and the same bounds map as the boxed chrome, so `dialog-layout` works unchanged.
(draw-hint-bar! g left row inner-w hint)Draw hint bar. hint can be:
Hints are CENTERED (not full-width justified) so short hint sets read as one tidy line instead of being stretched ragged across the dialog. Examples: "simple hint" ["move" "select" "cancel"] [["Up/Dn" "move"] ["Enter" "select"] ["Esc" "cancel"]]
Draw hint bar. `hint` can be: - a string: rendered as-is, left-aligned - a vec of strings: centered, dim italic, joined with ' · ' - a vec of [key action] pairs: key bold, action dim italic, the whole run centered with thin ' · ' separators between pairs Hints are CENTERED (not full-width justified) so short hint sets read as one tidy line instead of being stretched ragged across the dialog. Examples: "simple hint" ["move" "select" "cancel"] [["Up/Dn" "move"] ["Enter" "select"] ["Esc" "cancel"]]
(footer-content-width cols hint)(footer-content-width cols hint min-content)Content width for an action-footer dialog: sized so the box is EXACTLY the
footer's natural width plus two columns of padding on each side, never
narrower than min-content (the widest content line) nor wider than the
terminal. The +2 supplies the extra pad beyond the single-column gutter
draw-dialog-chrome! already reserves inside the border, so a footer of
width W yields 2 blank columns between the frame and the hints on each side.
Content width for an action-footer dialog: sized so the box is EXACTLY the footer's natural width plus two columns of padding on each side, never narrower than `min-content` (the widest content line) nor wider than the terminal. The `+2` supplies the extra pad beyond the single-column gutter `draw-dialog-chrome!` already reserves inside the border, so a footer of width W yields 2 blank columns between the frame and the hints on each side.
(hint-bar-width hint)Natural rendered width (chars) of a draw-hint-bar! hint — a plain string,
a vec of strings, or a vec of [key action] pairs — using the SAME segment
and separator math the hint bar paints with. Lets a dialog size its box to
the footer instead of a fixed terminal ratio.
Natural rendered width (chars) of a `draw-hint-bar!` hint — a plain string, a vec of strings, or a vec of `[key action]` pairs — using the SAME segment and separator math the hint bar paints with. Lets a dialog size its box to the footer instead of a fixed terminal ratio.
(list-dialog! screen
title
items
{:keys [filter? placeholder enter-label height]})Reusable scrollable, selectable list dialog — the SINGLE implementation
behind select-dialog! (plain) and searchable-select! (type-to-filter).
Owns the whole loop: chrome → optional query field → selectable list (each
row via the shared draw-list-item!, with an optional dim right-aligned
:hint chip) → scrollbar → hint-bar → nav keys. Returns the chosen item map
(the full map, so callers recover :id/slash keys), or nil on Esc.
items is a vec of maps with at least :label. opts:
:filter? type-to-filter on :label, case-insensitive (default false)
:placeholder query placeholder shown while the filter is empty
:enter-label hint-bar verb for Enter (default "select")
:height :content sizes the box to the item count (+ the query
field), capped; nil uses the shared (tall) footprint.
Reusable scrollable, selectable list dialog — the SINGLE implementation
behind `select-dialog!` (plain) and `searchable-select!` (type-to-filter).
Owns the whole loop: chrome → optional query field → selectable list (each
row via the shared `draw-list-item!`, with an optional dim right-aligned
`:hint` chip) → scrollbar → hint-bar → nav keys. Returns the chosen item map
(the full map, so callers recover `:id`/slash keys), or nil on Esc.
`items` is a vec of maps with at least `:label`. opts:
:filter? type-to-filter on `:label`, case-insensitive (default false)
:placeholder query placeholder shown while the filter is empty
:enter-label hint-bar verb for Enter (default "select")
:height `:content` sizes the box to the item count (+ the query
field), capped; nil uses the shared (tall) footprint.(markdown-viewer-dialog! screen title md)Scrollable read-only MARKDOWN viewer: md is lifted to canonical IR
(vis/markdown->ir) and painted styled — headings, bold, code
accents, tables — through the SAME IR walker the chat uses
(render-ir/ir->lines). The rich twin of text-viewer-dialog!.
Returns nil on Esc. Supports keyboard scrolling.
Scrollable read-only MARKDOWN viewer: `md` is lifted to canonical IR (`vis/markdown->ir`) and painted styled — headings, bold, code accents, tables — through the SAME IR walker the chat uses (`render-ir/ir->lines`). The rich twin of `text-viewer-dialog!`. Returns nil on Esc. Supports keyboard scrolling.
(modal-close-click? key)True when key is a mouse click on the dialog close (✕) button.
True when `key` is a mouse click on the dialog close (✕) button.
(modal-wheel-delta key)Return list-selection delta for a wheel mouse event, else nil. Negative moves up; positive moves down.
Return list-selection delta for a wheel mouse event, else nil. Negative moves up; positive moves down.
(modal-wheel-step key)Return wheel delta multiplied by any coalesced event count.
Return wheel delta multiplied by any coalesced event count.
(model-picker! screen current)Searchable per-session model picker — TUI parity with the web footer
chooser. Lists every configured model as a row (<provider> / <model>,
the active one marked ● current) plus a top ★ router default row
that CLEARS the per-session override. current is the session's stored
model preference ({:provider <str|kw> :model <str>}) or nil; it marks
the active row exactly like the web picker. Returns the chosen item map
— {:reset? true} for the router-default row, else {:provider <str> :model <str>} — or nil on Esc.
Searchable per-session model picker — TUI parity with the web footer
chooser. Lists every configured model as a row (`<provider> / <model>`,
the active one marked `● current`) plus a top `★ router default` row
that CLEARS the per-session override. `current` is the session's stored
model preference (`{:provider <str|kw> :model <str>}`) or nil; it marks
the active row exactly like the web picker. Returns the chosen item map
— `{:reset? true}` for the router-default row, else `{:provider <str>
:model <str>}` — or nil on Esc.(multi-select-dialog! screen title items)Checkbox multi-select over items (vec of strings). Space toggles the
cursor row, a toggles all, Enter confirms, Esc cancels. Returns the vec
of selected strings (possibly empty) on confirm, nil on Esc. Mirrors the
web modal's alias chips — same proposed options, multi-pick semantics.
Checkbox multi-select over `items` (vec of strings). Space toggles the cursor row, `a` toggles all, Enter confirms, Esc cancels. Returns the vec of selected strings (possibly empty) on confirm, nil on Esc. Mirrors the web modal's alias chips — same proposed options, multi-pick semantics.
(navigator-dialog! screen opts)Global C-g picker. Returns a target action map or nil on Esc.
Global C-g picker. Returns a target action map or nil on Esc.
(normalize-modal-key key)Normalize raw terminal CR/LF/ESC character keystrokes to Lanterna
Enter/Escape key types. Some terminals surface modal Enter/Escape as
KeyType/Character; modal code should not need to special-case that.
Normalize raw terminal CR/LF/ESC character keystrokes to Lanterna Enter/Escape key types. Some terminals surface modal Enter/Escape as `KeyType/Character`; modal code should not need to special-case that.
Command palette entries. Each is {:id keyword :label str}. The :id is the
action the screen's run-command! executes. Quit is intentionally NOT here
— use Ctrl+C to quit.
The palette is THE discoverable entry point for every app verb: opened with Ctrl+P (reliable on every terminal, unlike Alt/Option chords on macOS) and filtered by typing. Mirrors the web command palette so both channels expose the same command set.
Command palette entries. Each is {:id keyword :label str}. The `:id` is the
action the screen's `run-command!` executes. Quit is intentionally NOT here
— use Ctrl+C to quit.
The palette is THE discoverable entry point for every app verb: opened with
Ctrl+P (reliable on every terminal, unlike Alt/Option chords on macOS) and
filtered by typing. Mirrors the web command palette so both channels expose
the same command set.(read-modal-input! screen)Read one modal input event. Consecutive pending wheel events are drained
and returned as one :scroll-delta, so a wheel flood costs one redraw.
The first non-wheel event encountered while draining is held for the next
modal read on this thread. MOVE/DRAG events also refresh the close (X)
hover flag so the button can light up under the cursor.
Read one modal input event. Consecutive pending wheel events are drained and returned as one `:scroll-delta`, so a wheel flood costs one redraw. The first non-wheel event encountered while draining is held for the next modal read on this thread. MOVE/DRAG events also refresh the close (X) hover flag so the button can light up under the cursor.
(read-modal-key! screen)Like Screen/readInput, but drains wheel floods into one synthetic wheel
event. Existing modal loops can use it without bespoke scroll-delta code.
Like `Screen/readInput`, but drains wheel floods into one synthetic wheel event. Existing modal loops can use it without bespoke scroll-delta code.
(resources-dialog! screen session-id)Modal list of THIS session's vis-managed resources (nREPLs, daemons, …).
↑/↓ move · s = stop · r = restart · Esc = close. Stop/restart go through
vis/stop-resource! / vis/restart-resource! — the SAME canonical path the
agent's resource_stop/resource_restart tools use, so footer, agent and
this dialog all drive one definition. The list re-reads every loop, so an
item vanishes the instant it's stopped.
Modal list of THIS session's vis-managed resources (nREPLs, daemons, …). ↑/↓ move · s = stop · r = restart · Esc = close. Stop/restart go through `vis/stop-resource!` / `vis/restart-resource!` — the SAME canonical path the agent's `resource_stop`/`resource_restart` tools use, so footer, agent and this dialog all drive one definition. The list re-reads every loop, so an item vanishes the instant it's stopped.
(searchable-select! screen title items)Type-to-filter selection list — the searchable spine of the command palette.
Thin wrapper over list-dialog! (filter on, content-sized, palette
placeholder). Returns the FULL chosen item map (so callers recover
:id / slash keys), or nil on Esc.
Type-to-filter selection list — the searchable spine of the command palette. Thin wrapper over `list-dialog!` (filter on, content-sized, palette placeholder). Returns the FULL chosen item map (so callers recover `:id` / slash keys), or nil on Esc.
(select-dialog! screen title items)Show a selection list dialog. Returns the selected item map or nil on Esc.
items is a vec of {:label str, …} maps. Thin wrapper over list-dialog!.
Show a selection list dialog. Returns the selected item map or nil on Esc.
`items` is a vec of `{:label str, …}` maps. Thin wrapper over `list-dialog!`.(session-dialog-items sessions active-id)(session-dialog-items sessions active-id body-w)Build table rows for existing sessions only. New/fork stay dialog options via the N/F shortcuts and command palette; they are not fake table data rows. Rows are sorted by Modified at desc, then Created at desc.
Build table rows for existing sessions only. New/fork stay dialog options via the N/F shortcuts and command palette; they are not fake table data rows. Rows are sorted by Modified at desc, then Created at desc.
(session-dialog-label {:keys [id turn-count modified-at created-at] :as session}
active-id
body-w)Format one fixed-width session table row. Columns are intentionally stable so the picker reads as a table inside the shared dialog chrome.
Format one fixed-width session table row. Columns are intentionally stable so the picker reads as a table inside the shared dialog chrome.
(session-picker-dialog! screen sessions active-id)Show recent TUI sessions in a fixed-size table. Returns
{:action :new}, {:action :fork}, {:action :switch :id <session-id>},
or nil on Esc.
Show recent TUI sessions in a fixed-size table. Returns
`{:action :new}`, `{:action :fork}`, `{:action :switch :id <session-id>}`,
or nil on Esc.(settings-dialog! screen settings)(settings-dialog! screen settings callbacks)Show the settings dialog.
ONE flat, grouped, scrollable list (mirrors the web settings modal), laid out VS Code-style: a left Table-of-Contents sidebar rail lists the sections with per-section counts and highlights the one owning the selection, while the right pane shows the settings themselves. Toggle rows render a leading status glyph; choice rows cycle their value with Enter; action rows invoke a callback. The rail is a passive locator — arrow keys still move through the right pane and the rail tracks where you are.
settings is the persisted TUI settings map (see
state/default-settings). Esc clears an active search first, then closes
and returns the current settings map.
Show the settings dialog. ONE flat, grouped, scrollable list (mirrors the web settings modal), laid out VS Code-style: a left Table-of-Contents sidebar rail lists the sections with per-section counts and highlights the one owning the selection, while the right pane shows the settings themselves. Toggle rows render a leading status glyph; choice rows cycle their value with Enter; action rows invoke a callback. The rail is a passive locator — arrow keys still move through the right pane and the rail tracks where you are. `settings` is the persisted TUI settings map (see `state/default-settings`). Esc clears an active search first, then closes and returns the current settings map.
(start-resource-flow! screen session-id)Generic 'start a new resource' flow driven by the declarative
:ext/startable-resources registry — the SAME definitions the web
modal renders. Steps:
:fields or multi-select proposed :options-fn,:start-fn with the session env + the collected value.
No resource type is hardcoded here; adding a startable elsewhere makes
it appear in this dialog automatically. Returns nil.Generic 'start a new resource' flow driven by the declarative `:ext/startable-resources` registry — the SAME definitions the web modal renders. Steps: 1. pick a startable (skip when only one is registered), 2. collect declared text `:fields` or multi-select proposed `:options-fn`, 3. call its `:start-fn` with the session env + the collected value. No resource type is hardcoded here; adding a startable elsewhere makes it appear in this dialog automatically. Returns nil.
(text-input-dialog! screen
title
label
&
{:keys [mask initial body logo flat?] :or {initial ""}})Show a text input dialog. Returns string or nil on Esc. Options: :mask char (e.g. * for passwords), :initial string, :body string-or-lines rendered above the input label, :logo lines drawn centered in the accent color above the body, :flat? true for the flat chrome (no shadow / title stripe).
Show a text input dialog. Returns string or nil on Esc. Options: :mask char (e.g. \* for passwords), :initial string, :body string-or-lines rendered above the input label, :logo lines drawn centered in the accent color above the body, :flat? true for the flat chrome (no shadow / title stripe).
(text-view-dialog! screen title lines & {:keys [refresh-fn tail?]})Show read-only lines in a scrollable modal. Returns nil after close.
Keys: ↑/↓ line, PgUp/PgDn page, Home/End jump, mouse-wheel scroll, Enter/Esc close. Options:
Show read-only lines in a scrollable modal. Returns nil after close.
Keys: ↑/↓ line, PgUp/PgDn page, Home/End jump, mouse-wheel scroll,
Enter/Esc close. Options:
- :refresh-fn thunk returning fresh lines — enables [r] refresh so a live
buffer (e.g. background logs) can be re-pulled in place.
- :tail? start pinned to the newest line and re-follow the bottom on
refresh (log-tail behaviour); scrolling up releases the pin.(text-viewer-dialog! screen title text)Show a scrollable read-only text viewer dialog.
title is the dialog header. text is a string (may contain newlines)
that is rendered VERBATIM - same content the LLM receives, only soft-
wrapped to fit the dialog width. No markdown, no truncation, no
reformatting.
Returns nil on Esc. Supports keyboard scrolling.
Show a scrollable read-only text viewer dialog. `title` is the dialog header. `text` is a string (may contain newlines) that is rendered VERBATIM - same content the LLM receives, only soft- wrapped to fit the dialog width. No markdown, no truncation, no reformatting. Returns nil on Esc. Supports keyboard scrolling.
(update-modal-close-hover! key)On a MOVE/DRAG event, set the thread-local close-hover flag when the cursor is within the recorded close-button bounds, clear it otherwise. Lets the modal close (X) button light up on hover like the header/overlay buttons.
On a MOVE/DRAG event, set the thread-local close-hover flag when the cursor is within the recorded close-button bounds, clear it otherwise. Lets the modal close (X) button light up on hover like the header/overlay buttons.
ASCII rendition of the real vis emblem (logo.png) followed by the wordmark. Drawn at the top of branded dialogs (the provider auth gate). Centered + accent-colored by the caller; auto-dropped when the terminal is too short.
ASCII rendition of the real vis emblem (logo.png) followed by the wordmark. Drawn at the top of branded dialogs (the provider auth gate). Centered + accent-colored by the caller; auto-dropped when the terminal is too short.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |