Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.human-input

Typed human-input requests — the pause/resume primitive an extension uses to ask the operator for structured values in the middle of a run.

An extension calls request! with a title and a vector of typed fields. The call BLOCKS the calling thread, publishes a :human-input/request channel event so the mounted channel can draw a dialog, and returns once a channel calls submit! / cancel!, or the request runs out of time.

A request either carries a DEADLINE (:timeout-ms, five minutes by default) or waits INDEFINITELY (:timeout-ms 0) — an extension that must not guess an answer says so and parks until the human is back at the keyboard, while one that can carry on alone names the wait it is willing to bill and gets a timeout answer when nobody came. Even an indefinite request cannot park a thread nobody can release: a request that reaches no mounted channel is answered undeliverable at once, and interrupting the surrounding turn cancels it.

This namespace PARSES: it takes either spelling of every key, looks an extension-supplied type name up in a CLOSED vocabulary (com.blockether.vis.internal.human-input.spec/field-types) instead of keyword-minting it, and names the key an author has to fix when it cannot. What it produces is DECLARED by clojure.spec in com.blockether.vis.internal.human-input.spec: every normalized field, every normalized request, and every answer handed back to a blocked extension — its VALUES included, each against the domain the field that asked for it declared — is checked against that contract, so an engine bug surfaces here instead of as a half-built dialog three namespaces away. Coercion and validation live in one place, so the value an extension receives already matches the declared type: a :checkbox yields a boolean, a :multiselect a vector of declared option values, a :select one declared option value.

Secrets never travel as plaintext. A :password and an :otp field both resolve to an opaque vis-secret:<uuid> handle; the plaintext stays in a process-local vault and is readable only through reveal-secret from the trusted extension side. Handles are what land in logs, transcripts and wire payloads, so a leaked event is worthless.

Typed human-input requests — the pause/resume primitive an extension uses to
ask the operator for structured values in the middle of a run.

An extension calls [[request!]] with a title and a vector of typed fields.
The call BLOCKS the calling thread, publishes a `:human-input/request`
channel event so the mounted channel can draw a dialog, and returns once a
channel calls [[submit!]] / [[cancel!]], or the request runs out of time.

A request either carries a DEADLINE (`:timeout-ms`, five minutes by default)
or waits INDEFINITELY (`:timeout-ms` 0) — an extension that must not guess an
answer says so and parks until the human is back at the keyboard, while one
that can carry on alone names the wait it is willing to bill and gets a
`timeout` answer when nobody came. Even an indefinite request cannot park a
thread nobody can release: a request that reaches no mounted channel is
answered `undeliverable` at once, and interrupting the surrounding turn
cancels it.

This namespace PARSES: it takes either spelling of every key, looks an
extension-supplied type name up in a CLOSED vocabulary
([[com.blockether.vis.internal.human-input.spec/field-types]]) instead of
`keyword`-minting it, and names the key an author has to fix when it cannot.
What it produces is DECLARED by `clojure.spec` in
`com.blockether.vis.internal.human-input.spec`: every normalized field, every
normalized request, and every answer handed back to a blocked extension — its
VALUES included, each against the domain the field that asked for it declared
— is checked against that contract, so an engine bug surfaces here instead of
as a half-built dialog three namespaces away. Coercion and validation live in
one place, so the value an extension receives already matches the declared
type: a `:checkbox` yields a boolean, a `:multiselect` a vector of declared
option values, a `:select` one declared option value.

Secrets never travel as plaintext. A `:password` and an `:otp` field both
resolve to an opaque
`vis-secret:<uuid>` handle; the plaintext stays in a process-local vault and
is readable only through [[reveal-secret]] from the trusted extension side.
Handles are what land in logs, transcripts and wire payloads, so a leaked
event is worthless.
raw docstring

answer->wireclj

(answer->wire answer)

Wire projection of a request! answer: snake_case string keys, JSON-safe values. :password and :otp values stay opaque handles.

Wire projection of a [[request!]] answer: snake_case string keys, JSON-safe
values. `:password` and `:otp` values stay opaque handles.
sourceraw docstring

cancel!clj

(cancel! request-id)
(cancel! request-id reason)

Cancel pending request request-id on the operator's behalf. Returns true when it was pending AND dismissable.

A request declared :is-cancellable false refuses here, so EVERY surface is refused alike — the TUI dialog, the companion app, any extension API. The only ways out of such a request are an accepted answer, its timeout, and cancel-all!.

Cancel pending request `request-id` on the operator's behalf. Returns true
when it was pending AND dismissable.

A request declared `:is-cancellable false` refuses here, so EVERY surface is
refused alike — the TUI dialog, the companion app, any extension API. The
only ways out of such a request are an accepted answer, its timeout, and
[[cancel-all!]].
sourceraw docstring

cancel-all!clj

(cancel-all!)
(cancel-all! reason)

Cancel every pending request. Returns how many were released. Used when a channel detaches or the session shuts down, so no thread stays parked — this one ignores :is-cancellable, because nothing is left to answer with.

Cancel every pending request. Returns how many were released. Used when a
channel detaches or the session shuts down, so no thread stays parked — this
one ignores `:is-cancellable`, because nothing is left to answer with.
sourceraw docstring

checkclj

(check request)

Why request is not a valid human-input spec, as ONE line, or nil when it is fine.

The very seam request! takes, minus the human: normalize-request runs for its refusals and its result is dropped, so an extension can prove a form it just built without mounting a dialog, publishing an event or parking a thread. Only a spec problem answers a reason -- an engine bug still throws.

Why `request` is not a valid human-input spec, as ONE line, or `nil` when it
is fine.

The very seam [[request!]] takes, minus the human: [[normalize-request]] runs
for its refusals and its result is dropped, so an extension can prove a form
it just built without mounting a dialog, publishing an event or parking a
thread. Only a spec problem answers a reason -- an engine bug still throws.
sourceraw docstring

check-jsonclj

(check-json request-json)

The strings-only mirror of check, for the Python boundary: a JSON request object in, a JSON verdict out -- {"is_valid": true, "error": null} or {"is_valid": false, "error": "<one line>"}.

Total by construction: unreadable JSON, a JSON array where an object belongs and a refused spec all come back as the same verdict shape, because the caller is a static check that must report every file rather than die on the first one.

The strings-only mirror of [[check]], for the Python boundary: a JSON request
object in, a JSON verdict out -- `{"is_valid": true, "error": null}` or
`{"is_valid": false, "error": "<one line>"}`.

Total by construction: unreadable JSON, a JSON array where an object belongs
and a refused spec all come back as the same verdict shape, because the
caller is a static check that must report every file rather than die on the
first one.
sourceraw docstring

coerce-valueclj

(coerce-value {:keys [type] :as field} value)

Coerce and validate one raw value against normalized field. Returns [:ok coerced] or [:error message].

Coerce and validate one raw `value` against normalized `field`. Returns
`[:ok coerced]` or `[:error message]`.
sourceraw docstring

coerce-valuesclj

(coerce-values fields values)

validate-values for a SUBMISSION: identical answer, except that accepted :password and :otp values are replaced with opaque vault handles.

[[validate-values]] for a SUBMISSION: identical answer, except that accepted
`:password` and `:otp` values are replaced with opaque vault handles.
sourceraw docstring

default-timeout-msclj

How long a request waits when its spec says nothing: five minutes — long enough for a human who is reading, short enough that a dialog nobody noticed does not park an extension all afternoon. A caller who wants another budget names its own :timeout-ms, or no-timeout-ms to wait as long as it takes.

How long a request waits when its spec says nothing: five minutes — long
enough for a human who is reading, short enough that a dialog nobody noticed
does not park an extension all afternoon. A caller who wants another budget
names its own `:timeout-ms`, or [[no-timeout-ms]] to wait as long as it takes.
sourceraw docstring

forget-secret!clj

(forget-secret! handle)

Drop the plaintext behind handle. Returns true when something was dropped.

Drop the plaintext behind `handle`. Returns true when something was dropped.
sourceraw docstring

forget-secrets!clj

(forget-secrets!)

Empty the vault. Returns how many plaintexts were dropped.

Empty the vault. Returns how many plaintexts were dropped.
sourceraw docstring

indefinite-timeout?clj

(indefinite-timeout? timeout-ms)

True when timeout-ms is no-timeout-ms: the request waits indefinitely.

True when `timeout-ms` is [[no-timeout-ms]]: the request waits indefinitely.
sourceraw docstring

input-fieldsclj

(input-fields fields)

Every ANSWERABLE field in fields, depth-first in the order a surface draws them. A group carries no value, so it is walked through and never returned: this is the sequence that keys :values, and the reason a layout change can never change an extension's answer map.

The hot path of the whole module — every keystroke on every surface re-validates through here — so it is a transient walk, not a lazy one.

Every ANSWERABLE field in `fields`, depth-first in the order a surface draws
them. A group carries no value, so it is walked through and never returned:
this is the sequence that keys `:values`, and the reason a layout change can
never change an extension's answer map.

The hot path of the whole module — every keystroke on every surface
re-validates through here — so it is a transient walk, not a lazy one.
sourceraw docstring

no-timeout-msclj

The :timeout-ms that means NO deadline at all: request! parks until a human answers, a surface cancels, or the surrounding turn is interrupted.

Nothing infers it. A spec asks for it explicitly with timeout_ms 0, so a spec that merely FORGOT the key still expires at default-timeout-ms instead of silently pinning the run on an operator who walked away.

The `:timeout-ms` that means NO deadline at all: [[request!]] parks until a
human answers, a surface cancels, or the surrounding turn is interrupted.

Nothing infers it. A spec asks for it explicitly with `timeout_ms` 0, so a
spec that merely FORGOT the key still expires at [[default-timeout-ms]]
instead of silently pinning the run on an operator who walked away.
sourceraw docstring

normalize-fieldclj

(normalize-field field)

Validate one FIELD spec — a leaf holding exactly one answer — and return its internal form. Throws ex-info with :type :vis/human-input-invalid-field on a bad spec.

Three names, three jobs, and every field ends up with all three:

  • :name is how the answer is KEYED — the key the extension reads back out of :values (:id is the historical alias, accepted and still emitted).
  • :label is how the field is SHOWN. Never blank: a field without one shows its :name, so no surface ever draws a bare, unlabelled input.
  • :description is the prose under that label, rendered in italic by every dialog.

A layout group is not a field and never arrives here: normalize-node routes it to [[normalize-group]] before a single value key is parsed.

Validate one FIELD spec — a leaf holding exactly one answer — and return its
internal form. Throws `ex-info` with `:type :vis/human-input-invalid-field`
on a bad spec.

Three names, three jobs, and every field ends up with all three:

  - `:name` is how the answer is KEYED — the key the extension reads back out
    of `:values` (`:id` is the historical alias, accepted and still emitted).
  - `:label` is how the field is SHOWN. Never blank: a field without one
    shows its `:name`, so no surface ever draws a bare, unlabelled input.
  - `:description` is the prose under that label, rendered in italic by every
    dialog.

A layout group is not a field and never arrives here: [[normalize-node]]
routes it to [[normalize-group]] before a single value key is parsed.
sourceraw docstring

normalize-nodeclj

(normalize-node node)

Validate one node of a request's field TREE and return its internal form.

This is the fork the whole shape hangs on, and it is taken ONCE, up here: a group is control flow — it arranges the children below it and holds no answer — a heading or a paragraph is pure decoration that neither asks nor arranges, and anything else is a field holding exactly one answer. Deciding it above the three normalizers is why normalize-field never has to ask whether it is really layout or ink, and why no value path below carries a branch for a node that can never take one.

Validate one node of a request's field TREE and return its internal form.

This is the fork the whole shape hangs on, and it is taken ONCE, up here: a
`group` is control flow — it arranges the children below it and holds no
answer — a `heading` or a `paragraph` is pure decoration that neither asks
nor arranges, and anything else is a field holding exactly one answer.
Deciding it above the three normalizers is why [[normalize-field]] never has
to ask whether it is really layout or ink, and why no value path below
carries a branch for a node that can never take one.
sourceraw docstring

normalize-requestclj

(normalize-request request)

Validate a human-input request spec and return its internal form. Throws ex-info with :type :vis/human-input-invalid-request (or :vis/human-input-invalid-field) on a bad spec.

Validate a human-input request spec and return its internal form. Throws
`ex-info` with `:type :vis/human-input-invalid-request` (or
`:vis/human-input-invalid-field`) on a bad spec.
sourceraw docstring

pending-requestclj

(pending-request request-id)

The pending request request-id, as a view, or nil.

The pending request `request-id`, as a view, or nil.
sourceraw docstring

pending-requestsclj

(pending-requests)

Snapshot of the currently pending requests, oldest first. Views only.

Snapshot of the currently pending requests, oldest first. Views only.
sourceraw docstring

request!clj

(request! request)

Ask the operator for typed values and BLOCK until they answer.

request is a spec map — :title, :fields, optional :description, :submit-label, :cancel-label, :is-cancellable, :timeout-ms, :channel-ids (string keys from the Python boundary work too).

Every field carries :name, :type, :label and an optional :description. :name keys the answer in :values, :label is what the dialog shows above the input, and :description is the italic line under that label — see normalize-field.

Publishes a :human-input/request channel event, waits for submit! / cancel!, and always returns a map, either

:is-submitted true, :reason "submitted", plus :request-id and :values

or

:is-submitted false, :reason "cancelled"/"timeout"/"undeliverable"/…, plus :request-id

"undeliverable" is the honest answer when the event reached ZERO listeners: no surface is mounted on any channel the request names, so no dialog can be drawn and nobody can answer. That returns AT ONCE and logs an error naming the request — parking the caller for the full timeout would report a run nobody was ever shown as if a human had ignored it.

:timeout-ms is the wait this call is willing to bill: default-timeout-ms when the spec says nothing, or no-timeout-ms (0) to wait INDEFINITELY. A finite wait that runs out settles the request itself — the dialog closes on every surface and the answer reads timeout, so the extension resumes with one clear fixed outcome instead of a half-open form nobody can answer. An indefinite wait never gives up on the human: only an answer, a cancel or an interrupt releases it.

:password and :otp values in :values are opaque handles — see reveal-secret.

A request MUST name the session it parks — :session-id, or the session of the extension environment currently executing. A run nobody can attribute is a run the companion app is never told about: the gateway bridge turns the request into a session event, and a session event with no session has nowhere to go, so only a surface mounted in this very process could ever answer it. That is refused here, before anything blocks.

Ask the operator for typed values and BLOCK until they answer.

`request` is a spec map — `:title`, `:fields`, optional `:description`,
`:submit-label`, `:cancel-label`, `:is-cancellable`, `:timeout-ms`,
`:channel-ids` (string keys from the Python boundary work too).

Every field carries `:name`, `:type`, `:label` and an optional
`:description`. `:name` keys the answer in `:values`, `:label` is what the
dialog shows above the input, and `:description` is the italic line under
that label — see [[normalize-field]].

Publishes a `:human-input/request` channel event, waits for [[submit!]] /
[[cancel!]], and always returns a map, either

  :is-submitted true, :reason "submitted", plus :request-id and :values

or

  :is-submitted false, :reason "cancelled"/"timeout"/"undeliverable"/…,
  plus :request-id

`"undeliverable"` is the honest answer when the event reached ZERO
listeners: no surface is mounted on any channel the request names, so no
dialog can be drawn and nobody can answer. That returns AT ONCE and logs an
error naming the request — parking the caller for the full timeout would
report a run nobody was ever shown as if a human had ignored it.

`:timeout-ms` is the wait this call is willing to bill: [[default-timeout-ms]]
when the spec says nothing, or [[no-timeout-ms]] (0) to wait INDEFINITELY. A
finite wait that runs out settles the request itself — the dialog closes on
every surface and the answer reads `timeout`, so the extension resumes with one
clear fixed outcome instead of a half-open form nobody can answer. An
indefinite wait never gives up on the human: only an answer, a cancel or an
interrupt releases it.

`:password` and `:otp` values in `:values` are opaque handles — see
[[reveal-secret]].

A request MUST name the session it parks — `:session-id`, or the session of
the extension environment currently executing. A run nobody can attribute is
a run the companion app is never told about: the gateway bridge turns the
request into a session event, and a session event with no session has nowhere
to go, so only a surface mounted in this very process could ever answer it.
That is refused here, before anything blocks.
sourceraw docstring

request->viewclj

(request->view request)

The channel/wire-facing projection of a pending request: the spec a dialog needs, and nothing a channel must not see (no promise, no submitted values, and no validator — validation is CODE the engine runs when the form is confirmed, a function cannot cross the wire, and a surface's job is to render the errors it is handed rather than to invent its own).

The field TREE is projected as a tree: a group crosses the wire with its own :direction and :fields, so both surfaces lay the form out from the same data instead of each inventing a layout.

The channel/wire-facing projection of a pending request: the spec a dialog
needs, and nothing a channel must not see (no promise, no submitted values,
and no validator — validation is CODE the engine runs when the form is
confirmed, a function cannot cross the wire, and a surface's job is to render
the errors it is handed rather than to invent its own).

The field TREE is projected as a tree: a group crosses the wire with its own
`:direction` and `:fields`, so both surfaces lay the form out from the same
data instead of each inventing a layout.
sourceraw docstring

request-json!clj

(request-json! request-json)
(request-json! request-json validators-json run)

The strings-only seam a Python extension crosses: a JSON request object in, a JSON answer object out. Blocks exactly like request!.

Channel routing is host-side — a channel_id/channel_ids key is dropped rather than minting keywords from guest data, so a Python extension always reaches the channels the host picked.

Validation is CODE, so it does not travel as JSON either: validators-json is {field name -> how many validators that field declared} and run is called (run field-name index value values) to reach the extension's own function, answering the verdict com.blockether.vis.internal.human-input.validation/check understands (nil/true, a message string, false, or a throw). Only a name, an index and the value being judged ever cross.

The strings-only seam a Python extension crosses: a JSON request object in, a
JSON answer object out. Blocks exactly like [[request!]].

Channel routing is host-side — a `channel_id`/`channel_ids` key is dropped
rather than minting keywords from guest data, so a Python extension always
reaches the channels the host picked.

Validation is CODE, so it does not travel as JSON either: `validators-json` is
`{field name -> how many validators that field declared}` and `run` is called
`(run field-name index value values)` to reach the extension's own function,
answering the verdict
[[com.blockether.vis.internal.human-input.validation/check]] understands
(nil/true, a message string, false, or a throw). Only a name, an index and the
value being judged ever cross.
sourceraw docstring

reveal-secretclj

(reveal-secret handle)

Return the plaintext behind a vis-secret: handle, or nil when the handle is unknown/forgotten. Trusted-side only: never hand the result to a channel, a log, or the model.

Return the plaintext behind a `vis-secret:` handle, or nil when the handle is
unknown/forgotten. Trusted-side only: never hand the result to a channel, a
log, or the model.
sourceraw docstring

submit!clj

(submit! request-id values)

Resolve pending request request-id with a raw field id -> value map.

Returns {:is-accepted false :errors {field-id message}} when a value fails its field's validation — the request stays pending so the dialog can show the errors inline. Returns {:is-accepted true} once the waiter is released, and {:is-accepted false :reason "unknown"} for an already-settled request.

Resolve pending request `request-id` with a raw `field id -> value` map.

Returns `{:is-accepted false :errors {field-id message}}` when a value fails
its field's validation — the request stays pending so the dialog can show the
errors inline. Returns `{:is-accepted true}` once the waiter is released, and
`{:is-accepted false :reason "unknown"}` for an already-settled request.
sourceraw docstring

validate-valuesclj

(validate-values fields values)

Coerce and validate a raw field id -> value map against a request's fields. Returns {:is-accepted true :values …} or {:is-accepted false :errors {id message}}.

fields may be the request's TREE: layout groups hold no answer, so they are flattened away here and a group can never change what an extension reads.

Pure — no vault, no state, no side effect of any kind — but not free: it runs the extension's own validator FUNCTIONS. So it runs ONCE, when the human confirms the form, never on a keystroke; only a real submission goes through coerce-values.

Coerce and validate a raw `field id -> value` map against a request's
`fields`. Returns `{:is-accepted true :values …}` or
`{:is-accepted false :errors {id message}}`.

`fields` may be the request's TREE: layout groups hold no answer, so they are
flattened away here and a group can never change what an extension reads.

Pure — no vault, no state, no side effect of any kind — but not free: it runs
the extension's own validator FUNCTIONS. So it runs ONCE, when the human
confirms the form, never on a keystroke; only a real submission goes through
[[coerce-values]].
sourceraw docstring

view<-wireclj

(view<-wire wire)

Inverse of request->view for a view that CROSSED A PROCESS BOUNDARY — the canonical snake_case map a human_input.request session event carries.

A run parked inside vis serve publishes on an in-process channel bus that never leaves that JVM, so for every other process the session event IS the request. Rebuilding the view goes back through the engine's own parser rather than through a second field vocabulary; only the stamps of request!, which normalize-request refuses by contract, are lifted across unchanged.

Inverse of [[request->view]] for a view that CROSSED A PROCESS BOUNDARY — the
canonical snake_case map a `human_input.request` session event carries.

A run parked inside `vis serve` publishes on an in-process channel bus that
never leaves that JVM, so for every other process the session event IS the
request. Rebuilding the view goes back through the engine's own parser rather
than through a second field vocabulary; only the stamps of [[request!]],
which `normalize-request` refuses by contract, are lifted across unchanged.
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