TUI rendering for :human-input/request — the terminal half of the engine's
typed human-input pause primitive (com.blockether.vis.internal.human-input).
The engine parks an extension thread and publishes a request VIEW; this
namespace turns that view into a form the operator can fill in, and hands
the collected values back through submit-human-input! / cancel-human-input!.
Everything except paint! is PURE: init-form builds the form model
from a request view, handle-event is a reducer over one normalized
keystroke, and form-rows is the paint plan. The Lanterna surface only
shows up in key->event (decoding) and paint! (drawing), so the whole
interaction is testable without a terminal.
Navigation is a flat list of STOPS — one per text field, one per checkbox, and one per select/multiselect OPTION — so ↑/↓/Tab walks the form the way the settings dialog walks its rows.
TUI rendering for `:human-input/request` — the terminal half of the engine's typed human-input pause primitive (`com.blockether.vis.internal.human-input`). The engine parks an extension thread and publishes a request VIEW; this namespace turns that view into a form the operator can fill in, and hands the collected values back through `submit-human-input!` / `cancel-human-input!`. Everything except [[paint!]] is PURE: [[init-form]] builds the form model from a request view, [[handle-event]] is a reducer over one normalized keystroke, and [[form-rows]] is the paint plan. The Lanterna surface only shows up in [[key->event]] (decoding) and [[paint!]] (drawing), so the whole interaction is testable without a terminal. Navigation is a flat list of STOPS — one per text field, one per checkbox, and one per select/multiselect OPTION — so ↑/↓/Tab walks the form the way the settings dialog walks its rows.
(action-bar {:keys [stops focus]})The request's own buttons as ONE row: the solid Submit pill, then the muted
Cancel one.
PRIMARY vs SECONDARY is a SEMANTIC ranking, not a focus state — submit is the
confirming action and keeps the ink fill wherever the cursor is, so walking the
caps never promotes Cancel to look like the default. :is-focused says which
cap the cursor sits on, and the painter answers it with the project-wide •
marker instead of a second colour.
No chord travels with a button: a cap is a focus stop reached with ↑/↓ like
every other row of the form. The hint bar one row below stays silent about
submit and cancel too (hint) — it used to reprint these very labels as
Enter submit · Esc cancel, two rows of chrome for one meaning.
PINNED by the painter under the scrolling body instead of trailing it, so a form taller than the band can never push the two controls that END the pause off the screen — the same reason the companion pins them in its footer.
The request's own buttons as ONE row: the solid ` Submit ` pill, then the muted ` Cancel ` one. PRIMARY vs SECONDARY is a SEMANTIC ranking, not a focus state — submit is the confirming action and keeps the ink fill wherever the cursor is, so walking the caps never promotes Cancel to look like the default. `:is-focused` says which cap the cursor sits on, and the painter answers it with the project-wide `•` marker instead of a second colour. No chord travels with a button: a cap is a focus stop reached with ↑/↓ like every other row of the form. The hint bar one row below stays silent about submit and cancel too ([[hint]]) — it used to reprint these very labels as `Enter submit · Esc cancel`, two rows of chrome for one meaning. PINNED by the painter under the scrolling body instead of trailing it, so a form taller than the band can never push the two controls that END the pause off the screen — the same reason the companion pins them in its footer.
(action-stops request)The request's own confirm/cancel buttons, as focus stops of their own.
A transient that can only be accepted by a chord is a transient nobody
accepts: these are the LAST stops, so ↓ walks off the final field straight
onto the Submit cap and Enter presses it. Cancel only exists when the request
allows it — an uncancellable ask must not offer a button that does nothing.
The request's own confirm/cancel buttons, as focus stops of their own. A transient that can only be accepted by a chord is a transient nobody accepts: these are the LAST stops, so ↓ walks off the final field straight onto the `Submit` cap and Enter presses it. Cancel only exists when the request allows it — an uncancellable ask must not offer a button that does nothing.
(default-value {:keys [type default] :as field})The value a field starts with: its declared :default coerced to the shape
the field type submits, else that type's empty value.
The value a field starts with: its declared `:default` coerced to the shape the field type submits, else that type's empty value.
(focused-row rows)Index of the row carrying the focused stop, or 0.
Index of the row carrying the focused stop, or 0.
(focused-stop {:keys [stops focus]})The stop under the cursor, or nil for a form with no stops.
The stop under the cursor, or nil for a form with no stops.
(form-rows form)(form-rows {:keys [request focus stops] :as form} text-w)PURE paint plan: the ordered rows the dialog body draws for form.
Fields only — the confirm/cancel buttons live in action-bar, pinned below
this body, so they are never scrolled away.
text-w is the column budget prose gets — the request's description and
every field's wrap to it. Omit it (or pass nil) for the unwrapped plan.
PURE paint plan: the ordered rows the dialog body draws for `form`. Fields only — the confirm/cancel buttons live in [[action-bar]], pinned below this body, so they are never scrolled away. `text-w` is the column budget prose gets — the request's description and every field's wrap to it. Omit it (or pass nil) for the unwrapped plan.
(handle-event form {:keys [kind char] :as _event})PURE reducer: apply ONE normalized event to form.
Returns {:form form' :action action} where action is nil (stay open),
:submit (ask the engine to accept submit-values), or :cancel.
PURE reducer: apply ONE normalized event to `form`.
Returns `{:form form' :action action}` where `action` is nil (stay open),
`:submit` (ask the engine to accept `submit-values`), or `:cancel`.(hint form)Hint-bar pairs for form — TYPING chords only, spelled the canonical dialog
way: chord, then a lowercase action.
NAVIGATION IS NOT A HINT. ↑/↓ move used to lead this list on every single
pause: a permanent row of chrome teaching the one thing every terminal
operator already knows, printed under a form whose focus ring already shows
where the cursor is. So the bar is EMPTY unless the FOCUSED stop accepts a
chord the band itself cannot show — Space on a toggle, digits in an OTP, ←/→
on a slider, Enter for a newline.
Submit and cancel are not here either: action-bar paints those two
controls one row above with their own chords ON the caps, so a hint pair for
either would print the same verb twice, a row apart. The one Enter that
belongs here is the multiline NEWLINE — that is a typing chord, and it is also
why the submit cap switches to ^S.
Hint-bar pairs for `form` — TYPING chords only, spelled the canonical dialog way: chord, then a lowercase action. NAVIGATION IS NOT A HINT. `↑/↓ move` used to lead this list on every single pause: a permanent row of chrome teaching the one thing every terminal operator already knows, printed under a form whose focus ring already shows where the cursor is. So the bar is EMPTY unless the FOCUSED stop accepts a chord the band itself cannot show — Space on a toggle, digits in an OTP, ←/→ on a slider, Enter for a newline. Submit and cancel are not here either: [[action-bar]] paints those two controls one row above with their own chords ON the caps, so a hint pair for either would print the same verb twice, a row apart. The one Enter that belongs here is the multiline NEWLINE — that is a typing chord, and it is also why the submit cap switches to `^S`.
(init-form request)Build the form model for a request VIEW. Text cursors start at end-of-text and focus starts on the first stop.
Build the form model for a request VIEW. Text cursors start at end-of-text and focus starts on the first stop.
(key->event key)Normalize one Lanterna keystroke into the event map handle-event takes,
or nil when the keystroke means nothing to a form (mouse, unknown chords).
Normalize one Lanterna keystroke into the event map [[handle-event]] takes, or nil when the keystroke means nothing to a form (mouse, unknown chords).
(paint! g cols rows form)(paint! g cols rows form content-top)Draw the human-input band for form INSIDE the session's own frame. Returns
the TerminalPosition the caller should place the terminal cursor at (the
focused text field), or nil when no text field has focus.
A magit-style TRANSIENT, not a modal. The band is bottom-anchored on the
session's bottom chrome — it takes over the prompt's rows and grows UPWARD
over the transcript, never past content-top — and reads ─── / bold title /
─── / the fields / the action bar / ─── / hint bar, the same chrome every
other transient in the TUI wears. The rule directly above the hint bar is the
host's closing rule, so the footer below the band is never swallowed.
The action bar is PINNED: only the fields scroll under it, so the Submit
and Cancel caps stay on screen for a form of any length.
TWO passes over the plan, because a scrollbar costs a column: the first plan sizes the band, and an overflowing one re-wraps one column narrower.
Draw the human-input band for `form` INSIDE the session's own frame. Returns the `TerminalPosition` the caller should place the terminal cursor at (the focused text field), or nil when no text field has focus. A magit-style TRANSIENT, not a modal. The band is bottom-anchored on the session's bottom chrome — it takes over the prompt's rows and grows UPWARD over the transcript, never past `content-top` — and reads `───` / bold title / `───` / the fields / the action bar / `───` / hint bar, the same chrome every other transient in the TUI wears. The rule directly above the hint bar is the host's closing rule, so the footer below the band is never swallowed. The action bar is PINNED: only the fields scroll under it, so the `Submit` and `Cancel` caps stay on screen for a form of any length. TWO passes over the plan, because a scrollbar costs a column: the first plan sizes the band, and an overflowing one re-wraps one column narrower.
(request-id form)The engine request id this form answers.
The engine request id this form answers.
(request<-wire wire)Rehydrate a request VIEW from the canonical snake_case wire map a
human_input.request SESSION event carries — the only shape a request takes
when the parked run lives in the serve daemon instead of this process. The
ENGINE owns that inverse (view<-wire); the terminal never keeps a second
field vocabulary.
Rehydrate a request VIEW from the canonical snake_case wire map a `human_input.request` SESSION event carries — the only shape a request takes when the parked run lives in the serve daemon instead of this process. The ENGINE owns that inverse (`view<-wire`); the terminal never keeps a second field vocabulary.
(session-id form)The gateway session whose run this form parks, or nil. A form built from a session event must be answered over the gateway that owns it.
The gateway session whose run this form parks, or nil. A form built from a session event must be answered over the gateway that owns it.
(set-errors form errors)The ONE way a form turns red: the engine's per-field verdict on a CONFIRMATION. Focus lands on the first offending field, and the next touch of that field clears its message (see [[put-text]], [[nudge-range]], [[toggle-stop]]) — so the form is pristine again until the next confirmation.
The ONE way a form turns red: the engine's per-field verdict on a CONFIRMATION. Focus lands on the first offending field, and the next touch of that field clears its message (see [[put-text]], [[nudge-range]], [[toggle-stop]]) — so the form is pristine again until the next confirmation.
(stops request)Flat vector of focus stops for request — the linear order ↑/↓/Tab walks.
Flat vector of focus stops for `request` — the linear order ↑/↓/Tab walks.
(submit-values form)The values map handed to submit-human-input! — keyed by field id.
The values map handed to `submit-human-input!` — keyed by field id.
(window-start rows visible)First plan row to draw so the focused field stays usable: the minimal scroll that keeps the focused row on screen, pulled up to that field's LABEL row when the window is too short to hold both — a bare unlabelled input row would not say which field is being edited.
First plan row to draw so the focused field stays usable: the minimal scroll that keeps the focused row on screen, pulled up to that field's LABEL row when the window is too short to hold both — a bare unlabelled input row would not say which field is being edited.
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 |