Liking cljdoc? Tell your friends :D

com.blockether.vis.ext.channel-tui.render


*image-placements*clj

When bound to an atom holding a vector, draw-chat-bubble! conj's the EXACT painted screen position of every inline-image row it draws: {:row abs-screen-row :col abs-screen-col :img {...}}. The screen loop drains it after the delta refresh to place Kitty/iTerm2 graphics precisely over the reserved cells — recording from inside the paint loop avoids re-deriving the bubble's chrome offsets (role banner, top pad, turn separator) that the copy-region geometry deliberately skips.

When bound to an atom holding a vector, `draw-chat-bubble!` conj's the
EXACT painted screen position of every inline-image row it draws:
`{:row abs-screen-row :col abs-screen-col :img {...}}`. The screen loop
drains it after the delta refresh to place Kitty/iTerm2 graphics precisely
over the reserved cells — recording from inside the paint loop avoids
re-deriving the bubble's chrome offsets (role banner, top pad, turn
separator) that the copy-region geometry deliberately skips.
sourceraw docstring

bubble-heightclj

(bubble-height {:keys [text role prewrapped-lines turn-separator?
                       iteration-count duration-ms tokens cost status
                       llm-selected llm-actual llm-fallback? llm-routing-trace]
                :as message}
               max-w)

Memoized bubble-height*. Keyed by projected line identity when available; live progress keeps stable prewrapped body lines and only appends a cheap spinner row. Metadata that can add/remove the assistant footer is part of the key; otherwise a no-usage render can stale-cache the shorter height before usage arrives.

Memoized `bubble-height*`. Keyed by projected line identity when
available; live progress keeps stable prewrapped body lines and only
appends a cheap spinner row. Metadata that can add/remove the
assistant footer is part of the key; otherwise a no-usage render can
stale-cache the shorter height before usage arrives.
sourceraw docstring

bubble-height*clj

(bubble-height* {:keys [text role prewrapped-lines status] :as message} max-w)

Uncached calculation: rows a chat message will consume without drawing. label(1) + optional top-pad(1, user only) + wrapped-lines

  • optional bottom-pad(1, user only) + optional meta top margin/footer(0|2)
  • gap(1). Mirrors draw-chat-bubble!'s wrap width (bubble-w - 2*h-pad) so layout math stays consistent across the height calc and the draw.
Uncached calculation: rows a chat message will consume without drawing.
label(1) + optional top-pad(1, user only) + wrapped-lines
+ optional bottom-pad(1, user only) + optional meta top margin/footer(0|2)
+ gap(1).
Mirrors `draw-chat-bubble!`'s wrap width (`bubble-w - 2*h-pad`) so
layout math stays consistent across the height calc and the draw.
sourceraw docstring

cache-sizeclj

(cache-size)

Current number of entries (handy for tests/diagnostics).

Current number of entries (handy for tests/diagnostics).
sourceraw docstring

draw-chat-bubble!clj

(draw-chat-bubble! g
                   {:keys [role text timestamp status slash?] :as message}
                   start-row
                   left
                   max-w
                   &
                   [{:keys [viewport-top viewport-h]
                     :or {viewport-top 0 viewport-h 0}}])

Draw a chat message at the given row. No border, no bubble container. message is a map: {:role :user|:assistant, :text str, :timestamp #inst} Optional :duration-ms for assistant response time. left and max-w define the horizontal bounds.

Layout (no outer border, no horizontal rule under the label, both roles left-anchored): [role-label] [timestamp] [content lines, each with role bg fill] [meta line, dimmed, when present] [blank gap]

User content rows get a subtle blue-gray background block (user-bubble-bg) to visually separate user input from the rest of the session. Assistant content rows render on terminal bg - the only fills come from inline marker zones (code blocks, answer-bg, etc.).

Returns the number of screen rows consumed (including spacing).

Extra params: viewport-top and viewport-h describe the absolute screen window the messages-area paints into. They’re consulted by the per-row click-region painters to decide whether to register a click region for an off-screen row (they don’t). Callers that paint outside draw-messages-area! (tests, REPL exploration) can pass 0 / 0 to disable click registration.

Draw a chat message at the given row. No border, no bubble container.
`message` is a map: {:role :user|:assistant, :text str, :timestamp #inst}
Optional `:duration-ms` for assistant response time.
`left` and `max-w` define the horizontal bounds.

Layout (no outer border, no horizontal rule under the label,
both roles left-anchored):
  [role-label]                              [timestamp]
  [content lines, each with role bg fill]
  [meta line, dimmed, when present]
  [blank gap]

User content rows get a subtle blue-gray background block
(`user-bubble-bg`) to visually separate user input from the rest of
the session. Assistant content rows render on terminal bg -
the only fills come from inline marker zones (code blocks,
answer-bg, etc.).

Returns the number of screen rows consumed (including spacing).

Extra params:
  `viewport-top` and `viewport-h` describe the absolute screen
  window the messages-area paints into. They’re consulted by the
  per-row click-region painters to decide whether to register a
  click region for an off-screen row (they don’t).
  Callers that paint outside `draw-messages-area!` (tests, REPL
  exploration) can pass `0 / 0` to disable click registration.
sourceraw docstring

draw-detail-labels!clj

(draw-detail-labels! g active? frozen)

Vim-style jump-label overlay for collapsible disclosures.

frozen is the [label region] assignment captured ONCE when the mode opened (cr/assign-labels on that frame), read from db — NOT re-derived per frame. Freezing matters mid-turn: a live stream keeps re-registering cr/current as the trace grows, so a per-frame assign-labels would reshuffle the letters under the user's fingers and race the keypress. With a frozen map the letter→fold binding is stable. Each badge is re-anchored to its fold's CURRENT painted position — the region is matched by [session-id node-id] against this frame's cr/current, so the letter tracks the fold as the transcript scrolls and is simply dropped when the fold scrolls off. Falls back to a live assign-labels when no frozen set is present (the command-palette entry can open the mode from a dialog frame that had nothing to freeze). Inactive / empty ⇒ paints nothing.

Called by the full-frame painter right AFTER cr/commit-frame!, so cr/current holds this frame's freshly-registered regions. The input handler resolves a typed letter against the SAME frozen map, so a badge and its keypress always point at the same fold without shared mutable state.

Vim-style jump-label overlay for collapsible disclosures.

`frozen` is the `[label region]` assignment captured ONCE when the mode
opened (`cr/assign-labels` on that frame), read from `db` — NOT re-derived
per frame. Freezing matters mid-turn: a live stream keeps re-registering
`cr/current` as the trace grows, so a per-frame `assign-labels` would
reshuffle the letters under the user's fingers and race the keypress. With a
frozen map the letter→fold binding is stable. Each badge is re-anchored to
its fold's CURRENT painted position — the region is matched by
`[session-id node-id]` against this frame's `cr/current`, so the letter
tracks the fold as the transcript scrolls and is simply dropped when the
fold scrolls off. Falls back to a live `assign-labels` when no frozen set is
present (the command-palette entry can open the mode from a dialog frame
that had nothing to freeze). Inactive / empty ⇒ paints nothing.

Called by the full-frame painter right AFTER `cr/commit-frame!`, so
`cr/current` holds this frame's freshly-registered regions. The input
handler resolves a typed letter against the SAME frozen map, so a badge and
its keypress always point at the same fold without shared mutable state.
sourceraw docstring

draw-dialog!clj

(draw-dialog! g cols rows title body)
(draw-dialog! g cols rows title body max-w)

Draw a centered confirmation dialog with text wrapping. body can be a string or vec of strings. Long lines are wrapped to fit. Optional max-w limits dialog width (defaults to 60% of screen).

Draw a centered confirmation dialog with text wrapping.
`body` can be a string or vec of strings. Long lines are wrapped to fit.
Optional `max-w` limits dialog width (defaults to 60% of screen).
sourceraw docstring

draw-input-box!clj

(draw-input-box! g input box-top text-rows cols hint)

Draw bordered input area with internal padding. Returns [cursor-col cursor-row] in screen coords.

Long logical lines are SOFT-WRAPPED at the box width: typing past the right edge flows onto the next visual row instead of scrolling the line horizontally. The logical model ({:lines :crow :ccol}) is unchanged - wrapping is a render-time projection only.

hint optionally embeds a short label in the top border. Runtime keybinding helpers live in the echo area (footer/draw-echo-area!), not here, so input/editor paint stays isolated from footer chrome.

No left/right side rails: the input area is framed by top and bottom rules only, so the typing zone sits flush with the message column on either side and the eye tracks the prompt directly without -noise.

Draw bordered input area with internal padding. Returns
[cursor-col cursor-row] in screen coords.

Long logical lines are SOFT-WRAPPED at the box width: typing past
the right edge flows onto the next visual row instead of scrolling
the line horizontally. The logical model (`{:lines :crow :ccol}`)
is unchanged - wrapping is a render-time projection only.

`hint` optionally embeds a short label in the top border. Runtime
keybinding helpers live in the echo area (`footer/draw-echo-area!`), not here,
so input/editor paint stays isolated from footer chrome.

No left/right side rails: the input area is framed by top and bottom
rules only, so the typing zone sits flush with the message column on
either side and the eye tracks the prompt directly without `│`-noise.
sourceraw docstring

draw-messages-area!clj

(draw-messages-area! g layout box-top box-bottom cols)

Draw structured chat messages as left-aligned blocks inside a clean, border-less scrolling area.

layout is a virtual-layout plan produced by com.blockether.vis.ext.channel-tui.virtual/layout. Carries {:total-h :eff-scroll :visible} - the painter only iterates :visible (the messages whose interval intersects the viewport), so off-screen bubbles never trigger format-answer-with-thinking / wrap-text / markdown->lines. That's the single largest user-visible win for cold-opening long sessions: cold paint drops from O(N x trace-size) to O(visible x trace-size).

No outer box, no title bar - just a vertical column of messages with generous side gutters. The right gutter doubles as scrollbar space when the session overflows. The session title (if any) is surfaced via the input-box bottom status line, not here.

Draw structured chat messages as left-aligned blocks inside a clean,
border-less scrolling area.

`layout` is a virtual-layout plan produced by
`com.blockether.vis.ext.channel-tui.virtual/layout`. Carries
`{:total-h :eff-scroll :visible}` - the painter only iterates
`:visible` (the messages whose interval intersects the viewport),
so off-screen bubbles never trigger `format-answer-with-thinking`
/ `wrap-text` / `markdown->lines`. That's the single largest
user-visible win for cold-opening long sessions: cold paint
drops from O(N x trace-size) to O(visible x trace-size).

No outer box, no title bar - just a vertical column of messages with
generous side gutters. The right gutter doubles as scrollbar space
when the session overflows. The session title (if any) is
surfaced via the input-box bottom status line, not here.
sourceraw docstring

draw-messages-box!clj

(draw-messages-box! g messages box-top box-bottom cols scroll)

Draw bordered message area with top-anchored scrollable messages.

Draw bordered message area with top-anchored scrollable messages.
sourceraw docstring

draw-slash-command-suggestions!clj

(draw-slash-command-suggestions! g suggestions input-top cols)
(draw-slash-command-suggestions! g suggestions input-top cols selected-index)

Overlay fuzzy slash-command suggestions immediately above the input box.

Visual stack from top to bottom: (top margin row — terminal-bg gap) [Slash commands … Enter/Tab complete] ; title bar (dialog-title-bg accent) ───────────────────────────────────────── ; border under title (dialog-border) suggestion rows…

The accent title + under-rule signal this is a menu, matching the draw-dialog-chrome! chrome idiom but in overlay form (no side rails, since the input box below has no side rails either).

The whole overlay is inset by INPUT_BORDER_HORIZONTAL_PAD cols on each side so the accent stripe and rule line up exactly with the input box's top/bottom rules below.

Overlay fuzzy slash-command suggestions immediately above the input box.

Visual stack from top to bottom:
  (top margin row — terminal-bg gap)
  [Slash commands  …  Enter/Tab complete]   ; title bar (dialog-title-bg accent)
  ─────────────────────────────────────────   ; border under title (dialog-border)
  suggestion rows…

The accent title + under-rule signal `this is a menu`, matching the
`draw-dialog-chrome!` chrome idiom but in overlay form (no side
rails, since the input box below has no side rails either).

The whole overlay is inset by `INPUT_BORDER_HORIZONTAL_PAD` cols on
each side so the accent stripe and rule line up exactly with the
input box's top/bottom rules below.
sourceraw docstring

error-signatureclj

(error-signature entry)

Stable comparison key for two trace :error maps. Returns nil for non-error iterations (so they never collapse).

Iter-level :error is the canonical surface; iterations whose only content is a zero-code form carrying the transport error (the provider-truncated-stream shape) hash with the same key so they collapse alongside true iter-level errors.

Public so the virtual-layout pre-pass can squash runs of error-only assistant messages across turns using the same key the iteration-level collapser uses.

Stable comparison key for two trace `:error` maps. Returns nil for
non-error iterations (so they never collapse).

Iter-level `:error` is the canonical surface; iterations whose
only content is a zero-code form carrying the transport error
(the provider-truncated-stream shape) hash with the same key so
they collapse alongside true iter-level errors.

Public so the virtual-layout pre-pass can squash runs of
error-only assistant messages across turns using the same key
the iteration-level collapser uses.
sourceraw docstring

fill-background!clj

(fill-background! g cols rows)

Fill entire screen with the terminal background color.

Fill entire screen with the terminal background color.
sourceraw docstring

format-answer-markdownclj

(format-answer-markdown answer bubble-w)
(format-answer-markdown answer bubble-w opts)
source

format-answer-markdown*clj

(format-answer-markdown* answer bubble-w)
source

format-answer-markdown-dataclj

(format-answer-markdown-data answer bubble-w)
(format-answer-markdown-data answer bubble-w opts)
source

format-answer-markdown-data*clj

(format-answer-markdown-data* answer bubble-w opts)
source

format-answer-with-thinkingclj

(format-answer-with-thinking answer trace bubble-w)
(format-answer-with-thinking answer trace bubble-w settings)
(format-answer-with-thinking answer trace bubble-w settings confidence)
(format-answer-with-thinking answer
                             trace
                             bubble-w
                             settings
                             confidence
                             cancelled?)
(format-answer-with-thinking answer
                             trace
                             bubble-w
                             settings
                             confidence
                             cancelled?
                             opts)
source

format-answer-with-thinking*clj

(format-answer-with-thinking* answer
                              trace
                              bubble-w
                              settings
                              confidence
                              cancelled?)
source

format-answer-with-thinking-dataclj

(format-answer-with-thinking-data answer trace bubble-w)
(format-answer-with-thinking-data answer trace bubble-w settings)
(format-answer-with-thinking-data answer trace bubble-w settings confidence)
(format-answer-with-thinking-data answer
                                  trace
                                  bubble-w
                                  settings
                                  confidence
                                  cancelled?)
(format-answer-with-thinking-data answer
                                  trace
                                  bubble-w
                                  settings
                                  confidence
                                  cancelled?
                                  opts)
source

format-answer-with-thinking-data*clj

(format-answer-with-thinking-data* answer
                                   trace
                                   bubble-w
                                   settings
                                   _confidence
                                   cancelled?
                                   opts)

Uncached implementation. Returns {:text :lines :line-meta} so the bubble painter can keep clickable summary-row metadata aligned with the already-wrapped lines.

STRICT: answer is canonical answer-IR ([:ir & nodes]) or nil.

Uncached implementation. Returns `{:text :lines :line-meta}` so the
bubble painter can keep clickable summary-row metadata aligned with
the already-wrapped lines.

STRICT: `answer` is canonical answer-IR (`[:ir & nodes]`) or nil.
sourceraw docstring

format-iteration-entryclj

(format-iteration-entry entry code-width iteration-number & [opts])
source

golden-dialog-sizeclj

(golden-dialog-size cols rows content-w content-h)

Compute golden-ratio dialog size [w h] sized to fit content. content-w = widest content line (chars). content-h = content line count. Only expands WIDTH when content is too narrow for golden ratio. Never inflates height beyond what content needs. Clamped to terminal bounds.

Compute golden-ratio dialog size [w h] sized to fit content.
`content-w` = widest content line (chars). `content-h` = content line count.
Only expands WIDTH when content is too narrow for golden ratio.
Never inflates height beyond what content needs.
Clamped to terminal bounds.
sourceraw docstring

input-pad-yclj

Internal vertical padding (rows above/below text) inside the input box. Set to 0: the top + bottom rule lines already provide all the visual breathing room the editor needs; an extra padded row on each side just wasted vertical space and pushed the cursor down a line. The previous value (1) made an empty editor render four rows tall (rule + pad + line + pad + rule) when three is the right minimum.

Internal vertical padding (rows above/below text) inside the input
box. Set to 0: the top + bottom rule lines already provide all the
visual breathing room the editor needs; an extra padded row on each
side just wasted vertical space and pushed the cursor down a line.
The previous value (1) made an empty editor render four rows tall
(rule + pad + line + pad + rule) when three is the right minimum.
sourceraw docstring

input-text-wclj

(input-text-w cols)

Visible text width (in columns) inside the input box for a given terminal cols. Single source of truth so screen.clj can compute wrapped row counts and draw-input-box! can render with the same wrap point.

The input box is SIDELESS (top/bottom rules only, no rails) and input-pad-x is now 0, so the typing zone spans the full terminal width - text-w = cols (clamped to >=1).

Visible text width (in columns) inside the input box for a given
terminal `cols`. Single source of truth so screen.clj can compute
wrapped row counts and `draw-input-box!` can render with the same
wrap point.

The input box is SIDELESS (top/bottom rules only, no `│` rails)
and `input-pad-x` is now 0, so the typing zone spans the full
terminal width - `text-w` = `cols` (clamped to >=1).
sourceraw docstring

input-visual-row-countclj

(input-visual-row-count lines text-w)

Total visual rows occupied by every logical line in the input editor when soft-wrapped at text-w cols.

Total visual rows occupied by every logical line in the input
editor when soft-wrapped at `text-w` cols.
sourceraw docstring

invalidate-cache!clj

(invalidate-cache!)

Drop every cached projection. Call on settings changes that the key tuple doesn't capture, or from tests.

Drop every cached projection. Call on settings changes that the
key tuple doesn't capture, or from tests.
sourceraw docstring

message-detail-expansions-keyclj

(message-detail-expansions-key session-id message detail-expansions)

Per-message disclosure-expansion fingerprint for the height cache: the subset of detail-expansions whose disclosure node-ids belong to message's turn (matched by the :t<short-id> token every node-id carries), or :expand-all when the global expand flag is set. A bulk :baseline (C-x [ collapse-all / C-x ] expand-all) is prepended so a bulk op busts every message's cached height; with no bulk active the key keeps its original shape so unrelated fold clicks don't churn the cache. Lets the height cache key a message's height to its OWN expand/collapse state without busting every other message's cached height — the SAME per-turn scoping this file already uses for its projection cache.

Per-message disclosure-expansion fingerprint for the height cache: the
subset of `detail-expansions` whose disclosure node-ids belong to
`message`'s turn (matched by the `:t<short-id>` token every node-id
carries), or `:expand-all` when the global expand flag is set. A bulk
`:baseline` (C-x [ collapse-all / C-x ] expand-all) is prepended so a bulk
op busts every message's cached height; with no bulk active the key keeps
its original shape so unrelated fold clicks don't churn the cache. Lets the
height cache key a message's height to its OWN expand/collapse state
without busting every other message's cached height — the SAME per-turn
scoping this file already uses for its projection cache.
sourceraw docstring

MESSAGE_MARGIN_BOTTOMclj

source

MESSAGE_MARGIN_LEFTclj

source

MESSAGE_MARGIN_RIGHTclj

source

MESSAGE_MARGIN_TOPclj

source

MESSAGE_SIDE_PADclj

source

progress->lines-dataclj

(progress->lines-data progress bubble-w settings)
(progress->lines-data progress bubble-w settings extra)

Build prewrapped lines for the live progress placeholder bubble.

The bubble lives in the assistant slot (right where the final answer will land), so the user sees the agent thinking/working in place instead of a status line wedged into the input box.

Layout while loading (spinner ALWAYS at the bottom): <iteration trace, if iterations exist and not hidden> <blank> [spinner] phase... elapsed / Esc to cancel

The activity row sits last so it tracks the natural reading direction - trace history flows top-down like a transcript, and what is happening RIGHT NOW lives where the cursor is about to write next. Putting the spinner above the trace forced the user to look at a moving line, then drop their eye further down to read static history that doesn't change. Inverted now.

progress is the :progress slot from app-db: {:iterations [...]}. bubble-w is the outer bubble width in chars (we subtract the bubble's symmetric inner padding to match draw-chat-bubble!). settings is the display settings map: {:show-thinking bool :show-iterations bool}. extra carries: :now-ms - System/currentTimeMillis from the render thread (drives the spinner frame); defaults to current ms :turn-start-ms - wall-clock start, used for elapsed time :cancelling? - true once Esc was pressed :session-id - current session id; enables live detail rows :session-turn-id - optional turn id, when known :detail-expansions - detail expansion state keyed by session/node

Build prewrapped lines for the live progress placeholder bubble.

The bubble lives in the assistant slot (right where the final
answer will land), so the user sees the agent thinking/working in
place instead of a status line wedged into the input box.

Layout while loading (spinner ALWAYS at the bottom):
  <iteration trace, if iterations exist and not hidden>
  <blank>
  [spinner] phase... elapsed / Esc to cancel

The activity row sits last so it tracks the natural reading
direction - trace history flows top-down like a transcript, and
what is happening RIGHT NOW lives where the cursor is about to
write next. Putting the spinner above the trace forced the user
to look at a moving line, then drop their eye further down to
read static history that doesn't change. Inverted now.

`progress` is the `:progress` slot from app-db: `{:iterations [...]}`.
`bubble-w` is the outer bubble width in chars (we subtract the
bubble's symmetric inner padding to match draw-chat-bubble!).
`settings` is the display settings map: `{:show-thinking bool :show-iterations bool}`.
`extra` carries:
  :now-ms         - `System/currentTimeMillis` from the render thread
                    (drives the spinner frame); defaults to current ms
  :turn-start-ms - wall-clock start, used for elapsed time
  :cancelling?    - true once Esc was pressed
  :session-id - current session id; enables live detail rows
  :session-turn-id - optional turn id, when known
  :detail-expansions - detail expansion state keyed by session/node
sourceraw docstring

progress->textclj

(progress->text progress bubble-w settings)
(progress->text progress bubble-w settings extra)

Build the text body of the live progress placeholder bubble.

Build the text body of the live progress placeholder bubble.
sourceraw docstring

repeat-strclj

(repeat-str ch n)

Allocate a String of n copies of ch. Drop-in replacement for (apply str (repeat n ch)) - bypasses lazy-seq + per-char StringBuilder appends, so the box-border + padding code paths stop showing up on the flame graph.

Allocate a String of `n` copies of `ch`. Drop-in replacement for
`(apply str (repeat n ch))` - bypasses lazy-seq + per-char
StringBuilder appends, so the box-border + padding code paths stop
showing up on the flame graph.
sourceraw docstring

spinner-frameclj

(spinner-frame now-ms)

Pick the spinner glyph for now-ms. Pure - same input always returns the same frame, which makes the render loop testable.

Pick the spinner glyph for `now-ms`. Pure - same input always
returns the same frame, which makes the render loop testable.
sourceraw docstring

total-messages-heightclj

(total-messages-height messages max-w)

Calculate total row height for a vec of structured messages.

Calculate total row height for a vec of structured messages.
sourceraw docstring

visual-rows-for-lineclj

(visual-rows-for-line line text-w)

How many visual rows logical line line occupies when soft-wrapped at text-w cols. Empty line still counts as 1 (a typable row).

How many visual rows logical line `line` occupies when soft-wrapped
at `text-w` cols. Empty line still counts as 1 (a typable row).
sourceraw docstring

wrap-messagesclj

(wrap-messages messages max-width)

Wrap a vec of display lines to fit within max-width. Returns flat vec of wrapped lines.

Wrap a vec of display lines to fit within max-width. Returns flat vec of wrapped lines.
sourceraw docstring

wrap-textclj

(wrap-text text max-width)

Memoized wrap-text*. Keyed by source-string identity so finalized message texts hit the cache across frames (their string instance is stable on the immutable message map).

Memoized `wrap-text*`. Keyed by source-string identity so finalized
message texts hit the cache across frames (their string instance is
stable on the immutable message map).
sourceraw docstring

wrap-text*clj

(wrap-text* text max-width)

Uncached implementation. Prefer wrap-text everywhere except inside wrap-text itself.

Uncached implementation. Prefer `wrap-text` everywhere except inside
`wrap-text` itself.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close