Process-wide feature-toggle registry.
Replaces the two parallel toggle plumbings we had drifting apart:
:show-thinking, :show-iterations,
:show-silent) wired into state/default-settings,if (some-flag …) … else … checks scattered through
internal/render.clj and channel-tui's render layer.A toggle has stable metadata (id, label, description, default,
owner) and a current ON/OFF value. Anyone — internal modules,
extensions, channels — registers their toggles into the same
registry; any caller flips a toggle through the same set!. The
TUI settings dialog walks the registry to render the list, so
adding a new toggle from an extension shows up in the user's UI
without any TUI patch.
Persistence is opt-in: register-toggle! accepts :persist? true
and on set! the wrapper file writes {:toggles {id bool}} into
~/.vis/config.edn via vis.config/save-config!. Hydration
happens at process start (call hydrate-from-config! once after
config/load-config-raw).
Contract:
:vis/show-thinking,
:foundation-git/auto-commit, ...).enabled? is cheap (single atom deref + keyword lookup),
called per-paint per-row by the render layer; do not turn it
into a function-call indirection.state is left alone so a
user override survives a reload.Process-wide feature-toggle registry.
Replaces the two parallel toggle plumbings we had drifting apart:
- hard-coded TUI booleans (`:show-thinking`, `:show-iterations`,
`:show-silent`) wired into `state/default-settings`,
- per-render `if (some-flag …) … else …` checks scattered through
`internal/render.clj` and channel-tui's render layer.
A toggle has stable metadata (`id`, label, description, default,
owner) and a current ON/OFF value. Anyone — internal modules,
extensions, channels — registers their toggles into the same
registry; any caller flips a toggle through the same `set!`. The
TUI settings dialog walks the registry to render the list, so
adding a new toggle from an extension shows up in the user's UI
without any TUI patch.
Persistence is opt-in: `register-toggle!` accepts `:persist? true`
and on `set!` the wrapper file writes `{:toggles {id bool}}` into
`~/.vis/config.edn` via `vis.config/save-config!`. Hydration
happens at process start (call `hydrate-from-config!` once after
`config/load-config-raw`).
Contract:
- Toggle ids are namespaced keywords (`:vis/show-thinking`,
`:foundation-git/auto-commit`, ...).
- `enabled?` is cheap (single atom deref + keyword lookup),
called per-paint per-row by the render layer; do not turn it
into a function-call indirection.
- Defaults are immutable once registered. Re-registering the
same id is allowed (idempotent boot path) and merges over
prior metadata; the live VALUE in `state` is left alone so a
user override survives a reload.Toggle ids forced ON for the current dynamic scope, regardless of the global
value. sub_loop children bind this (Clojure binding-conveyance carries it
into parallel futures) so a dispatched agent always has the shell + harness
layers, even when the user left them OFF globally. RUNTIME gating only —
value-of (settings UI, persistence) stays the global truth.
Toggle ids forced ON for the current dynamic scope, regardless of the global value. `sub_loop` children bind this (Clojure binding-conveyance carries it into `parallel` futures) so a dispatched agent always has the shell + harness layers, even when the user left them OFF globally. RUNTIME gating only — `value-of` (settings UI, persistence) stays the global truth.
(add-listener! f)Register a no-arg-or-event listener fn. Returns a dispose! thunk
the caller invokes when their consumer goes away (channel close,
extension reload, ...).
Register a no-arg-or-event listener fn. Returns a `dispose!` thunk the caller invokes when their consumer goes away (channel close, extension reload, ...).
(choices-of id)Vec of legal choices for an :enum toggle. Empty when id is
unregistered or registered as :boolean.
Vec of legal choices for an `:enum` toggle. Empty when `id` is unregistered or registered as `:boolean`.
(clear-state!)Wipe live overrides and listeners. Registry is untouched. Used by
tests; production callers should prefer reset-to-default!.
Wipe live overrides and listeners. Registry is untouched. Used by tests; production callers should prefer `reset-to-default!`.
(cycle-value! id)Advance an :enum toggle one step through its registered
:choices. Wraps at the end. Throws on boolean toggles.
Advance an `:enum` toggle one step through its registered `:choices`. Wraps at the end. Throws on boolean toggles.
(enabled? id)Boolean cast of (value-of id), OR forced ON in the current dynamic scope
(*forced-on*). Fail-closed: returns false when id is not registered and
not forced. Hot-path — one set lookup + one atom deref.
Boolean cast of `(value-of id)`, OR forced ON in the current dynamic scope (`*forced-on*`). Fail-closed: returns `false` when `id` is not registered and not forced. Hot-path — one set lookup + one atom deref.
Sentinel that records whether the canonical internal toggles have been installed (idempotent).
Sentinel that records whether the canonical internal toggles have been installed (idempotent).
(hydrate-from-config! config-map)Bulk-apply persisted toggle values from (:toggles config-map). Silently
skips ids not in the registry so a stale config file from a
previous install can't break boot. Routes through set-value!
so enum entries get validated; individual invalid values are
dropped (logged via the listener) instead of aborting the whole
hydrate.
Bulk-apply persisted toggle values from `(:toggles config-map)`. Silently skips ids not in the registry so a stale config file from a previous install can't break boot. Routes through `set-value!` so enum entries get validated; individual invalid values are dropped (logged via the listener) instead of aborting the whole hydrate.
(register-toggle! spec)Register one toggle. spec must satisfy :toggle/spec.
Re-registering the same :id is idempotent: metadata MERGES, the
live VALUE in state is preserved (user overrides survive reload).
Returns the canonical registered spec.
Register one toggle. `spec` must satisfy `:toggle/spec`. Re-registering the same `:id` is idempotent: metadata MERGES, the live VALUE in `state` is preserved (user overrides survive reload). Returns the canonical registered spec.
(register-toggles! specs)Convenience: register a sequence of specs in order, returns the vec of canonical specs.
Convenience: register a sequence of specs in order, returns the vec of canonical specs.
(registered-toggles)Vec of every registered toggle's normalized spec, in registration insertion order. Stable for the TUI settings dialog.
Vec of every registered toggle's normalized spec, in registration insertion order. Stable for the TUI settings dialog.
(reset-to-default! id)Drop the user override for id so resolution falls back to the
registered default. Notifies listeners when the effective value
changes.
Drop the user override for `id` so resolution falls back to the registered default. Notifies listeners when the effective value changes.
(set-enabled! id value)Boolean alias of set-value! for the TUI dialog — keeps the
common toggle-flip call sites readable. Refuses :enum toggles
so an accidental boolean-flip on a multi-value toggle surfaces
loudly; use cycle-value! / set-value! for those.
Boolean alias of `set-value!` for the TUI dialog — keeps the common toggle-flip call sites readable. Refuses `:enum` toggles so an accidental boolean-flip on a multi-value toggle surfaces loudly; use `cycle-value!` / `set-value!` for those.
(set-value! id value)Set id to value and notify listeners. Returns the new value.
Validation matches the registered :type:
:boolean — coerce to boolean.
:enum — must be one of :choices; otherwise throws
:vis.toggles/invalid-value so the bug surfaces
at the call site instead of later in render.
Set `id` to `value` and notify listeners. Returns the new value.
Validation matches the registered `:type`:
`:boolean` — coerce to boolean.
`:enum` — must be one of `:choices`; otherwise throws
`:vis.toggles/invalid-value` so the bug surfaces
at the call site instead of later in render.(snapshot)Return a map {id value} of EVERY persistable toggle's effective
value, intended for serialisation. Skips toggles whose
:persist? is false. Boolean toggles are coerced to boolean;
enum toggles surface their raw choice value. Orphans from a
previously-installed extension are dropped.
Return a map `{id value}` of EVERY persistable toggle's effective
value, intended for serialisation. Skips toggles whose
`:persist?` is false. Boolean toggles are coerced to boolean;
enum toggles surface their raw choice value. Orphans from a
previously-installed extension are dropped.(toggle-for-channel? channel spec)True when a toggle should appear in channel's settings UI. A toggle
with no :channels set is channel-neutral (shown everywhere); one with
a :channels set shows only in the listed channels. Keeps a channel's
Settings free of OTHER channels' controls (e.g. the web has no use for
the TUI's mouse-selection-copy or transcript-display toggles).
True when a toggle should appear in `channel`'s settings UI. A toggle with no `:channels` set is channel-neutral (shown everywhere); one with a `:channels` set shows only in the listed channels. Keeps a channel's Settings free of OTHER channels' controls (e.g. the web has no use for the TUI's mouse-selection-copy or transcript-display toggles).
(toggle-spec id)Lookup the registered spec for id, or nil.
Lookup the registered spec for `id`, or nil.
(toggle-visible? spec)True when a toggle should appear in a settings UI: no :visible-fn
means always visible; a throwing predicate fails OPEN (shown) so a
broken predicate can never hide a control the user needs.
True when a toggle should appear in a settings UI: no `:visible-fn` means always visible; a throwing predicate fails OPEN (shown) so a broken predicate can never hide a control the user needs.
(toggles-for-channel channel)visible-toggles further scoped to channel via toggle-for-channel?.
Channels render THIS instead of visible-toggles so each Settings UI
only shows controls it actually honours.
`visible-toggles` further scoped to `channel` via `toggle-for-channel?`. Channels render THIS instead of `visible-toggles` so each Settings UI only shows controls it actually honours.
(type-of id):boolean / :enum / nil for unknown.
`:boolean` / `:enum` / nil for unknown.
(value-of id)Resolve the live value for id. Lookup order:
state,nil if the toggle isn't registered.Returns the raw value (boolean for :boolean toggles, any value
from :choices for :enum toggles). enabled? is the
boolean-cast convenience for the common boolean path.
Resolve the live value for `id`. Lookup order: 1. live override in `state`, 2. registered default, 3. `nil` if the toggle isn't registered. Returns the raw value (boolean for `:boolean` toggles, any value from `:choices` for `:enum` toggles). `enabled?` is the boolean-cast convenience for the common boolean path.
(visible-toggles)registered-toggles filtered to what settings UIs should SHOW —
provider-specific knobs declare a :visible-fn so e.g. the OpenAI
Codex verbosity cycle only appears when a Codex provider is actually
configured, and :settings? false toggles (e.g. reasoning-effort, which
has its own Ctrl+R control) stay out of the Settings dialog entirely.
State ops always work on the FULL registry; visibility is a presentation
concern only.
`registered-toggles` filtered to what settings UIs should SHOW — provider-specific knobs declare a `:visible-fn` so e.g. the OpenAI Codex verbosity cycle only appears when a Codex provider is actually configured, and `:settings? false` toggles (e.g. reasoning-effort, which has its own Ctrl+R control) stay out of the Settings dialog entirely. State ops always work on the FULL registry; visibility is a presentation concern only.
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 |