Skip to main content

ControlKind

Enum ControlKind 

Source
#[non_exhaustive]
pub enum ControlKind { Choice { options: Vec<ChoiceOption>, }, Text, Toggle, Secret, Number { min: Option<i64>, max: Option<i64>, step: Option<i64>, }, Range { min: i64, max: i64, step: i64, }, Temporal { precision: TemporalPrecision, }, Color, Path { kind: PathKind, accept: Vec<String>, }, }
Expand description

How a control accepts input.

Choice carries its options inline because they belong to it: one field, N mutually-exclusive answers. Modelling each option as its own control would make Requirement incoherent — under “the response must include this control”, answering an allow would require also answering the deny.

§A kind is a MEANING; an affordance is not a kind

The line this vocabulary is drawn on, because the HTML input catalog it mirrors does not draw it and copying that list variant-for-variant would have produced twenty-two.

A kind earns its place when it changes what a well-formed ANSWER is — #c0ffee is a color and not a date, and no surface may accept it as one. It does not earn a place merely by suggesting a different widget: a slider and a typed number accept the same answers, so Range is separate from Number only because a bounded-and-stepped value space IS different (its bounds are mandatory), not because one draws as a slider.

What this excludes, deliberately:

  • button, submit, reset, image — form ACTIONS. A definition’s actions are its SemanticRoles; a submit button that carried its own meaning would be a second authority vocabulary.
  • hidden — not a control at all. A value the operator cannot see is not a thing they answered, and putting one here would let a surface claim an answer nobody gave.
  • radio, checkbox, password, select — already spelled Choice, Toggle, Secret and Choice again. <select> and a radio group are one meaning with two presentations.

§Every kind here is answerable by typing

None of these mint a SurfaceFeature demand (see downgrade::intrinsic_demands), and that is deliberate rather than unfinished. A date answered by typing 2026-09-01 is a satisfied date, so a surface with no calendar widget has NOT failed to present the question — it presented it plainly. Only Secret demands a feature, because it changes what may be DISCLOSED rather than how something is drawn. Minting a demand per kind would make plain and headless surfaces refuse questions they can answer perfectly well, which is law 5 pointed at the wrong axis.

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

Choice

Pick exactly one of the options offered.

Fields

§options: Vec<ChoiceOption>

The options, in presentation order.

§

Text

Free text.

Deliberately still a unit variant. HTML spells five flavours of text — email, url, tel, search, plain — and carrying one here as Text { format } would re-encode the most common kind in the vocabulary: a unit variant is the dag-cbor string "text", a struct variant is a map. That breaks tests/data/interaction-vectors.json, whose frozen canonical bytes are DECODED rather than rebuilt, and the external-consumer corpus answering them — the compatibility fixtures CLAUDE.md names as what a rewrite is written against.

The flavours differ, in a terminal, only in what counts as valid. That is not worth re-addressing every definition ever written. If one earns its own kind later it can be ADDED as a new variant, which breaks nothing — the way Number, Temporal, Color and Path were.

§

Toggle

A boolean.

§

Secret

A secret: never persisted in markup or logs (ADR D1).

§

Number

A whole number, optionally bounded and stepped.

Integers, and that is a decision. A binary float cannot be canonically encoded without deciding what to do about NaN and -0.0, and this vocabulary is content-addressed — two encodings of one value would be two identities. Every number newt asks for today (a round cap, a port, a timeout, a percentage) is integral. A fractional control is a later, separate decision, and it will carry its digits as a decimal STRING rather than reopening this one.

Fields

§min: Option<i64>

Smallest acceptable value, inclusive.

§max: Option<i64>

Largest acceptable value, inclusive.

§step: Option<i64>

The grid the value must sit on, counted from min (or from zero when unbounded below). None accepts any integer in range.

§

Range

A number on a bounded, stepped scale — HTML’s range.

Distinct from Number because its bounds are MANDATORY: a slider without ends is not a slider, and a surface that draws one needs both to draw anything at all.

Fields

§min: i64

Smallest acceptable value, inclusive.

§max: i64

Largest acceptable value, inclusive.

§step: i64

The grid the value must sit on, counted from min.

§

Temporal

A point in time at the given precision.

Fields

§precision: TemporalPrecision

How much of a moment is being asked for.

§

Color

An sRGB color, written #rrggbb.

§

Path

A filesystem path.

The protocol says what is being NAMED, never whether it exists: an existence check is the host’s business, is racy by nature, and would make a definition’s validity depend on the machine reading it.

Fields

§kind: PathKind

Whether the path names a file or a directory.

§accept: Vec<String>

Extensions or globs a surface may filter by — HTML’s accept. Advisory: a surface that ignores it still collects a valid answer.

Implementations§

Source§

impl ControlKind

Source

pub fn check_text(&self, text: &str) -> Result<(), String>

Is text a well-formed answer to this control?

Err carries what was EXPECTED, phrased for a human — “an integer in 1..=10000”, “YYYY-MM-DD”, “#rrggbb”. The caller decides what a malformed answer means: the binding layer refuses it, an interactive surface can show it as you type. One implementation either way, which is the point — a panel that validated Number its own way would be a second opinion about what the definition means.

Choice always answers Ok: typed input for a choice is RESOLVED (canonical-first, aliases second, ambiguity denies) by binding::resolve_typed, and a second, weaker check here is exactly the duplicate that rule exists to prevent. Toggle and Secret do not travel as text at all.

§Errors

The expectation this control’s answers must meet, when text does not.

Source

pub fn hint(&self) -> String

What this kind advertises after a control’s label — [y/n], [YYYY-MM-DD], (secret, not echoed).

One table, not one per surface. The plain projection and the RichTUI view model each carried this suffix list, which is two places for one piece of knowledge and the shape the reuse discipline exists to stop: a kind added to one and forgotten in the other renders as a bare label on that surface, silently. Here a new kind advertises itself everywhere by construction.

The strings for the pre-existing kinds are EXACT: the plain projection is a byte-identity contract, so Toggle is [y/n] — never [y/N], because a rendered default is how a headless surface chooses one by accident.

Choice advertises nothing here: its options are rendered as their own lines, not as a suffix.

Source

pub fn travels_as_text(&self) -> bool

Whether an answer to this control travels as ControlValue::Text.

True for every value-shaped kind — text, number, range, temporal, color, path. False for the three that have their own value shape: a choice names an option, a toggle carries a bool, and a secret carries a REFERENCE (there is deliberately no variant that can hold plaintext).

Trait Implementations§

Source§

impl Clone for ControlKind

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ControlKind

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for ControlKind

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for ControlKind

Source§

impl PartialEq for ControlKind

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for ControlKind

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for ControlKind

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.