//! The renderer-agnostic half of the make-family design system. //! //! //! //! `makeover` answers *what colour*, and varies by theme. `makeover-geometry` //! answers *how much space*, and varies by density and surface. This crate //! answers *what the thing is*, and varies by nothing. //! //! # The deferral rule //! //! A description names intents and relationships, never values. Say //! [`Fill::Raised`], never `#D9DDF4`. Say `Gap::Peer`, never `6px`. What is //! left once colour and spacing are deferred is **composition**: which edges //! are lit, what inverts on press, what nests in what. //! //! The constraint that shapes all of it: a renderer that can only paint //! rectangles has to be able to express the result. egui has no //! `box-shadow: inset` and one stroke per widget with no per-side control; a //! terminal has box-drawing characters and one cell of resolution, and cannot //! draw a two-tone lit edge at all. A description that assumes per-side edges //! is a CSS description wearing a neutral name. So this crate names the //! *intent* — this region is a well — and each renderer chooses an expression //! it can actually produce, including dropping half of one. //! //! # Scope //! //! Depth came first: the bevel and the surfaces it shapes. That much was //! settled the hard way — the vocabulary here was read off audiofiles' //! `ui::theme` and `ui::widgets`, which are the only implementation written //! by a consumer with no CSS, then checked against both webview apps. All //! three agreed once Balanced Breakfast's fills were corrected. //! //! 0.2.0 adds the rest of the description, each member drawn the same way, //! from what the three apps already hand-write rather than from a taxonomy: //! //! - Components. [`Token`] (badge against chip), [`Notice`] (toast against //! banner), [`RowPart`], [`Heading`], [`Selector`], [`Readiness`], and //! [`Tone`], which is the one intent family they share. //! - Schemas. [`Field`] for forms and [`Column`] for lists and tables. //! - Structure. [`Region`] for the parts of a screen, [`Arrangement`] for how //! a screen is put together. //! //! **Validation** was absent on purpose here, on the grounds that neither app //! had a shared story. That reasoning is retired — see 0.11.0 below, which is //! where the constraints arrived and why the argument did not survive contact //! with what the apps were measured to do. //! //! 0.3.0 closes a gap the first real adoption found, which is what adopting //! against goingson first was for. [`Selector`] described only the *chosen* //! option, so an unchosen one fell through to [`Depth::Flat`] and no renderer //! drew it; goingson's tab strip recesses its unchosen tabs by hand and could //! not delete the line, because being recessed is *why* the chosen tab reads as //! coming forward. So [`Selector::unchosen`] joins `chosen`, and saying it //! needed [`Fill::Sunken`] and [`Depth::Sunken`]: a surface set back by colour //! with no edge, which is neither a well nor level-with. //! //! 0.7.0 adds [`State`], the interaction axis, closing the gap that adopting //! against three apps rather than one made visible. The description named //! rest and, through [`Depth::pressed`], pressed. It named neither focus nor //! disabled, so `makeover-webview` emitted a hover rule and stopped, and each //! consumer completed the primitive from outside by out-specifying a rule it //! did not own: 19 such rules in goingson, 21 in the MNW server, a further set //! in Balanced Breakfast, and three focus rings that do not match. The axis is //! deliberately two members wide, because hover and pressed belong where they //! already are. [`State`]'s own docs carry that argument. //! //! 0.8.0 finishes [`Field`], which described a field well enough to label it and //! not well enough to draw it. Writing `makeover-webview`'s form emitter found //! three things missing and the renderer supplied all three from outside: the //! current value, a select's options, and the placeholder. Two of those move //! here and one does not. //! //! - [`Field::placeholder`] is user-facing text sitting beside `label` and //! `hint`. There was never a reading on which it was renderer state; it was //! outside only because adding a field to a published struct is breaking. //! - [`Field::options`] moves because every renderer needs them and each was //! going to invent its own shape. [`Choice`] is the shape `makeover-webview` //! already arrived at, taken as-is rather than redesigned. //! - The current value stays renderer-side and is not coming here. It is the //! one of the three that is genuinely state: a webview reads it out of the //! DOM, an immediate-mode renderer holds a `&mut` to the app's own field, and //! a description that carried it would be a form model. //! //! 0.9.0 opens [`RowPart`], which was the last closed enum in the vocabulary, //! and adds [`RowPart::Tokens`]. Both halves come from the same finding, made //! by the first two real screens described through the router rather than by //! reading a stylesheet. //! //! A goingson project card carries two trailing badges, a type and a toned //! status; a contact card carries a primary email *and* a strip of tags. `Meta` //! is one slot and one string, so both ports joined their facts with a //! separator and lost what the second one was: a status reads as text where it //! used to read as colour. [`Token`] already says exactly the right thing — a //! small labelled thing with a kind, a tone and an optional action — and could //! only ever be a node in its own right, never inside a row. //! //! So the missing thing was permission rather than a concept. `Tokens` is that //! permission, and `#[non_exhaustive]` arrives with it so the next member is not //! a lockstep event across three renderers. The pairing is the point: this //! enum's own consumer in `makeover-webview` carried a comment predicting it //! would stop compiling one day, which is a lockstep break written down and //! waited for rather than prevented. //! //! Balanced Breakfast was checked before the member was added, because one //! consumer wanting something is not evidence. It packs a count and two icon //! buttons into the same single `Meta` slot while leaving `Actions` empty, so //! the slot was already straining under a second consumer for a different //! reason. //! //! 0.10.0 adds [`Meter`], a proportion carried as a pair rather than as a //! percentage. Its own docs carry the argument; the short form is that the //! percentage shape had already been tried in goingson and had already needed a //! companion flag to recover what rounding and clamping threw away. //! //! 0.11.0 is four members from the quasi proving ground, batched into one //! release because pre-1.0 a minor is breaking and a cascade is nine repos. //! Three findings that arrived with them turned out not to belong here at all: //! this crate has no notion of an action, a route or a destination, so anything //! asking what a control *calls* was never the vocabulary's to say. //! //! - [`Figure`], a value with a caption. goingson had five of them across five //! screens with five class vocabularies for the one shape, which is the //! divergence this crate exists to end, sitting in plain sight and counted for //! the first time. //! - [`RowPart::Proportion`], so a [`Meter`] can sit in a row. `Meter` reached //! two of its seven sites at 0.10.0 and the other five are row-shaped. Exactly //! [`RowPart::Tokens`]'s problem with a different payload, and it takes //! `Tokens`' answer: the part carries the description of a bar, not a node. //! - [`Field::max_length`], [`Field::min`] and [`Field::max`], joining //! [`Field::required`], which had been sitting here as the sole constraint //! while the header above claimed there were none. The set stops before //! `pattern`, which fails the renderer test and is one site in one app. //! - [`FieldKind::File`]. Every host has an honest answer — a native picker, an //! ``, a path prompt, an argument — and it carries no //! accepted-types list because `accept` appears at zero sites in either app. //! //! The evidence rule changed under these, and it is worth recording because four //! earlier decisions were made under the old one. The two-app test said a shape //! earns a word once a second app wants it. It is backwards: a rule that //! withholds a word until a second app has duplicated the code guarantees the //! duplication, and app three writes it a third time. The bar is now generic //! against bespoke — is this furniture any app would have, or is it this app's //! own? Bespoke keeps [`Region::Bespoke`], which already carries a completion //! heatmap and is the right answer for a calendar nobody will build twice. //! //! 0.12.0 is two more from the same proving ground, and the same sorting //! happened first: six findings came out of a measurement of goingson's whole //! frontend, and four of them turned out to be asking what a control *calls*, //! which this crate cannot say. The two that were really here: //! //! - [`Readiness`] grows from two states to four. It named `Ready` and //! `Pending` and stopped, so a screen whose list came back empty had nothing //! to say about it; goingson draws an empty state at 27 sites and Balanced //! Breakfast at 9. `Empty` and `Failed` are the same axis rather than a new //! member beside it, because a region shows one of the four and never two. //! `#[non_exhaustive]` arrives with them, the pairing [`RowPart`] made at //! 0.9.0 and for the same reason. //! - [`Column::sortable`], [`Column::sorted`] and [`Sort`]. The one finding in //! the set that completes a member rather than adding one: `Column` shipped //! with a width and a priority and could not say that a table is ordered by a //! column, so a described table could draw no caret and offer no reordering. //! //! 0.14.0 is two additive members on two `#[non_exhaustive]` enums, released //! together because publishing twice for that is waste and the cascade below //! this crate is nine repos. //! //! - [`Depth::Overlay`]. The enum could say raised, well, sunken and flat, and //! could not say that a surface sits *over* the page. Every renderer already //! had the surface — `makeover-tui`'s `Palette::overlay`, //! `makeover-immediate`'s `Palette::elevation`, `makeover-webview`'s //! `--elevation-overlay` — and none of them could be reached from a //! description. buckets_of_money has 16 modals waiting on it. //! - [`CellPart`], which is [`RowPart`] for tables. A row's parts have carried //! their own content intent since 0.2.0, so `.row-actions` inherits rather //! than taking a text colour; a table cell had no such vocabulary and //! `makeover-webview` emitted one undifferentiated `.cell`, so a button in a //! cell was painted as text. The four members are the four things quasi's //! `Cell` was measured to hold, and the count is in that crate's history //! rather than assumed here. //! //! 0.15.0 adds [`FieldKind::Date`] and [`FieldKind::DateTime`], on the argument //! [`FieldKind::Email`] was admitted on: a webview emits a different `type=`, //! which is a native picker, the platform's validation and a different keyboard //! on a touch device. Described as text with a "YYYY-MM-DD" hint, all three are //! lost. //! //! Two members and not one or five, from a count rather than from symmetry: 13 //! sites of `date` and 13 of `datetime-local` across the MNW server and //! goingson, and zero of `time`, `month` or `week`. The wire format each takes //! is named here as [`DATE_FORMAT`] and [`DATETIME_FORMAT`], because a host //! left to pick its own would disagree with a server silently, and //! [`FieldKind::temporal`] is the pair asked about once rather than at each //! renderer. `FieldKind`'s own comment claiming `radio` was the last HTML input //! type missing was already false when 0.8.1 wrote it; these are what it was //! missing. //! //! What each of the 0.12.0 findings deliberately leaves out is the address — what pressing a //! header calls, and where an empty state's "Add your first project" button //! goes. That is the boundary this crate is defined by, and four findings moved //! across it rather than being answered here. //! //! 0.19.0 narrows [`State`] to [`State::Disabled`] alone. `State::Focus` is //! gone: a description never states what has focus, because what focus *is* //! differs per host and every renderer had already decided for itself — the //! webview draws it from `:focus-visible`, egui refused the variant outright, //! and quasi-tui honoured it once at startup and overrode it thereafter. //! //! 0.20.0 adds [`Region::Widget`], the third tier, and `#[non_exhaustive]` to //! [`Region`] with it. Every vocabulary finding until now had two answers //! available — grow the primitive set, or [`Region::Bespoke`] — and a whole //! class of thing is wrong for both. A carousel is not a primitive, because a //! terminal has none and that is the test `Node::Html` failed. It is not //! bespoke either, because bespoke is what one app owns and every part of a //! carousel is furniture plus members this crate already has. //! //! The cost of the binary was that refusing a primitive was expensive: the app //! hand-rolls the thing forever, so the pressure always ran toward growing the //! primitive set with one host's idioms. A named assembly changes what "no" //! costs without changing what the vocabulary can say. //! //! MNW's carousel is the first consumer and was the finding that started it: one //! partial, three pages, an ordered set of frames with a position, prev/next and //! a dot strip, all of it sayable already and none of it nameable. See wiki //! `widget-tier` for the ownership model, which is why this member carries a //! name a renderer may decline to know. //! //! 0.21.0 adds [`Image`] and [`Fit`], found by trying to describe MNW's //! carousel under 0.20.0's widget tier and getting one step in. Nothing named a //! picture. The vocabulary could say a number with a caption, a badge, a meter //! and a table, and could not say the thing three of MNW's public pages are //! mostly made of. //! //! It reads as an oversight and is a measurement: 24 `` sites across 22 //! MNW templates, against one in goingson and none in Balanced Breakfast or //! audiofiles. A picture is furniture a *content platform* has, and MNW is the //! only one in the tree, so the evidence never arrived from the two-app //! direction the earlier rule looked in. Under the generic-against-bespoke bar //! it is not close: a picture is not one app's own. //! //! A primitive rather than a widget, which is worth stating now that the tier //! makes it a real question. A widget is an assembly of things already sayable //! and a picture is a leaf, assembled from nothing. It also passes the test //! `Node::Html` failed — every host has an honest answer, including a terminal, //! which has a graphics protocol or has [`Image::alt`]. //! //! [`Image`] carries no source, the split [`Act`] already makes: an address is //! not this crate's to hold. See its own docs, which is where the argument is. //! //! 0.22.0 finishes [`Image`], which 0.21.0 shipped unable to say how much room //! a picture needs. Without that a renderer cannot reserve space, so a picture //! occupies nothing until its bytes arrive and then takes its full height at //! once. Measured on MNW's landing page: a 478px jump per frame and a //! cumulative layout shift of 0.087 for the page. //! //! - [`Image::intrinsic`], the picture's own dimensions, carried as [`Extent`]. //! A fact about the asset rather than a display size, which is what keeps it //! on this side of the deferral rule: 5120x3412 is what the file *is*, and no //! renderer can learn it without fetching the bytes. //! - [`Loading`], and the default flips to [`Loading::Eager`]. 0.21.0 emitted //! the webview's `loading="lazy"` for every picture, which read one //! consumer's habit as a rule. Deferring a picture that is on screen at first //! paint saves nothing and makes its shift land later. The carousel is the //! case that proves this cannot be one renderer-wide setting: its first frame //! is on screen and its others are not, in one widget, at one moment. //! //! 0.23.0 adds [`Showing`], which is three open findings collapsing into one //! member. A tab group could not say which tab was open, a carousel could not //! say which frame was up, and a disclosure could not say whether its child was //! showing. All three are the same missing sentence, and while it was missing a //! renderer had two moves: match on a widget name, or draw every child. //! //! So the widget tier was taking the blame for a gap one level below it. With //! this a renderer derives its chrome from the description — labels get a strip, //! no labels get previous/position/next — once, for every widget there will ever //! be, and [`Region::Widget`]'s name goes back to being app vocabulary a //! renderer may decline to know. //! //! Only the kind lives here. Which child is up, and what each child is called, //! sit with whatever holds the regions, the same split [`Selector`] already made //! against `Node::Select`. //! //! 0.27.0 adds [`Region::Group`], which closes a gap this crate had carried //! since 0.2.0 without noticing: [`Heading::Section`] is documented as naming a //! block within the screen, and there was no block. A section heading is a leaf //! beside the things it names, so the description could say a section had //! *started* and never that one had ended. //! //! Found by asking how a screen distinguishes groups of settings by colour, and //! the answer turned out to be two findings rather than one. This is the first //! and it is the precondition: there is nothing to tint until there is a //! container. The second — every renderer already resolves `category.one` //! through `category.six` and no description can reach any of them — is filed //! and not shipped here. //! //! What the colour question settled anyway, because it shapes this member: the //! group carries no colour and no ordinal. A renderer distinguishing sibling //! groups derives the assignment from their order, which is //! [`Region::Columns`]' reasoning about counts applied to colour — the children //! say, and a value here would be a second source for something the description //! already states by containing them. //! //! 0.28.0 is what audiofiles' forms port found it could not say, three findings //! filed against a working conversion rather than guessed at in advance. All //! three are about a *question* rather than about a control, which is the line //! this crate keeps having to redraw. //! //! - [`FieldKind::Range`] and [`Field::step`]. A bounded number the user drags //! across, where both ends being on screen is what the question means. The //! reading to resist is that this is [`FieldKind::Number`] with bounds, and it //! is [`FieldKind::Radio`]'s argument again: a validated number can be out of //! range and a slider cannot, so the bounds stop being a rule and become the //! control's extent. [`Field::bounded`] is the check a renderer asks, since a //! range missing an end has nothing to draw. //! - [`Choice::unavailable`]. An option that is real, worth showing, and cannot //! be picked yet. Without it an app either drops the option — and the user //! never learns it is there — or hand-rolls the control outside the //! description, which is what audiofiles' instrument panel did: a permanently //! disabled radio plus a hand-written line saying what would enable it. //! `#[non_exhaustive]` arrives on [`Choice`] in the same release, so this is //! the last breaking addition to it. //! - Not a member at all: [`Field::placeholder`] on a chooser. It was sayable //! already and no renderer read it, so a select with nothing chosen showed an //! empty box and the instruction lived on a disabled button elsewhere. The //! renderers moved, not the description. //! //! 0.29.1 adds [`Awaiting`], which is the sentence [`Readiness`] could say about //! a region and could not say about a control. A described screen could state //! that a list was on its way and could not state that the button just pressed //! is doing the thing it was pressed for, so every renderer's in-flight //! treatment was the app's to hand-write. The MNW server hand-writes it 57 times //! and hand-writes the guard against a second press twice, which is the half //! that matters going missing on a codebase that sells things. //! //! The mark is the fact that something outstanding will complete, once, in //! expected finite time. Deliberately not remoteness, since a heavy local query //! waits too, and deliberately not slowness, which is a judgement rather than a //! property. It carries an optional amount, stated only when the amount is //! measured, and it carries no duration at all: a renderer draws what is done //! over what there is plus the time so far, and never an estimate of what is //! left. //! //! One mark and two readings, which is what keeps a slow region from being //! hand-split into its own route the way MNW's payout summary is: a pressed //! control goes busy and locks, a region fed by an awaiting call stands in as //! [`Readiness::Pending`] and fills when it lands. //! //! A patch release for a new member, which is the 0.27.5 precedent rather than a //! new rule: nothing existing changed shape, so every consumer already asking //! for 0.29 keeps resolving and the suite below this crate does not have to move //! for a type only quasi reads. The minor releases above were minor because they //! also narrowed something. //! //! 0.30.0 is two members batched into one release, which is 0.11.0's precedent //! and its reasoning: pre-1.0 a minor is breaking, the cascade below this crate //! is seven repos, and paying that twice in a week for two unrelated words is //! the tax the batching exists to avoid. //! //! - [`FieldKind::Rich`], a field whose value is markdown source. The editing //! counterpart of prose already carried as markdown, and it is renderable //! everywhere for the reason the carrying is: editing markdown is editing //! text. It buys a renderer permission to offer a preview or a syntax pass and //! buys a host reading the value back the knowledge of what it holds; a //! renderer with neither draws a textarea. It says nothing about when the //! value is saved, because autosave is a clock. Measured against four MNW //! section editors that are one shape written four times. //! [`FieldKind::multiline`] arrives with it, since the pair is now two members //! every renderer has to ask about. //! - [`Facet`], [`Selecting`], [`FacetValue`] and [`Standing`]: a named //! dimension a set is narrowed by. MNW's discover page filters six ways //! through six mechanisms, and its filter rows carry a tick box *and* a //! chevron only because a tag's selection and a tag's browse position were //! held separately. One word covers all six, and [`Selecting::Subtree`] is the //! member that made it an enum rather than a bool: a tree's selection is //! branches taken and branches pruned, which no flat mode can express, and //! making one gesture do browsing and filtering together is what lets the //! second mechanism go. [`Standing`] has four members rather than a bool for //! the tree's sake — a value in force because an ancestor is, is not a value //! somebody picked. [`FacetValue`] splits an identifier from a label for //! [`Choice`]'s reason and one of its own: two leaves under different parents //! are legitimately both called "Ambient", and the path is what tells them //! apart and what nearest-ancestor-wins resolves over. Deliberately wider than //! that one page: audiofiles' library browser and goingson's filters are the //! same shape. //! //! 0.39.0 gives an *option* its second line. [`Choice::detail`] is the sentence //! that says what picking one means, and it is one member and not two. Measured //! 2026-08-29 (`5e21dcfc`), off the MNW server's un-ported markup. //! //! - **Six sites, and four of them had already folded it into the label.** //! `Public: Anyone can see this repository` in git settings, //! the same shape in the project-basics AI tier and the cart's currency //! conversion, `Mislabeled (wrong AI tier or category)` in the report modal. //! The item-type and item-pricing wizards give it its own span. The described //! screens fold it in miniature: `Every 15 minutes (recommended)`. One fact, //! six spellings, and the folding is what says the member was missing. //! - **It is not a price**, and the count is what decided that rather than //! taste. The site that asked carries a name, a price and a description; the //! tree's other three priced tier lists are not option lists at all — each //! card carries its own submit, so each is a region with a heading, a fact //! and an act and is sayable today. A price member would have one consumer //! and would mean growing a money type this crate does not have. //! - **Where it is drawn is [`unavailable`](Choice::unavailable)'s question met //! a third time**, and it takes the same answer: its own element in a radio //! group, run into the option's own text in a ``, egui has `Slider`, a terminal draws a bar and /// takes arrow keys, a CLI takes a bounded argument. /// /// # It owes its bounds /// /// [`min`](Field::min) and [`max`](Field::max) are `Option` for every other /// kind and are **required** here, in the sense the description can require /// anything: [`Field::bounded`] is the check, and a range missing one has no /// extent for a renderer to draw. What a renderer does with an unbounded /// range is its own call and both answers are honest — fall back to a typed /// number, or pick a host default — so this is stated rather than enforced, /// the way every other constraint here is. /// /// [`Field::step`] is the third fact and is genuinely optional: absent, the /// host's own granularity stands. /// /// Added 0.28.0, from audiofiles' classifier thresholds and storage cap /// picker (`fb93426b`), where four sliders were hand-rolled against a /// vocabulary that could not say what they were. Range, /// One question with two ends: a lower value and an upper one, submitted /// under two names. /// /// "Show me samples between 90 and 130 BPM" has a single answer with two /// ends, and the ends constrain each other: a minimum above the maximum is /// not a wrong value, it is an empty result nobody asked for. Described as /// two [`Number`](Self::Number) fields that is unsayable — nothing says they /// are one question, so a renderer draws two controls with two labels and no /// relationship, and [`Field::error`] can only be attached to one side of a /// fault that belongs to both. /// /// Not [`Range`](Self::Range), which was the reading to resist and the /// resistance is the same one `Range` itself needed against `Number`. A /// range describes *one* value inside an extent; this describes two, and the /// extent is a bound on each rather than the question's meaning. The two /// come apart in the answer: a range has a value, an interval has a pair, /// and either end may be absent while the other stands. /// /// # It states both names /// /// [`Field::name`] is the lower end and [`Field::upper_name`] is the upper /// one, stated rather than derived. Measured 2026-08-21, the two sites in /// this tree disagree about affix order — audiofiles submits `bpm_min` / /// `bpm_max` and the MNW server submits `min_price` / `max_price` — so any /// derived rule picks one and renames the other's parameters. One member /// instead of a naming convention this crate would then own forever. /// /// Direction is carried by which member the name sits in, so nothing /// separate says which end is which. /// /// # What it does not enforce /// /// The crossing rule. A lower end above the upper one is describable here /// and always was, exactly as an out-of-[`min`](Field::min) number is: this /// crate carries constraints and never checks them, and deciding a value is /// wrong stays with whoever validated. What the description buys is that the /// fault now has one place to be reported rather than two. /// /// # Both ends take the same facts /// /// [`min`](Field::min), [`max`](Field::max), [`step`](Field::step) and /// [`unit`](Field::unit) describe the axis rather than one end of it, so /// they are read once and applied to both. Six of audiofiles' filter axes /// are exactly this: one extent, one unit, one granularity, two ends. /// /// The bounds are optional here, unlike `Range`. They are a rule the answer /// is checked against rather than the control's extent, which is /// [`Number`](Self::Number)'s arrangement and not a slider's. /// /// Added 0.34.0, ruled by Max 2026-08-21, from audiofiles' six filter axes /// and the MNW server's price pair. Interval, /// An email address. /// /// Distinct from [`Text`](Self::Text) because the distinction is not /// decoration: a webview renderer emits `type="email"`, which on a touch /// device changes the keyboard that appears and turns on the platform's own /// validation. goingson ships to iOS, so collapsing this into text costs a /// keyboard with no `@` on it. /// /// Added 0.5.0, from goingson's contact form. Email, /// A URL. Same reasoning as [`Email`](Self::Email). /// /// Added 0.5.0, from goingson's contact-social and contact-feed forms. Url, /// A telephone number. Same reasoning as [`Email`](Self::Email), and the /// clearest case of it: the keyboard is a numeric pad rather than letters. /// /// Added 0.5.0, from goingson's contact-phone form. Tel, /// A calendar day, with no time of day in it. /// /// [`Email`](Self::Email)'s argument, and it carries further: a webview /// emits `type="date"`, which is a native picker, the platform's own /// validation, and on a touch device the date keyboard. Described as /// [`Text`](Self::Text) with a hint reading "YYYY-MM-DD", all three are /// lost and the hint is doing the platform's job in prose. /// /// The membership test passes on every host without stretching: a webview /// and a Tauri app emit the input, egui has a date picker, a terminal /// prompts for a day and can validate it, a CLI takes an argument. /// /// # The value is ISO 8601, `YYYY-MM-DD` /// /// Named here rather than left to each host, because a host that picks /// differently sends a server something it parses differently, and the /// failure is silent and per-host. It is ``'s own wire /// format, so the webview renderer owes nothing to honour it and the other /// hosts have one spelling to meet. [`DATE_FORMAT`] is the constant, and a /// test asserts this doc and that constant agree. /// /// Added 0.15.0, from the MNW server's git access-token expiry /// (`user_ssh_keys_tab.html`) and six further sites across the server and /// goingson. Date, /// A calendar day and a time of day together. /// /// Apart from [`Date`](Self::Date) because the question is different rather /// than more precise: "which day does this expire" and "at what moment does /// this publish" are asked by different screens and answered by different /// controls. A webview emits `type="datetime-local"` for one and /// `type="date"` for the other, and a host that collapsed them would ask /// half the tree for a precision it does not want. /// /// Both arrived together on measurement rather than on symmetry: 13 sites /// of each across the MNW server and goingson, and **zero** of `time`, /// `month` or `week`, which is why those are not here. A member added for a /// case nobody has is a member designed against nothing, which is /// [`File`](Self::File)'s reasoning about `accept` applied to a whole /// member. /// /// # The value is `YYYY-MM-DDTHH:MM`, local, with no zone /// /// ``'s own format, and the "local" is the /// load-bearing half: the value carries no offset and no `Z`, so the moment /// it names is only fixed once something supplies a zone. That is the app's /// business and not the description's. Seconds are absent, which is the /// browser's own default and is left as the rule rather than restated as a /// constraint. [`DATETIME_FORMAT`] is the constant. /// /// [`Field::min`] and [`Field::max`] already take "the host's own spelling /// of a bound", so a floor of *not in the past* needs nothing new here: it /// is a string in this same format. /// /// Added 0.15.0, from goingson's snooze picker and day planner and the MNW /// server's publish-at fields. DateTime, /// Several lines of text. Textarea, /// Several lines of text the user writes markdown in. /// /// The editing counterpart of prose a description carries as markdown /// source, and the reason it can exist at all is the same one that lets the /// source be carried: editing markdown is editing text, so a terminal, an /// immediate-mode host and a webview all have an honest answer, and none of /// them has to refuse. A kind that meant "rich text" in the WYSIWYG sense /// would have been a document model, and two of the three hosts would have /// had to draw something they cannot. /// /// What the mark buys over [`Textarea`](Self::Textarea) is that a renderer /// may offer the affordances markdown has and plain text does not — a /// preview, a syntax pass, a monospaced face for the source — and that a /// host reading the value back knows what it is holding. A renderer with /// none of that draws a textarea, which is why this is additive rather than /// a second control. /// /// It says nothing about **when** the value is saved. Autosave is a clock, /// clocks are not described here, and the four MNW editors this was measured /// against each keep their own. /// /// Sanitising stays where it already is for markdown that is only displayed: /// with the renderer, at the point markup is produced. Being described is /// not a safety property, and a host with its own sanitiser and its own /// content-security posture still owns both. /// /// Added 0.30.0, `f8ad0b32`, from four hand-written MNW section editors — /// `project-sections.js`, `blog-editor.js`, `partial-item-text-editor.js` /// and `wizard-item-sections.js` — which are one shape written four times. Rich, /// One of a fixed set, offered behind a control that shows one at a time. Select, /// One of a fixed set, with every option on screen at once. /// /// Not a presentation of [`Select`](Self::Select), which is the reading to /// resist: what differs is a property of the *question*. A choice that is /// consequential or irreversible has to be readable without opening /// anything, because a closed control shows one option and hides the rest, /// and the one it shows is whichever was current before the user had read /// the alternatives. audiofiles asks whether a library copies samples into /// its store or references them where they lie — which cannot be changed /// afterwards — and had already promoted that out of a checkbox by hand, /// with a comment giving this reason, before the description could say it. /// /// It was described here at 0.8.1 as "the one HTML input type this enum was /// missing", which was not true then and is not true now: `file` arrived at /// 0.11.0 and `date` and `datetime-local` at 0.15.0. Everything here is /// still an ``, a `