| 24 |
24 |
|
//!
|
| 25 |
25 |
|
//! # Scope
|
| 26 |
26 |
|
//!
|
| 27 |
|
- |
//! Depth came first: the bevel and the surfaces it shapes. That much was
|
| 28 |
|
- |
//! settled the hard way — the vocabulary here was read off audiofiles'
|
| 29 |
|
- |
//! `ui::theme` and `ui::widgets`, which are the only implementation written
|
| 30 |
|
- |
//! by a consumer with no CSS, then checked against both webview apps. All
|
| 31 |
|
- |
//! three agreed once Balanced Breakfast's fills were corrected.
|
| 32 |
|
- |
//!
|
| 33 |
|
- |
//! 0.2.0 adds the rest of the description, each member drawn the same way,
|
| 34 |
|
- |
//! from what the three apps already hand-write rather than from a taxonomy:
|
| 35 |
|
- |
//!
|
| 36 |
|
- |
//! - Components. [`Token`] (badge against chip), [`Notice`] (toast against
|
| 37 |
|
- |
//! banner), [`RowPart`], [`Heading`], [`Selector`], [`Readiness`], and
|
| 38 |
|
- |
//! [`Tone`], which is the one intent family they share.
|
| 39 |
|
- |
//! - Schemas. [`Field`] for forms and [`Column`] for lists and tables.
|
| 40 |
|
- |
//! - Structure. [`Region`] for the parts of a screen, [`Arrangement`] for how
|
| 41 |
|
- |
//! a screen is put together.
|
| 42 |
|
- |
//!
|
| 43 |
|
- |
//! **Validation** was absent on purpose here, on the grounds that neither app
|
| 44 |
|
- |
//! had a shared story. That reasoning is retired — see 0.11.0 below, which is
|
| 45 |
|
- |
//! where the constraints arrived and why the argument did not survive contact
|
| 46 |
|
- |
//! with what the apps were measured to do.
|
| 47 |
|
- |
//!
|
| 48 |
|
- |
//! 0.3.0 closes a gap the first real adoption found, which is what adopting
|
| 49 |
|
- |
//! against goingson first was for. [`Selector`] described only the *chosen*
|
| 50 |
|
- |
//! option, so an unchosen one fell through to [`Depth::Flat`] and no renderer
|
| 51 |
|
- |
//! drew it; goingson's tab strip recesses its unchosen tabs by hand and could
|
| 52 |
|
- |
//! not delete the line, because being recessed is *why* the chosen tab reads as
|
| 53 |
|
- |
//! coming forward. So [`Selector::unchosen`] joins `chosen`, and saying it
|
| 54 |
|
- |
//! needed [`Fill::Sunken`] and [`Depth::Sunken`]: a surface set back by colour
|
| 55 |
|
- |
//! with no edge, which is neither a well nor level-with.
|
| 56 |
|
- |
//!
|
| 57 |
|
- |
//! 0.7.0 adds [`State`], the interaction axis, closing the gap that adopting
|
| 58 |
|
- |
//! against three apps rather than one made visible. The description named
|
| 59 |
|
- |
//! rest and, through [`Depth::pressed`], pressed. It named neither focus nor
|
| 60 |
|
- |
//! disabled, so `makeover-webview` emitted a hover rule and stopped, and each
|
| 61 |
|
- |
//! consumer completed the primitive from outside by out-specifying a rule it
|
| 62 |
|
- |
//! did not own: 19 such rules in goingson, 21 in the MNW server, a further set
|
| 63 |
|
- |
//! in Balanced Breakfast, and three focus rings that do not match. The axis is
|
| 64 |
|
- |
//! deliberately two members wide, because hover and pressed belong where they
|
| 65 |
|
- |
//! already are. [`State`]'s own docs carry that argument.
|
| 66 |
|
- |
//!
|
| 67 |
|
- |
//! 0.8.0 finishes [`Field`], which described a field well enough to label it and
|
| 68 |
|
- |
//! not well enough to draw it. Writing `makeover-webview`'s form emitter found
|
| 69 |
|
- |
//! three things missing and the renderer supplied all three from outside: the
|
| 70 |
|
- |
//! current value, a select's options, and the placeholder. Two of those move
|
| 71 |
|
- |
//! here and one does not.
|
| 72 |
|
- |
//!
|
| 73 |
|
- |
//! - [`Field::placeholder`] is user-facing text sitting beside `label` and
|
| 74 |
|
- |
//! `hint`. There was never a reading on which it was renderer state; it was
|
| 75 |
|
- |
//! outside only because adding a field to a published struct is breaking.
|
| 76 |
|
- |
//! - [`Field::options`] moves because every renderer needs them and each was
|
| 77 |
|
- |
//! going to invent its own shape. [`Choice`] is the shape `makeover-webview`
|
| 78 |
|
- |
//! already arrived at, taken as-is rather than redesigned.
|
| 79 |
|
- |
//! - The current value stays renderer-side and is not coming here. It is the
|
| 80 |
|
- |
//! one of the three that is genuinely state: a webview reads it out of the
|
| 81 |
|
- |
//! DOM, an immediate-mode renderer holds a `&mut` to the app's own field, and
|
| 82 |
|
- |
//! a description that carried it would be a form model.
|
| 83 |
|
- |
//!
|
| 84 |
|
- |
//! 0.9.0 opens [`RowPart`], which was the last closed enum in the vocabulary,
|
| 85 |
|
- |
//! and adds [`RowPart::Tokens`]. Both halves come from the same finding, made
|
| 86 |
|
- |
//! by the first two real screens described through the router rather than by
|
| 87 |
|
- |
//! reading a stylesheet.
|
| 88 |
|
- |
//!
|
| 89 |
|
- |
//! A goingson project card carries two trailing badges, a type and a toned
|
| 90 |
|
- |
//! status; a contact card carries a primary email *and* a strip of tags. `Meta`
|
| 91 |
|
- |
//! is one slot and one string, so both ports joined their facts with a
|
| 92 |
|
- |
//! separator and lost what the second one was: a status reads as text where it
|
| 93 |
|
- |
//! used to read as colour. [`Token`] already says exactly the right thing — a
|
| 94 |
|
- |
//! small labelled thing with a kind, a tone and an optional action — and could
|
| 95 |
|
- |
//! only ever be a node in its own right, never inside a row.
|
| 96 |
|
- |
//!
|
| 97 |
|
- |
//! So the missing thing was permission rather than a concept. `Tokens` is that
|
| 98 |
|
- |
//! permission, and `#[non_exhaustive]` arrives with it so the next member is not
|
| 99 |
|
- |
//! a lockstep event across three renderers. The pairing is the point: this
|
| 100 |
|
- |
//! enum's own consumer in `makeover-webview` carried a comment predicting it
|
| 101 |
|
- |
//! would stop compiling one day, which is a lockstep break written down and
|
| 102 |
|
- |
//! waited for rather than prevented.
|
| 103 |
|
- |
//!
|
| 104 |
|
- |
//! Balanced Breakfast was checked before the member was added, because one
|
| 105 |
|
- |
//! consumer wanting something is not evidence. It packs a count and two icon
|
| 106 |
|
- |
//! buttons into the same single `Meta` slot while leaving `Actions` empty, so
|
| 107 |
|
- |
//! the slot was already straining under a second consumer for a different
|
| 108 |
|
- |
//! reason.
|
| 109 |
|
- |
//!
|
| 110 |
|
- |
//! 0.10.0 adds [`Meter`], a proportion carried as a pair rather than as a
|
| 111 |
|
- |
//! percentage. Its own docs carry the argument; the short form is that the
|
| 112 |
|
- |
//! percentage shape had already been tried in goingson and had already needed a
|
| 113 |
|
- |
//! companion flag to recover what rounding and clamping threw away.
|
| 114 |
|
- |
//!
|
| 115 |
|
- |
//! 0.11.0 is four members from the quasi proving ground, batched into one
|
| 116 |
|
- |
//! release because pre-1.0 a minor is breaking and a cascade is nine repos.
|
| 117 |
|
- |
//! Three findings that arrived with them turned out not to belong here at all:
|
| 118 |
|
- |
//! this crate has no notion of an action, a route or a destination, so anything
|
| 119 |
|
- |
//! asking what a control *calls* was never the vocabulary's to say.
|
| 120 |
|
- |
//!
|
| 121 |
|
- |
//! - [`Figure`], a value with a caption. goingson had five of them across five
|
| 122 |
|
- |
//! screens with five class vocabularies for the one shape, which is the
|
| 123 |
|
- |
//! divergence this crate exists to end, sitting in plain sight and counted for
|
| 124 |
|
- |
//! the first time.
|
| 125 |
|
- |
//! - [`RowPart::Proportion`], so a [`Meter`] can sit in a row. `Meter` reached
|
| 126 |
|
- |
//! two of its seven sites at 0.10.0 and the other five are row-shaped. Exactly
|
| 127 |
|
- |
//! [`RowPart::Tokens`]'s problem with a different payload, and it takes
|
| 128 |
|
- |
//! `Tokens`' answer: the part carries the description of a bar, not a node.
|
| 129 |
|
- |
//! - [`Field::max_length`], [`Field::min`] and [`Field::max`], joining
|
| 130 |
|
- |
//! [`Field::required`], which had been sitting here as the sole constraint
|
| 131 |
|
- |
//! while the header above claimed there were none. The set stops before
|
| 132 |
|
- |
//! `pattern`, which fails the renderer test and is one site in one app.
|
| 133 |
|
- |
//! - [`FieldKind::File`]. Every host has an honest answer — a native picker, an
|
| 134 |
|
- |
//! `<input type="file">`, a path prompt, an argument — and it carries no
|
| 135 |
|
- |
//! accepted-types list because `accept` appears at zero sites in either app.
|
| 136 |
|
- |
//!
|
| 137 |
|
- |
//! The evidence rule changed under these, and it is worth recording because four
|
| 138 |
|
- |
//! earlier decisions were made under the old one. The two-app test said a shape
|
| 139 |
|
- |
//! earns a word once a second app wants it. It is backwards: a rule that
|
| 140 |
|
- |
//! withholds a word until a second app has duplicated the code guarantees the
|
| 141 |
|
- |
//! duplication, and app three writes it a third time. The bar is now generic
|
| 142 |
|
- |
//! against bespoke — is this furniture any app would have, or is it this app's
|
| 143 |
|
- |
//! own? Bespoke keeps [`Region::Bespoke`], which already carries a completion
|
| 144 |
|
- |
//! heatmap and is the right answer for a calendar nobody will build twice.
|
| 145 |
|
- |
//!
|
| 146 |
|
- |
//! 0.12.0 is two more from the same proving ground, and the same sorting
|
| 147 |
|
- |
//! happened first: six findings came out of a measurement of goingson's whole
|
| 148 |
|
- |
//! frontend, and four of them turned out to be asking what a control *calls*,
|
| 149 |
|
- |
//! which this crate cannot say. The two that were really here:
|
| 150 |
|
- |
//!
|
| 151 |
|
- |
//! - [`Readiness`] grows from two states to four. It named `Ready` and
|
| 152 |
|
- |
//! `Pending` and stopped, so a screen whose list came back empty had nothing
|
| 153 |
|
- |
//! to say about it; goingson draws an empty state at 27 sites and Balanced
|
| 154 |
|
- |
//! Breakfast at 9. `Empty` and `Failed` are the same axis rather than a new
|
| 155 |
|
- |
//! member beside it, because a region shows one of the four and never two.
|
| 156 |
|
- |
//! `#[non_exhaustive]` arrives with them, the pairing [`RowPart`] made at
|
| 157 |
|
- |
//! 0.9.0 and for the same reason.
|
| 158 |
|
- |
//! - [`Column::sortable`], [`Column::sorted`] and [`Sort`]. The one finding in
|
| 159 |
|
- |
//! the set that completes a member rather than adding one: `Column` shipped
|
| 160 |
|
- |
//! with a width and a priority and could not say that a table is ordered by a
|
| 161 |
|
- |
//! column, so a described table could draw no caret and offer no reordering.
|
| 162 |
|
- |
//!
|
| 163 |
|
- |
//! 0.14.0 is two additive members on two `#[non_exhaustive]` enums, released
|
| 164 |
|
- |
//! together because publishing twice for that is waste and the cascade below
|
| 165 |
|
- |
//! this crate is nine repos.
|
| 166 |
|
- |
//!
|
| 167 |
|
- |
//! - [`Depth::Overlay`]. The enum could say raised, well, sunken and flat, and
|
| 168 |
|
- |
//! could not say that a surface sits *over* the page. Every renderer already
|
| 169 |
|
- |
//! had the surface — `makeover-tui`'s `Palette::overlay`,
|
| 170 |
|
- |
//! `makeover-immediate`'s `Palette::elevation`, `makeover-webview`'s
|
| 171 |
|
- |
//! `--elevation-overlay` — and none of them could be reached from a
|
| 172 |
|
- |
//! description. buckets_of_money has 16 modals waiting on it.
|
| 173 |
|
- |
//! - [`CellPart`], which is [`RowPart`] for tables. A row's parts have carried
|
| 174 |
|
- |
//! their own content intent since 0.2.0, so `.row-actions` inherits rather
|
| 175 |
|
- |
//! than taking a text colour; a table cell had no such vocabulary and
|
| 176 |
|
- |
//! `makeover-webview` emitted one undifferentiated `.cell`, so a button in a
|
| 177 |
|
- |
//! cell was painted as text. The four members are the four things quasi's
|
| 178 |
|
- |
//! `Cell` was measured to hold, and the count is in that crate's history
|
| 179 |
|
- |
//! rather than assumed here.
|
| 180 |
|
- |
//!
|
| 181 |
|
- |
//! 0.15.0 adds [`FieldKind::Date`] and [`FieldKind::DateTime`], on the argument
|
| 182 |
|
- |
//! [`FieldKind::Email`] was admitted on: a webview emits a different `type=`,
|
| 183 |
|
- |
//! which is a native picker, the platform's validation and a different keyboard
|
| 184 |
|
- |
//! on a touch device. Described as text with a "YYYY-MM-DD" hint, all three are
|
| 185 |
|
- |
//! lost.
|
| 186 |
|
- |
//!
|
| 187 |
|
- |
//! Two members and not one or five, from a count rather than from symmetry: 13
|
| 188 |
|
- |
//! sites of `date` and 13 of `datetime-local` across the MNW server and
|
| 189 |
|
- |
//! goingson, and zero of `time`, `month` or `week`. The wire format each takes
|
| 190 |
|
- |
//! is named here as [`DATE_FORMAT`] and [`DATETIME_FORMAT`], because a host
|
| 191 |
|
- |
//! left to pick its own would disagree with a server silently, and
|
| 192 |
|
- |
//! [`FieldKind::temporal`] is the pair asked about once rather than at each
|
| 193 |
|
- |
//! renderer. `FieldKind`'s own comment claiming `radio` was the last HTML input
|
| 194 |
|
- |
//! type missing was already false when 0.8.1 wrote it; these are what it was
|
| 195 |
|
- |
//! missing.
|
| 196 |
|
- |
//!
|
| 197 |
|
- |
//! What each of the 0.12.0 findings deliberately leaves out is the address — what pressing a
|
| 198 |
|
- |
//! header calls, and where an empty state's "Add your first project" button
|
| 199 |
|
- |
//! goes. That is the boundary this crate is defined by, and four findings moved
|
| 200 |
|
- |
//! across it rather than being answered here.
|
| 201 |
|
- |
//!
|
| 202 |
|
- |
//! 0.19.0 narrows [`State`] to [`State::Disabled`] alone. `State::Focus` is
|
| 203 |
|
- |
//! gone: a description never states what has focus, because what focus *is*
|
| 204 |
|
- |
//! differs per host and every renderer had already decided for itself — the
|
| 205 |
|
- |
//! webview draws it from `:focus-visible`, egui refused the variant outright,
|
| 206 |
|
- |
//! and quasi-tui honoured it once at startup and overrode it thereafter.
|
| 207 |
|
- |
//!
|
| 208 |
|
- |
//! 0.20.0 adds [`Region::Widget`], the third tier, and `#[non_exhaustive]` to
|
| 209 |
|
- |
//! [`Region`] with it. Every vocabulary finding until now had two answers
|
| 210 |
|
- |
//! available — grow the primitive set, or [`Region::Bespoke`] — and a whole
|
| 211 |
|
- |
//! class of thing is wrong for both. A carousel is not a primitive, because a
|
| 212 |
|
- |
//! terminal has none and that is the test `Node::Html` failed. It is not
|
| 213 |
|
- |
//! bespoke either, because bespoke is what one app owns and every part of a
|
| 214 |
|
- |
//! carousel is furniture plus members this crate already has.
|
| 215 |
|
- |
//!
|
| 216 |
|
- |
//! The cost of the binary was that refusing a primitive was expensive: the app
|
| 217 |
|
- |
//! hand-rolls the thing forever, so the pressure always ran toward growing the
|
| 218 |
|
- |
//! primitive set with one host's idioms. A named assembly changes what "no"
|
| 219 |
|
- |
//! costs without changing what the vocabulary can say.
|
| 220 |
|
- |
//!
|
| 221 |
|
- |
//! MNW's carousel is the first consumer and was the finding that started it: one
|
| 222 |
|
- |
//! partial, three pages, an ordered set of frames with a position, prev/next and
|
| 223 |
|
- |
//! a dot strip, all of it sayable already and none of it nameable. See wiki
|
| 224 |
|
- |
//! `widget-tier` for the ownership model, which is why this member carries a
|
| 225 |
|
- |
//! name a renderer may decline to know.
|
| 226 |
|
- |
//!
|
| 227 |
|
- |
//! 0.21.0 adds [`Image`] and [`Fit`], found by trying to describe MNW's
|
| 228 |
|
- |
//! carousel under 0.20.0's widget tier and getting one step in. Nothing named a
|
| 229 |
|
- |
//! picture. The vocabulary could say a number with a caption, a badge, a meter
|
| 230 |
|
- |
//! and a table, and could not say the thing three of MNW's public pages are
|
| 231 |
|
- |
//! mostly made of.
|
| 232 |
|
- |
//!
|
| 233 |
|
- |
//! It reads as an oversight and is a measurement: 24 `<img>` sites across 22
|
| 234 |
|
- |
//! MNW templates, against one in goingson and none in Balanced Breakfast or
|
| 235 |
|
- |
//! audiofiles. A picture is furniture a *content platform* has, and MNW is the
|
| 236 |
|
- |
//! only one in the tree, so the evidence never arrived from the two-app
|
| 237 |
|
- |
//! direction the earlier rule looked in. Under the generic-against-bespoke bar
|
| 238 |
|
- |
//! it is not close: a picture is not one app's own.
|
| 239 |
|
- |
//!
|
| 240 |
|
- |
//! A primitive rather than a widget, which is worth stating now that the tier
|
| 241 |
|
- |
//! makes it a real question. A widget is an assembly of things already sayable
|
| 242 |
|
- |
//! and a picture is a leaf, assembled from nothing. It also passes the test
|
| 243 |
|
- |
//! `Node::Html` failed — every host has an honest answer, including a terminal,
|
| 244 |
|
- |
//! which has a graphics protocol or has [`Image::alt`].
|
| 245 |
|
- |
//!
|
| 246 |
|
- |
//! [`Image`] carries no source, the split [`Act`] already makes: an address is
|
| 247 |
|
- |
//! not this crate's to hold. See its own docs, which is where the argument is.
|
| 248 |
|
- |
//!
|
| 249 |
|
- |
//! 0.22.0 finishes [`Image`], which 0.21.0 shipped unable to say how much room
|
| 250 |
|
- |
//! a picture needs. Without that a renderer cannot reserve space, so a picture
|
| 251 |
|
- |
//! occupies nothing until its bytes arrive and then takes its full height at
|
| 252 |
|
- |
//! once. Measured on MNW's landing page: a 478px jump per frame and a
|
| 253 |
|
- |
//! cumulative layout shift of 0.087 for the page.
|
| 254 |
|
- |
//!
|
| 255 |
|
- |
//! - [`Image::intrinsic`], the picture's own dimensions, carried as [`Extent`].
|
| 256 |
|
- |
//! A fact about the asset rather than a display size, which is what keeps it
|
| 257 |
|
- |
//! on this side of the deferral rule: 5120x3412 is what the file *is*, and no
|
| 258 |
|
- |
//! renderer can learn it without fetching the bytes.
|
| 259 |
|
- |
//! - [`Loading`], and the default flips to [`Loading::Eager`]. 0.21.0 emitted
|
| 260 |
|
- |
//! the webview's `loading="lazy"` for every picture, which read one
|
| 261 |
|
- |
//! consumer's habit as a rule. Deferring a picture that is on screen at first
|
| 262 |
|
- |
//! paint saves nothing and makes its shift land later. The carousel is the
|
| 263 |
|
- |
//! case that proves this cannot be one renderer-wide setting: its first frame
|
| 264 |
|
- |
//! is on screen and its others are not, in one widget, at one moment.
|
| 265 |
|
- |
//!
|
| 266 |
|
- |
//! 0.23.0 adds [`Showing`], which is three open findings collapsing into one
|
| 267 |
|
- |
//! member. A tab group could not say which tab was open, a carousel could not
|
| 268 |
|
- |
//! say which frame was up, and a disclosure could not say whether its child was
|
| 269 |
|
- |
//! showing. All three are the same missing sentence, and while it was missing a
|
| 270 |
|
- |
//! renderer had two moves: match on a widget name, or draw every child.
|
| 271 |
|
- |
//!
|
| 272 |
|
- |
//! So the widget tier was taking the blame for a gap one level below it. With
|
| 273 |
|
- |
//! this a renderer derives its chrome from the description — labels get a strip,
|
| 274 |
|
- |
//! no labels get previous/position/next — once, for every widget there will ever
|
| 275 |
|
- |
//! be, and [`Region::Widget`]'s name goes back to being app vocabulary a
|
| 276 |
|
- |
//! renderer may decline to know.
|
| 277 |
|
- |
//!
|
| 278 |
|
- |
//! Only the kind lives here. Which child is up, and what each child is called,
|
| 279 |
|
- |
//! sit with whatever holds the regions, the same split [`Selector`] already made
|
| 280 |
|
- |
//! against `Node::Select`.
|
| 281 |
|
- |
//!
|
| 282 |
|
- |
//! 0.27.0 adds [`Region::Group`], which closes a gap this crate had carried
|
| 283 |
|
- |
//! since 0.2.0 without noticing: [`Heading::Section`] is documented as naming a
|
| 284 |
|
- |
//! block within the screen, and there was no block. A section heading is a leaf
|
| 285 |
|
- |
//! beside the things it names, so the description could say a section had
|
| 286 |
|
- |
//! *started* and never that one had ended.
|
| 287 |
|
- |
//!
|
| 288 |
|
- |
//! Found by asking how a screen distinguishes groups of settings by colour, and
|
| 289 |
|
- |
//! the answer turned out to be two findings rather than one. This is the first
|
| 290 |
|
- |
//! and it is the precondition: there is nothing to tint until there is a
|
| 291 |
|
- |
//! container. The second — every renderer already resolves `category.one`
|
| 292 |
|
- |
//! through `category.six` and no description can reach any of them — is filed
|
| 293 |
|
- |
//! and not shipped here.
|
| 294 |
|
- |
//!
|
| 295 |
|
- |
//! What the colour question settled anyway, because it shapes this member: the
|
| 296 |
|
- |
//! group carries no colour and no ordinal. A renderer distinguishing sibling
|
| 297 |
|
- |
//! groups derives the assignment from their order, which is
|
| 298 |
|
- |
//! [`Region::Columns`]' reasoning about counts applied to colour — the children
|
| 299 |
|
- |
//! say, and a value here would be a second source for something the description
|
| 300 |
|
- |
//! already states by containing them.
|
| 301 |
|
- |
//!
|
| 302 |
|
- |
//! 0.28.0 is what audiofiles' forms port found it could not say, three findings
|
| 303 |
|
- |
//! filed against a working conversion rather than guessed at in advance. All
|
| 304 |
|
- |
//! three are about a *question* rather than about a control, which is the line
|
| 305 |
|
- |
//! this crate keeps having to redraw.
|
| 306 |
|
- |
//!
|
| 307 |
|
- |
//! - [`FieldKind::Range`] and [`Field::step`]. A bounded number the user drags
|
| 308 |
|
- |
//! across, where both ends being on screen is what the question means. The
|
| 309 |
|
- |
//! reading to resist is that this is [`FieldKind::Number`] with bounds, and it
|
| 310 |
|
- |
//! is [`FieldKind::Radio`]'s argument again: a validated number can be out of
|
| 311 |
|
- |
//! range and a slider cannot, so the bounds stop being a rule and become the
|
| 312 |
|
- |
//! control's extent. [`Field::bounded`] is the check a renderer asks, since a
|
| 313 |
|
- |
//! range missing an end has nothing to draw.
|
| 314 |
|
- |
//! - [`Choice::unavailable`]. An option that is real, worth showing, and cannot
|
| 315 |
|
- |
//! be picked yet. Without it an app either drops the option — and the user
|
| 316 |
|
- |
//! never learns it is there — or hand-rolls the control outside the
|
| 317 |
|
- |
//! description, which is what audiofiles' instrument panel did: a permanently
|
| 318 |
|
- |
//! disabled radio plus a hand-written line saying what would enable it.
|
| 319 |
|
- |
//! `#[non_exhaustive]` arrives on [`Choice`] in the same release, so this is
|
| 320 |
|
- |
//! the last breaking addition to it.
|
| 321 |
|
- |
//! - Not a member at all: [`Field::placeholder`] on a chooser. It was sayable
|
| 322 |
|
- |
//! already and no renderer read it, so a select with nothing chosen showed an
|
| 323 |
|
- |
//! empty box and the instruction lived on a disabled button elsewhere. The
|
| 324 |
|
- |
//! renderers moved, not the description.
|
| 325 |
|
- |
//!
|
| 326 |
|
- |
//! 0.29.1 adds [`Awaiting`], which is the sentence [`Readiness`] could say about
|
| 327 |
|
- |
//! a region and could not say about a control. A described screen could state
|
| 328 |
|
- |
//! that a list was on its way and could not state that the button just pressed
|
| 329 |
|
- |
//! is doing the thing it was pressed for, so every renderer's in-flight
|
| 330 |
|
- |
//! treatment was the app's to hand-write. The MNW server hand-writes it 57 times
|
| 331 |
|
- |
//! and hand-writes the guard against a second press twice, which is the half
|
| 332 |
|
- |
//! that matters going missing on a codebase that sells things.
|
| 333 |
|
- |
//!
|
| 334 |
|
- |
//! The mark is the fact that something outstanding will complete, once, in
|
| 335 |
|
- |
//! expected finite time. Deliberately not remoteness, since a heavy local query
|
| 336 |
|
- |
//! waits too, and deliberately not slowness, which is a judgement rather than a
|
| 337 |
|
- |
//! property. It carries an optional amount, stated only when the amount is
|
| 338 |
|
- |
//! measured, and it carries no duration at all: a renderer draws what is done
|
| 339 |
|
- |
//! over what there is plus the time so far, and never an estimate of what is
|
| 340 |
|
- |
//! left.
|
| 341 |
|
- |
//!
|
| 342 |
|
- |
//! One mark and two readings, which is what keeps a slow region from being
|
| 343 |
|
- |
//! hand-split into its own route the way MNW's payout summary is: a pressed
|
| 344 |
|
- |
//! control goes busy and locks, a region fed by an awaiting call stands in as
|
| 345 |
|
- |
//! [`Readiness::Pending`] and fills when it lands.
|
| 346 |
|
- |
//!
|
| 347 |
|
- |
//! A patch release for a new member, which is the 0.27.5 precedent rather than a
|
| 348 |
|
- |
//! new rule: nothing existing changed shape, so every consumer already asking
|
| 349 |
|
- |
//! for 0.29 keeps resolving and the suite below this crate does not have to move
|
| 350 |
|
- |
//! for a type only quasi reads. The minor releases above were minor because they
|
| 351 |
|
- |
//! also narrowed something.
|
| 352 |
|
- |
//!
|
| 353 |
|
- |
//! 0.30.0 is two members batched into one release, which is 0.11.0's precedent
|
| 354 |
|
- |
//! and its reasoning: pre-1.0 a minor is breaking, the cascade below this crate
|
| 355 |
|
- |
//! is seven repos, and paying that twice in a week for two unrelated words is
|
| 356 |
|
- |
//! the tax the batching exists to avoid.
|
| 357 |
|
- |
//!
|
| 358 |
|
- |
//! - [`FieldKind::Rich`], a field whose value is markdown source. The editing
|
| 359 |
|
- |
//! counterpart of prose already carried as markdown, and it is renderable
|
| 360 |
|
- |
//! everywhere for the reason the carrying is: editing markdown is editing
|
| 361 |
|
- |
//! text. It buys a renderer permission to offer a preview or a syntax pass and
|
| 362 |
|
- |
//! buys a host reading the value back the knowledge of what it holds; a
|
| 363 |
|
- |
//! renderer with neither draws a textarea. It says nothing about when the
|
| 364 |
|
- |
//! value is saved, because autosave is a clock. Measured against four MNW
|
| 365 |
|
- |
//! section editors that are one shape written four times.
|
| 366 |
|
- |
//! [`FieldKind::multiline`] arrives with it, since the pair is now two members
|
| 367 |
|
- |
//! every renderer has to ask about.
|
| 368 |
|
- |
//! - [`Facet`], [`Selecting`], [`FacetValue`] and [`Standing`]: a named
|
| 369 |
|
- |
//! dimension a set is narrowed by. MNW's discover page filters six ways
|
| 370 |
|
- |
//! through six mechanisms, and its filter rows carry a tick box *and* a
|
| 371 |
|
- |
//! chevron only because a tag's selection and a tag's browse position were
|
| 372 |
|
- |
//! held separately. One word covers all six, and [`Selecting::Subtree`] is the
|
| 373 |
|
- |
//! member that made it an enum rather than a bool: a tree's selection is
|
| 374 |
|
- |
//! branches taken and branches pruned, which no flat mode can express, and
|
| 375 |
|
- |
//! making one gesture do browsing and filtering together is what lets the
|
| 376 |
|
- |
//! second mechanism go. [`Standing`] has four members rather than a bool for
|
| 377 |
|
- |
//! the tree's sake — a value in force because an ancestor is, is not a value
|
| 378 |
|
- |
//! somebody picked. [`FacetValue`] splits an identifier from a label for
|
| 379 |
|
- |
//! [`Choice`]'s reason and one of its own: two leaves under different parents
|
| 380 |
|
- |
//! are legitimately both called "Ambient", and the path is what tells them
|
| 381 |
|
- |
//! apart and what nearest-ancestor-wins resolves over. Deliberately wider than
|
| 382 |
|
- |
//! that one page: audiofiles' library browser and goingson's filters are the
|
| 383 |
|
- |
//! same shape.
|
| 384 |
|
- |
//!
|
| 385 |
|
- |
//! 0.39.0 gives an *option* its second line. [`Choice::detail`] is the sentence
|
| 386 |
|
- |
//! that says what picking one means, and it is one member and not two. Measured
|
| 387 |
|
- |
//! 2026-08-29 (`5e21dcfc`), off the MNW server's un-ported markup.
|
| 388 |
|
- |
//!
|
| 389 |
|
- |
//! - **Six sites, and four of them had already folded it into the label.**
|
| 390 |
|
- |
//! `<strong>Public</strong>: Anyone can see this repository` in git settings,
|
| 391 |
|
- |
//! the same shape in the project-basics AI tier and the cart's currency
|
| 392 |
|
- |
//! conversion, `Mislabeled (wrong AI tier or category)` in the report modal.
|
| 393 |
|
- |
//! The item-type and item-pricing wizards give it its own span. The described
|
| 394 |
|
- |
//! screens fold it in miniature: `Every 15 minutes (recommended)`. One fact,
|
| 395 |
|
- |
//! six spellings, and the folding is what says the member was missing.
|
| 396 |
|
- |
//! - **It is not a price**, and the count is what decided that rather than
|
| 397 |
|
- |
//! taste. The site that asked carries a name, a price and a description; the
|
| 398 |
|
- |
//! tree's other three priced tier lists are not option lists at all — each
|
| 399 |
|
- |
//! card carries its own submit, so each is a region with a heading, a fact
|
| 400 |
|
- |
//! and an act and is sayable today. A price member would have one consumer
|
| 401 |
|
- |
//! and would mean growing a money type this crate does not have.
|
| 402 |
|
- |
//! - **Where it is drawn is [`unavailable`](Choice::unavailable)'s question met
|
| 403 |
|
- |
//! a third time**, and it takes the same answer: its own element in a radio
|
| 404 |
|
- |
//! group, run into the option's own text in a `<select>`, a row of its own on
|
| 405 |
|
- |
//! a terminal. A rule the crate had already paid for twice is the cheapest
|
| 406 |
|
- |
//! evidence that one member is the right size.
|
| 407 |
|
- |
//! - **This does not reopen 0.35.0.** That release ruled [`Candidate`] a type
|
| 408 |
|
- |
//! of its own rather than a `Choice` with a second line, and it stays one:
|
| 409 |
|
- |
//! what separates them is that an option is picked out of a set the user can
|
| 410 |
|
- |
//! see whole and a candidate is offered out of one nobody can see. The
|
| 411 |
|
- |
//! argument 0.35.0 made against a member here — that it would land on every
|
| 412 |
|
- |
//! option list including the ones with nowhere to draw it — is answered by
|
| 413 |
|
- |
//! the two sentences above: it lands as `None` on the ones that do not ask,
|
| 414 |
|
- |
//! and the one host with nowhere to draw it already had a rule.
|
| 415 |
|
- |
//!
|
| 416 |
|
- |
//! 0.38.0 describes the theme picker. [`FieldKind::Theme`], [`ThemeChoice`],
|
| 417 |
|
- |
//! [`ThemeVariant`], [`Contrast`], [`Field::themes`] and [`Field::follows`] are
|
| 418 |
|
- |
//! one screen's control named as furniture. Ruled by Max 2026-08-28
|
| 419 |
|
- |
//! (`70028e00`), against the grouped option list that was the obvious answer.
|
| 420 |
|
- |
//!
|
| 421 |
|
- |
//! - **The measurement is what rejected `Choice::group`.** `optgroup` appears
|
| 422 |
|
- |
//! at exactly one live site in the tree, in the one app not yet ported, and
|
| 423 |
|
- |
//! the non-theme grouping count is zero. Two apps' grouped pickers were
|
| 424 |
|
- |
//! deleted by their ports and both described replacements dropped the
|
| 425 |
|
- |
//! grouping on purpose. So the thing that recurs is not option lists that
|
| 426 |
|
- |
//! group; it is this picker, hand-written three times.
|
| 427 |
|
- |
//! - **Two of the four facts cannot come from an app.** A theme's group and its
|
| 428 |
|
- |
//! measured contrast tier come off the resolved theme, so the layer that
|
| 429 |
|
- |
//! loaded it is the only party holding them. `Choice::new(id, "{name}
|
| 430 |
|
- |
//! ({variant})")` is what all three apps wrote, and it turns structure into
|
| 431 |
|
- |
//! prose and drops the tier entirely.
|
| 432 |
|
- |
//! - **The order is the grouping**, rather than a returned list of groups. A
|
| 433 |
|
- |
//! renderer that draws headings walks the run of one variant; one that cannot
|
| 434 |
|
- |
//! still gets the useful order, and neither shape is made to flatten the
|
| 435 |
|
- |
//! other's.
|
| 436 |
|
- |
//! - **[`ThemeVariant`] is spelled twice on purpose.** This crate takes no
|
| 437 |
|
- |
//! dependencies, so it cannot name `makeover::Variant`, and a renderer that
|
| 438 |
|
- |
//! groups needs the groups as values. The adopter converts, in a three-arm
|
| 439 |
|
- |
//! match.
|
| 440 |
|
- |
//! - **The cost is stated rather than found later.** This is the first member
|
| 441 |
|
- |
//! here that names a subject instead of a shape of answer. It stays narrow: a
|
| 442 |
|
- |
//! theme picker, not a general host-resolved list. A second such list is when
|
| 443 |
|
- |
//! the generalisation gets measured.
|
| 444 |
|
- |
//!
|
| 445 |
|
- |
//! 0.35.0 gives a suggestion its second line. [`Candidate`] is the entry in a
|
| 446 |
|
- |
//! field's suggestion list: a value, a label, and the [`detail`] that orients
|
| 447 |
|
- |
//! it. Ruled by Max 2026-08-21 (`1fcf2e9b`) after the combobox member shipped
|
| 448 |
|
- |
//! and was then held against the two sites it was designed from, which is an
|
| 449 |
|
- |
//! order worth not repeating.
|
| 450 |
|
- |
//!
|
| 451 |
|
- |
//! - **A candidate is not a [`Choice`], and the difference is in the reading
|
| 452 |
|
- |
//! rather than the writing.** Both submit one string and read as another. An
|
| 453 |
|
- |
//! option is picked out of a set the user can see whole; a candidate is
|
| 454 |
|
- |
//! offered out of a set nobody can see, so it has to say what tells it from
|
| 455 |
|
- |
//! its neighbours. Both measured sites draw that second string today, by
|
| 456 |
|
- |
//! hand, in a second span.
|
| 457 |
|
- |
//! - **It is its own type rather than a member on [`Choice`].** `Choice` is the
|
| 458 |
|
- |
//! most-consumed struct in the vocabulary and the member would have landed on
|
| 459 |
|
- |
//! every option list in the tree the day it shipped, including the ones with
|
| 460 |
|
- |
//! nowhere to draw it.
|
| 461 |
|
- |
//! - **[`Candidate`] is `#[non_exhaustive]` from birth**, which is the whole of
|
| 462 |
|
- |
//! what 0.28.0 cost and is not being paid twice.
|
| 463 |
|
- |
//! - No `unavailable`. A suggestion that cannot be picked is a row a route
|
| 464 |
|
- |
//! should not have offered.
|
| 465 |
|
- |
//!
|
| 466 |
|
- |
//! [`detail`]: Candidate::detail
|
| 467 |
|
- |
//!
|
| 468 |
|
- |
//! 0.34.0 gives an interval a description. [`FieldKind::Interval`] and
|
| 469 |
|
- |
//! [`Field::upper_name`] say that two values are one question with two ends.
|
| 470 |
|
- |
//! Ruled by Max 2026-08-21 against audiofiles' six filter axes and the MNW
|
| 471 |
|
- |
//! server's price pair, which is HTML saying the grouping in an ARIA
|
| 472 |
|
- |
//! `role="group"` and nowhere else.
|
| 473 |
|
- |
//!
|
| 474 |
|
- |
//! - **The ends constrain each other, and nothing else in the vocabulary could
|
| 475 |
|
- |
//! say so.** Two [`FieldKind::Number`] fields are two questions: a renderer
|
| 476 |
|
- |
//! draws two labels with no relationship, and [`Field::error`] attaches to one
|
| 477 |
|
- |
//! side of a fault that belongs to both.
|
| 478 |
|
- |
//! - **It is not [`FieldKind::Range`]**, which is the reading to resist and the
|
| 479 |
|
- |
//! same resistance `Range` needed against `Number`. A range is one value
|
| 480 |
|
- |
//! inside an extent; this is two, and the extent is a bound on each rather
|
| 481 |
|
- |
//! than the question's meaning.
|
| 482 |
|
- |
//! - **Both names are stated rather than derived.** Measured the same day, the
|
| 483 |
|
- |
//! two sites disagree about affix order -- `bpm_min`/`bpm_max` against
|
| 484 |
|
- |
//! `min_price`/`max_price` -- so any rule renames one of them. One member
|
| 485 |
|
- |
//! instead of a naming convention this crate would own forever, and which
|
| 486 |
|
- |
//! member a name sits in is what says which end it is.
|
| 487 |
|
- |
//! - The crossing rule is not enforced, exactly as [`Field::min`] is not. What
|
| 488 |
|
- |
//! the description buys is one place to report the fault rather than two.
|
| 489 |
|
- |
//!
|
| 490 |
|
- |
//! [`Field::upper_name`] is a member on a struct that is not
|
| 491 |
|
- |
//! `#[non_exhaustive]`, so this is a formal break for crates.io and zero
|
| 492 |
|
- |
//! call-site edits in the tree: every literal builds on a constructor.
|
| 493 |
|
- |
//!
|
| 494 |
|
- |
//! 0.33.0 gives a number its unit. [`Field::unit`] carries what the value is
|
| 495 |
|
- |
//! measured in, and [`FieldKind::measurable`] says which kinds read it. Decided
|
| 496 |
|
- |
//! by Max 2026-08-21 (`32215e21`) against eight sites across four audiofiles
|
| 497 |
|
- |
//! files that had each independently put the unit in parentheses at the end of
|
| 498 |
|
- |
//! the label -- three of them written while the gap was a known open question.
|
| 499 |
|
- |
//!
|
| 500 |
|
- |
//! - **A unit is a fact about the value, not part of the question's name.** The
|
| 501 |
|
- |
//! two readings come apart the moment anything reads a field back rather than
|
| 502 |
|
- |
//! drawing it, which is the argument that decided it.
|
| 503 |
|
- |
//! - **The convention it replaces froze the worst placement.** A label is the
|
| 504 |
|
- |
//! sentence above the control, so unit-in-label was the same answer on every
|
| 505 |
|
- |
//! host -- including the host that had somewhere better, since egui's slider
|
| 506 |
|
- |
//! already draws a suffix beside the readout, which is what these controls did
|
| 507 |
|
- |
//! before they were described.
|
| 508 |
|
- |
//! - A string rather than a closed family, which is [`Curve`]'s argument
|
| 509 |
|
- |
//! inverted and correctly so: a curve is a mapping this crate computes, and a
|
| 510 |
|
- |
//! unit is a symbol it only carries. The measured set is `GiB`, `dBFS`, `s`
|
| 511 |
|
- |
//! and `ms`, and this crate does not know what the next consumer measures in.
|
| 512 |
|
- |
//!
|
| 513 |
|
- |
//! Additive: absent is what every field meant before.
|
| 514 |
|
- |
//!
|
| 515 |
|
- |
//! 0.32.0 is the slider's real shape. **The data of a slider is a fraction and
|
| 516 |
|
- |
//! a function taking numbers to numbers** (Max, 2026-08-21), so [`Curve`]
|
| 517 |
|
- |
//! arrives and [`Field::curve`] with it. [`Field::min`] and [`Field::max`] were
|
| 518 |
|
- |
//! never the control's extent: a slider's extent is always 0 to 1, and the
|
| 519 |
|
- |
//! bounds are `f(0)` and `f(1)`. Linear is the constant-slope case, which is
|
| 520 |
|
- |
//! why the mapping was invisible — under it the extent and the bounds coincide
|
| 521 |
|
- |
//! numerically — and why four renderers each hard-coded it without anyone
|
| 522 |
|
- |
//! deciding to.
|
| 523 |
|
- |
//!
|