max / quasi
| 1 | //! The screen tree: what a route answers with. |
| 2 | //! |
| 3 | //! Decision 3 on the wiki note puts this in quasi rather than in |
| 4 | //! `makeover-layout`, for two reasons. The tree will churn while the router is |
| 5 | //! being proven against a second host, and churning it here costs nothing where |
| 6 | //! churning it there is a breaking release against three adopters. And a route |
| 7 | //! is an address, which is the one thing `makeover-layout`'s deferral rule says |
| 8 | //! it never names. |
| 9 | //! |
| 10 | //! # What this crate adds, and what it does not |
| 11 | //! |
| 12 | //! It adds three things: an [`Action`], which is an address; a [`Slot`], which |
| 13 | //! is a region with a name a fragment can be aimed at; and ownership. |
| 14 | //! |
| 15 | //! Everything else is `makeover-layout`'s. Every member of [`Node`] composes a |
| 16 | //! vocabulary that already exists there, and that is the admission test for a |
| 17 | //! new one: if the thing being drawn has no name in the description layer, it |
| 18 | //! does not get a node here, it gets a [`Region::Handover`] or it gets named |
| 19 | //! there first. Without that rule this file becomes a widget library, which is |
| 20 | //! the failure `makeover-layout` was extracted to prevent. |
| 21 | //! |
| 22 | //! # Why these are owned when the description layer is borrowed |
| 23 | //! |
| 24 | //! `makeover-layout`'s structs borrow, because a description is built, read |
| 25 | //! once and dropped inside one frame. A router's answer outlives its handler by |
| 26 | //! construction: it is returned from a function, and its text is usually built |
| 27 | //! from state rather than found in it. So [`Field`], [`Choice`] and [`Column`] |
| 28 | //! have owned mirrors here, each with a conversion back, and the conversion is |
| 29 | //! what keeps them from drifting: adding a field over there stops the mirror |
| 30 | //! compiling over here. |
| 31 | |
| 32 | use BTreeSet; |
| 33 | |
| 34 | use makeover_layout as layout; |
| 35 | |
| 36 | use crate; |
| 37 | use crate; |
| 38 | |
| 39 | /// Where an action goes. |
| 40 | /// |
| 41 | /// The port put the URL in the row's trailing text, which made it something to |
| 42 | /// copy rather than something to follow. |
| 43 | /// |
| 44 | /// # Why this and not a separate link node |
| 45 | /// |
| 46 | /// Both were on the table. A destination keeps one concept where there would |
| 47 | /// have been two, and the cost is that every renderer now branches: a webview |
| 48 | /// emits an anchor rather than a button, and a terminal has to decide whether |
| 49 | /// it can open a browser or should show the address. That branch is honest |
| 50 | /// work, and it is work each renderer must do anyway once external addresses |
| 51 | /// exist at all. |
| 52 | /// |
| 53 | /// What it must never become is a guess. The renderer branches on this enum and |
| 54 | /// never on the shape of the string, because "starts with https" is how a route |
| 55 | /// named `/https-setup` ends up opening a browser. |
| 56 | /// |
| 57 | /// # Locality |
| 58 | /// |
| 59 | /// [`Local`](Self::Local) is for what happens without a request: arrow keys |
| 60 | /// moving a highlight, a toast dismissing itself, a price recomputing as a |
| 61 | /// slider is dragged. |
| 62 | /// |
| 63 | /// The mark hangs here, per element, because an [`Action`] does, and because |
| 64 | /// which interactions are local varies within one behaviour: typing asks the |
| 65 | /// route for suggestions, arrowing through what came back does not. Both are |
| 66 | /// actions on the same field and only one leaves. |
| 67 | /// |
| 68 | /// **What happens locally is named by the member carrying the action, never by |
| 69 | /// this variant.** A suggestion source's pick action being local says picking |
| 70 | /// sets the field; a notice's dismiss action being local says the notice goes |
| 71 | /// away. A bare [`Act`] with a local destination says only "press this and the |
| 72 | /// renderer's own affordance happens", which is a description bug everywhere |
| 73 | /// except inside a [`Region::Handover`](layout::Region::Handover) or a |
| 74 | /// [`Region::Ceded`](layout::Region::Ceded). This is the |
| 75 | /// same rule that keeps the other two variants off string-shape guessing, and |
| 76 | /// it is what stops this becoming `data-action="doTheThing"` in a new hat. |
| 77 | |
| 78 | |
| 79 | /// A path this app's router answers. |
| 80 | Route, |
| 81 | /// An address outside the app, offered as a reference. Nothing here will |
| 82 | /// ever call it. |
| 83 | /// |
| 84 | /// The reader is expected to come back, so a windowing host puts it aside |
| 85 | /// rather than in front: a webview opens a tab. See |
| 86 | /// [`Leaving`](Self::Leaving) for the other half, and read the two |
| 87 | /// together -- what separates them is the description's, and what a host |
| 88 | /// does about it is the host's. |
| 89 | External, |
| 90 | /// An address outside the app that this document hands the reader on to. |
| 91 | /// |
| 92 | /// The reader is finished here, so a host navigates in place rather than |
| 93 | /// opening anything: nothing is being kept. |
| 94 | /// |
| 95 | /// The measured consumer is MNW's custom-pages host. `u.makenot.work` |
| 96 | /// serves a creator's page with a platform strip on it, and every link in |
| 97 | /// that strip -- the brand, "View on makenot.work", the footer credit -- |
| 98 | /// goes to the apex in the same tab, because going to the apex is the |
| 99 | /// whole point of the strip. Said as [`External`](Self::External) all |
| 100 | /// three would open tabs, which is a satellite document refusing to let go |
| 101 | /// of a reader who asked to leave. |
| 102 | /// |
| 103 | /// # The split is intent, and the mechanism is still the renderer's |
| 104 | /// |
| 105 | /// A tab is window management, which is placement, which this vocabulary |
| 106 | /// has held is the host's throughout -- the same call [`Frame`] placement |
| 107 | /// and the clock a toast expires on both got. What a renderer cannot infer |
| 108 | /// from an address alone is which of the two kinds of leaving it is, and |
| 109 | /// that is what these two variants say. A terminal that has no tabs |
| 110 | /// follows both the one way it can, and is not wrong. |
| 111 | /// |
| 112 | /// [`Frame`]: crate::chrome::Frame |
| 113 | Leaving, |
| 114 | /// Nothing leaves the machine. The renderer performs it from what it |
| 115 | /// already holds, and no route is asked. |
| 116 | /// |
| 117 | /// Not "cheap" and not "fast", both of which are judgements. What it says |
| 118 | /// is that there is no request, which is a fact the description knows and |
| 119 | /// no renderer can infer. |
| 120 | /// |
| 121 | /// Who is obliged to read it is [`Renderer`](crate::Renderer)'s business: a |
| 122 | /// client renderer redraws every frame from memory and gets this for free, |
| 123 | /// a hybrid one has to be told. Read that type before implementing a |
| 124 | /// renderer against this variant. |
| 125 | Local, |
| 126 | /// Wherever the reader came from. |
| 127 | /// |
| 128 | /// A route cannot name this, because which route it is depends on history |
| 129 | /// the runtime holds and the description does not. That is what makes it a |
| 130 | /// destination rather than a path some screen computes: the description |
| 131 | /// says "back" and the host says where that is. |
| 132 | /// |
| 133 | /// # Whose history |
| 134 | /// |
| 135 | /// The runtime's, and this is the part worth stating. Each host runtime |
| 136 | /// keeps what it has been asked for -- `Outcome::Screen` pushes through its |
| 137 | /// own `remember` -- and answers this by popping it. A webview maps that |
| 138 | /// onto the browser's history, not the other way round: the description |
| 139 | /// cannot see a browser and must not be written against one. |
| 140 | /// |
| 141 | /// # Why not [`Local`](Self::Local) |
| 142 | /// |
| 143 | /// `Local` says no request is made at all. Going back usually makes one: |
| 144 | /// the runtime pops an address and calls it. What this variant says is that |
| 145 | /// the *address* is the host's to supply, which is a different fact, and |
| 146 | /// [`is_local`](Self::is_local) stays false for it. |
| 147 | /// |
| 148 | /// One thing to say it with, so a key, a visible Close and a system gesture |
| 149 | /// cannot disagree: |
| 150 | /// |
| 151 | /// ``` |
| 152 | /// # use quasi_router::{Act, Action, Chrome}; |
| 153 | /// Chrome::new().bind("escape", "Back", Action::back()); |
| 154 | /// Act::new("Close", Action::back()); |
| 155 | /// ``` |
| 156 | Back, |
| 157 | |
| 158 | |
| 159 | /// An empty route rather than an empty external address, so a half-built |
| 160 | /// [`Action`] is something this app would answer rather than somewhere it would |
| 161 | /// send a user. Same reasoning as [`Method`]'s default being the safe verb. |
| 162 | /// |
| 163 | /// Written out because `#[default]` only applies to unit variants. |
| 164 | |
| 165 | |
| 166 | SelfRoute |
| 167 | |
| 168 | |
| 169 | |
| 170 | |
| 171 | /// The route path, if it is one. |
| 172 | /// |
| 173 | /// `None` for an external address, which is the answer a host wants when it |
| 174 | /// is deciding whether it can dispatch something. |
| 175 | |
| 176 | |
| 177 | match self |
| 178 | SelfRoute => Some, |
| 179 | // `Back` has no path *here*. The runtime supplies one when it pops |
| 180 | // its history, and that is a route like any other by then. |
| 181 | SelfExternal | SelfLeaving | SelfLocal | SelfBack => None, |
| 182 | |
| 183 | |
| 184 | |
| 185 | /// The address as written, whichever kind it is. |
| 186 | /// |
| 187 | /// For rendering only. A host deciding whether to dispatch wants |
| 188 | /// [`route`](Self::route), which cannot hand back something uncallable. |
| 189 | /// |
| 190 | /// Empty for [`Local`](Self::Local), which has no address to write: the |
| 191 | /// absence of one is the whole of what that variant says. A renderer |
| 192 | /// reaching here for a local action has skipped the branch it owes. |
| 193 | |
| 194 | |
| 195 | match self |
| 196 | SelfRoute | SelfExternal | SelfLeaving => address, |
| 197 | SelfLocal | SelfBack => "", |
| 198 | |
| 199 | |
| 200 | |
| 201 | /// Whether it leaves the app. |
| 202 | /// |
| 203 | /// True for both ways of leaving it. What separates them is where the |
| 204 | /// reader ends up, which is [`Leaving`](Self::Leaving)'s subject; a host |
| 205 | /// asking this is asking whether it can dispatch the thing, and it cannot |
| 206 | /// either way. |
| 207 | |
| 208 | pub const |
| 209 | matches! |
| 210 | |
| 211 | |
| 212 | /// Whether performing it takes no request at all. |
| 213 | /// |
| 214 | /// The complement of neither of the others: an external address leaves the |
| 215 | /// app and is still a request, and this is the case where nothing is asked. |
| 216 | |
| 217 | pub const |
| 218 | matches! |
| 219 | |
| 220 | |
| 221 | /// Whether it means "wherever I came from". |
| 222 | /// |
| 223 | /// The one question a host asks before dispatching, because it is the one |
| 224 | /// destination whose address the host has and the description does not. |
| 225 | |
| 226 | pub const |
| 227 | matches! |
| 228 | |
| 229 | |
| 230 | |
| 231 | /// Where a call's answer lands, when the responder cannot say. |
| 232 | /// |
| 233 | /// The value of [`Action::replaces`], which carries the whole of when this is |
| 234 | /// set at all: only for a route the description layer does not serve. Read that |
| 235 | /// first. This type is about what may be named once you are already in that |
| 236 | /// case. |
| 237 | /// |
| 238 | /// # Why three |
| 239 | /// |
| 240 | /// Measured on the MNW server's dashboard, across the two conversions that |
| 241 | /// stopped on this. A region id covered neither. |
| 242 | /// |
| 243 | /// - Two acts target the repeated element they sit in and nothing else |
| 244 | /// (`hx-target="closest .link-row"`, `closest .tag`). Both are shared |
| 245 | /// partials called from several parents, so there is no id to name. |
| 246 | /// - Fifteen acts target nothing at all: they fire and the surface they sit on |
| 247 | /// is refetched, navigated away from, or reloaded whole (`data-after` in |
| 248 | /// `frontend/src/core/dispatch.ts`). |
| 249 | /// |
| 250 | /// # Deliberately not `#[non_exhaustive]` |
| 251 | /// |
| 252 | /// [`Outcome`](crate::Outcome)'s reasoning, and for the same reason: every |
| 253 | /// renderer has to decide what to draw for each of these, and a wildcard arm |
| 254 | /// has nothing sensible to put in it. A member added here should break every |
| 255 | /// host on purpose. |
| 256 | |
| 257 | |
| 258 | /// The region with this [`Slot::id`]. |
| 259 | /// |
| 260 | /// What this was before it was three things, and still the common case. |
| 261 | Region, |
| 262 | /// The repeated element this control is drawn inside. |
| 263 | /// |
| 264 | /// A row of a list or of a table, without either side naming the other. |
| 265 | /// The containment is already in the description — a [`Row`] holds its acts |
| 266 | /// — so the alternative was making every repeated element author a unique |
| 267 | /// id whose only purpose is to be pointed at from within itself. That is |
| 268 | /// the reasoning [`Field::suggests`] used when it gave the field its own |
| 269 | /// list rather than having two elements point at each other by id. |
| 270 | /// |
| 271 | /// Replaces the element rather than filling it: "remove this row" is what |
| 272 | /// both measured sites mean, and a row that answered into itself would |
| 273 | /// nest. |
| 274 | Enclosing, |
| 275 | /// Nothing on screen contains it, so what is showing is stale. |
| 276 | /// |
| 277 | /// The act fires and the surface it sits on is no longer trustworthy. A |
| 278 | /// statement about staleness rather than a verb naming a mechanism, which |
| 279 | /// is the same choice [`Invalidated`](crate::Invalidated) made and for the |
| 280 | /// same reason: "reload the page" is a webview sentence, and two of the |
| 281 | /// three renderers have no page to reload. |
| 282 | /// |
| 283 | /// What each renderer does with it: a webview asks the host to load the |
| 284 | /// document again, a terminal redraws, an immediate-mode host does nothing |
| 285 | /// because it was going to redraw anyway. |
| 286 | /// |
| 287 | /// This is the *control's* half. A described route says the same thing by |
| 288 | /// answering [`Outcome::Screen`](crate::Outcome::Screen), which is the |
| 289 | /// member for an effect no one region contains. |
| 290 | Everything, |
| 291 | |
| 292 | |
| 293 | /// An address a control calls when it acts. |
| 294 | /// |
| 295 | /// Decision 2: an action is a route. The webview emits this as an `hx-get` or |
| 296 | /// `hx-post`, the terminal binds a key to it, egui calls it directly. All three |
| 297 | /// are calling the same path with the same verb. |
| 298 | /// |
| 299 | /// A route is not the only thing it can be: see |
| 300 | /// [`Destination`]. Decision 2 still holds for everything the app answers, and |
| 301 | /// an external address is the case it never covered. |
| 302 | /// |
| 303 | /// It does not carry a target. What a response replaces is the *response's* |
| 304 | /// business, per decision 7, because the router is the only party that knows |
| 305 | /// what it just changed. |
| 306 | |
| 307 | |
| 308 | /// Asking or telling. |
| 309 | /// |
| 310 | /// Meaningless for a [`Destination::External`], which is nobody's route to |
| 311 | /// answer. Left on the struct rather than moved inside `Destination` |
| 312 | /// because a method that is ignored is simpler than two shapes of action. |
| 313 | pub method: Method, |
| 314 | /// Where it goes. |
| 315 | pub destination: Destination, |
| 316 | /// Values the control sends that are not in the path. |
| 317 | /// |
| 318 | /// A webview emits these as `hx-vals`; a terminal passes them straight |
| 319 | /// through. Here so that no app hand-builds a query string, which is where |
| 320 | /// escaping bugs live. |
| 321 | /// |
| 322 | /// Empty on a read. A read has nothing to send: its values are its address, |
| 323 | /// so [`Action::with`] puts them in [`Self::carried`] instead. See |
| 324 | /// [`Request`](crate::Request) for the whole of why the two are separate. |
| 325 | pub params: Params, |
| 326 | /// The view this control was offered under. |
| 327 | /// |
| 328 | /// A filtered list sends its filters on every control it draws, so that |
| 329 | /// pressing one answers with the list you were looking at rather than with a |
| 330 | /// default. Kept apart from [`Self::params`] because a screen that filters |
| 331 | /// on `status` and also writes a `status` would otherwise have one name for |
| 332 | /// two things, and the handler would read whichever landed first. |
| 333 | /// |
| 334 | /// Emitted as the query string, on the address itself, which is where a view |
| 335 | /// belongs: the link is then the view, and a middle-click reaches the same |
| 336 | /// place the control does. |
| 337 | pub carried: Params, |
| 338 | /// The region this call's answer replaces, when the responder cannot say. |
| 339 | /// |
| 340 | /// Normally nothing sets this and nothing should: a described route answers |
| 341 | /// with a `Response::Fragment` naming the region it changed, `quasi-http` |
| 342 | /// turns that into the transport's retarget header, and the router is the |
| 343 | /// only party that knows what it just changed. That is decision 7 and it is |
| 344 | /// unchanged. |
| 345 | /// |
| 346 | /// **Decision 7 assumes the responder is described, and a control may call |
| 347 | /// a route that is not.** Every write on the MNW server's dashboard goes to |
| 348 | /// a plain API route that quasi never sees and that answers with a status or |
| 349 | /// a hand-rendered fragment. Those routes cannot name a region, so if the |
| 350 | /// control does not either, nobody does: the answer lands wherever the |
| 351 | /// transport's default puts it, which for htmx is inside the button that was |
| 352 | /// pressed. That is not a second party deciding one thing. It is the only |
| 353 | /// party that can decide, because the other one is outside the description |
| 354 | /// layer. |
| 355 | /// |
| 356 | /// So: leave it unset when calling a described route, and set it when |
| 357 | /// calling something else. A screen that sets it against a described route |
| 358 | /// is overriding an answer that already knew better, and that is the misuse |
| 359 | /// decision 7 was guarding against. |
| 360 | /// |
| 361 | /// What it may name is [`Replaces`]. A region is still the common case; |
| 362 | /// the other two are the shapes the MNW server's undescribed routes were |
| 363 | /// measured to want and a region id could not say. |
| 364 | pub replaces: , |
| 365 | /// The name to keep the answer under, when the answer is a file. |
| 366 | /// |
| 367 | /// `Some` means the response is not a view: nothing swaps, and the reader |
| 368 | /// ends up holding a file called this. A webview makes that a browser |
| 369 | /// download; a terminal writes it to disk; either way the screen said what |
| 370 | /// it meant rather than a class name on a button implying it. |
| 371 | /// |
| 372 | /// Counted before adding it. Nine sites in the MNW server: five CSV export |
| 373 | /// buttons across four dashboard templates, a sixth in the item-sales tab's |
| 374 | /// own script, and three anchors carrying a `download` attribute. Six of the |
| 375 | /// nine are writes, which is what makes this a property of the action rather |
| 376 | /// than a kind of destination: a write cannot be a plain link, so the host |
| 377 | /// has to be told, and until now it was told by |
| 378 | /// `data-action="exportCsvButton"` plus two positional arguments. |
| 379 | /// |
| 380 | /// Independent of [`method`](Self::method). A read that saves is an anchor |
| 381 | /// the browser downloads instead of navigating to; a write that saves has to |
| 382 | /// be performed and then handed to the reader. Both are the same sentence |
| 383 | /// here and differ only in the emitting. |
| 384 | pub saves: , |
| 385 | /// That this call waits on something which resolves once, in expected |
| 386 | /// finite time. |
| 387 | /// |
| 388 | /// The mark itself is |
| 389 | /// [`layout::Awaiting`] and its docs carry the whole of what is described |
| 390 | /// and what is not. It rides on the action rather than on the control |
| 391 | /// because the wait is a fact about the call, and because the same call is |
| 392 | /// what a region is fed by: one mark, read twice. |
| 393 | /// |
| 394 | /// A control carrying one goes busy when it is pressed and refuses a second |
| 395 | /// press until the answer lands, which is the double-submit guard the MNW |
| 396 | /// server writes by hand twice against 57 spinners. A region carrying one |
| 397 | /// through [`Slot::fed_by`] stands in and fills. |
| 398 | /// |
| 399 | /// Not remoteness, which [`Destination`] would already answer and which |
| 400 | /// misses a heavy local query. Not slowness, which is a judgement. What it |
| 401 | /// says is that something is outstanding and will finish. |
| 402 | pub awaiting: , |
| 403 | /// That the host makes this call, and the renderer does not. |
| 404 | /// |
| 405 | /// The same shape as |
| 406 | /// [`saves`](Self::saves) and for the same reason it is stated rather than |
| 407 | /// inferred: there are calls a renderer cannot make, and the alternative to |
| 408 | /// saying so is a host reaching around the description to a control the |
| 409 | /// description already owns. |
| 410 | /// |
| 411 | /// The case it was ruled on is an upload. One described destination, and |
| 412 | /// three requests behind it: MNW asks its own server to sign a URL, PUTs the |
| 413 | /// file to S3 with it, then tells the server the file landed. A renderer |
| 414 | /// posting the field to the first of those would be wrong about the response |
| 415 | /// shape and about where the bytes go, and the sequence is not describable — |
| 416 | /// it is one address in the description because it is one thing to the |
| 417 | /// reader, and three calls in the host because that is how the bytes get |
| 418 | /// there. |
| 419 | /// |
| 420 | /// # What this costs, said out loud |
| 421 | /// |
| 422 | /// **A call marked this way is not portable.** Every other action in this |
| 423 | /// vocabulary is a sentence any host can carry out; this one is a sentence |
| 424 | /// only a host that already knows the chain can. A terminal meeting a |
| 425 | /// described upload learns the accept list, the multiplicity and that it |
| 426 | /// waits, and has nothing to perform. |
| 427 | /// |
| 428 | /// That is a real limit rather than a temporary one, and it is the price the |
| 429 | /// ruling accepted for keeping the file going browser-to-storage instead of |
| 430 | /// through the server. It is named here so the next host to meet one finds |
| 431 | /// the reason rather than the gap. |
| 432 | /// |
| 433 | /// Independent of [`method`](Self::method) and of everything else on this |
| 434 | /// type. The destination, the parameters and [`awaiting`](Self::awaiting) |
| 435 | /// all still mean what they mean; what changes is who acts on them. |
| 436 | pub by_host: bool, |
| 437 | /// That this call's answer belongs in a mount of its own, not in this one. |
| 438 | /// |
| 439 | /// The same family as |
| 440 | /// [`saves`](Self::saves) and [`replaces`](Self::replaces): all three say |
| 441 | /// where the answer lands, and this one says it lands somewhere that is not |
| 442 | /// here. |
| 443 | /// |
| 444 | /// The case it was ruled on is a compose window. goingson draws the same |
| 445 | /// compose screen in the main window and in a window of its own, and |
| 446 | /// [`Frame`](crate::Frame) already says what a mount puts around a screen. |
| 447 | /// What was missing was the sentence that asks for the second mount at all, |
| 448 | /// and without it the only ways left were a host script reaching around the |
| 449 | /// description or a menu item the description cannot see. |
| 450 | /// |
| 451 | /// # What a mount of its own means, per host |
| 452 | /// |
| 453 | /// Deliberately not "a window". A mount is whatever the renderer puts a |
| 454 | /// screen up in, and each host already has one: |
| 455 | /// |
| 456 | /// ```text |
| 457 | /// webview a second document, which the host opens as a window |
| 458 | /// immediate a viewport of its own |
| 459 | /// terminal nothing; the call is performed where it stands |
| 460 | /// ``` |
| 461 | /// |
| 462 | /// The terminal's answer is the interesting one and it is not a gap. A |
| 463 | /// second mount in a terminal would be a split or a tab, and both are that |
| 464 | /// renderer's furniture rather than the description's: a screen asking for |
| 465 | /// one would be asking for a layout. So a terminal reads this and navigates, |
| 466 | /// which is the honest degradation and is what |
| 467 | /// [`Renderer`](crate::Renderer) exists to allow. |
| 468 | /// |
| 469 | /// # Why it is not a [`Destination`] |
| 470 | /// |
| 471 | /// A destination says where the call goes. This says where its answer is |
| 472 | /// put, and the two are independent: the same address is the main window's |
| 473 | /// compose screen and the compose window's, which is the whole point of |
| 474 | /// [`Frame`]. One address, two mounts, and a screen that does not know |
| 475 | /// which one it is in. |
| 476 | /// |
| 477 | /// Independent of [`method`](Self::method), and mutually exclusive with |
| 478 | /// [`saves`](Self::saves) and [`replaces`](Self::replaces) by meaning |
| 479 | /// rather than by type: an answer cannot land in a region here, be kept as |
| 480 | /// a file, and be put up in a mount of its own. Nothing enforces that, |
| 481 | /// because a type that made it impossible would have to be a fourth |
| 482 | /// vocabulary for "where an answer goes". |
| 483 | pub elsewhere: bool, |
| 484 | /// That performing this replaces the whole document, rather than a region |
| 485 | /// of it. |
| 486 | /// |
| 487 | /// The plain "go there" that every list of links on a public site is made |
| 488 | /// of, and nothing said it until now: |
| 489 | /// [`replaces`](Self::replaces) names a region, and the whole document is |
| 490 | /// not one. |
| 491 | /// |
| 492 | /// On [`Action`] rather than as a fourth [`Destination`], knowingly against |
| 493 | /// the precedent `f35aafee` set when it put the sibling fact on the |
| 494 | /// destination. What differs between a navigation and a fragment swap is |
| 495 | /// what happens to the document, and the document is the action's business; |
| 496 | /// where it goes is the same place either way. A member would also foreclose |
| 497 | /// stacking navigation with a later destination kind, since a value can only |
| 498 | /// be one of them. |
| 499 | /// |
| 500 | /// # What each renderer does with it |
| 501 | /// |
| 502 | /// ```text |
| 503 | /// webview the anchor and no verb, so the browser navigates |
| 504 | /// terminal pushes a screen: anything open over it is put away first |
| 505 | /// immediate swaps its view, the same way |
| 506 | /// ``` |
| 507 | /// |
| 508 | /// The webview case is a narrowing rather than an addition. A read of a |
| 509 | /// route already emits an `href` beside the verb, for middle-click, |
| 510 | /// copy-link, crawlers and the page with JS off; a navigating act keeps that |
| 511 | /// anchor and drops the htmx swap, which would otherwise put a whole screen |
| 512 | /// inside the page it was meant to leave. |
| 513 | /// |
| 514 | /// Independent of [`method`](Self::method) on the type, and a read in |
| 515 | /// practice: navigation is asking for a place. A renderer meeting it on a |
| 516 | /// write performs the write as it otherwise would, since an anchor there |
| 517 | /// would ask where the description said to tell. |
| 518 | /// |
| 519 | /// Mutually exclusive with [`replaces`](Self::replaces), |
| 520 | /// [`saves`](Self::saves) and [`elsewhere`](Self::elsewhere) by meaning |
| 521 | /// rather than by type, for the reason `elsewhere` records: an answer cannot |
| 522 | /// land in a region here, be kept as a file, go up in a mount of its own, |
| 523 | /// and be the whole document. |
| 524 | pub navigates: bool, |
| 525 | |
| 526 | |
| 527 | |
| 528 | /// A read. |
| 529 | |
| 530 | Self |
| 531 | method: Get, |
| 532 | destination: Route, |
| 533 | params: new, |
| 534 | carried: new, |
| 535 | saves: None, |
| 536 | replaces: None, |
| 537 | awaiting: None, |
| 538 | by_host: false, |
| 539 | elsewhere: false, |
| 540 | navigates: false, |
| 541 | |
| 542 | |
| 543 | |
| 544 | /// A write. |
| 545 | |
| 546 | Self |
| 547 | method: Post, |
| 548 | destination: Route, |
| 549 | params: new, |
| 550 | carried: new, |
| 551 | saves: None, |
| 552 | replaces: None, |
| 553 | awaiting: None, |
| 554 | by_host: false, |
| 555 | elsewhere: false, |
| 556 | navigates: false, |
| 557 | |
| 558 | |
| 559 | |
| 560 | /// A write that removes what is at the address. |
| 561 | /// |
| 562 | /// Reach for it when the route the app already answers is a `DELETE`, not |
| 563 | /// to editorialise about what a `POST` means: the verb here exists to |
| 564 | /// address an interface, and a route that deletes over `POST` is still |
| 565 | /// [`post`](Self::post). |
| 566 | |
| 567 | Self |
| 568 | method: Delete, |
| 569 | destination: Route, |
| 570 | params: new, |
| 571 | carried: new, |
| 572 | saves: None, |
| 573 | replaces: None, |
| 574 | awaiting: None, |
| 575 | by_host: false, |
| 576 | elsewhere: false, |
| 577 | navigates: false, |
| 578 | |
| 579 | |
| 580 | |
| 581 | /// A write that replaces what is at the address. |
| 582 | |
| 583 | Self |
| 584 | method: Put, |
| 585 | destination: Route, |
| 586 | params: new, |
| 587 | carried: new, |
| 588 | saves: None, |
| 589 | replaces: None, |
| 590 | awaiting: None, |
| 591 | by_host: false, |
| 592 | elsewhere: false, |
| 593 | navigates: false, |
| 594 | |
| 595 | |
| 596 | |
| 597 | /// Somewhere outside the app. |
| 598 | /// |
| 599 | /// [`Method::Get`], because following a link asks and does not tell, and a |
| 600 | /// host that ignores the method loses nothing by it. |
| 601 | |
| 602 | Self |
| 603 | method: Get, |
| 604 | destination: External, |
| 605 | params: new, |
| 606 | carried: new, |
| 607 | saves: None, |
| 608 | replaces: None, |
| 609 | awaiting: None, |
| 610 | by_host: false, |
| 611 | elsewhere: false, |
| 612 | navigates: false, |
| 613 | |
| 614 | |
| 615 | |
| 616 | /// Somewhere outside the app, and the reader is going there. |
| 617 | /// |
| 618 | /// [`external`](Self::external)'s other half. See [`Destination::Leaving`] |
| 619 | /// for the split: a reference is kept aside, and this replaces the page. |
| 620 | /// |
| 621 | /// [`Method::Get`] for `external`'s reason. |
| 622 | |
| 623 | Self |
| 624 | method: Get, |
| 625 | destination: Leaving, |
| 626 | params: new, |
| 627 | carried: new, |
| 628 | saves: None, |
| 629 | replaces: None, |
| 630 | awaiting: None, |
| 631 | by_host: false, |
| 632 | elsewhere: false, |
| 633 | navigates: false, |
| 634 | |
| 635 | |
| 636 | |
| 637 | /// Wherever the reader came from. |
| 638 | /// |
| 639 | /// See [`Destination::Back`], which carries the whole argument: the address |
| 640 | /// is the host's and the description does not have it. One thing to say it |
| 641 | /// with, so a key binding, a visible Close and a system gesture cannot |
| 642 | /// disagree about where back goes. |
| 643 | /// |
| 644 | /// [`Method::Get`], for [`external`](Self::external)'s reason: what the |
| 645 | /// host performs is a read of somewhere it has been, and the safe verb is |
| 646 | /// the honest default. |
| 647 | |
| 648 | |
| 649 | Self |
| 650 | method: Get, |
| 651 | destination: Back, |
| 652 | params: new, |
| 653 | carried: new, |
| 654 | saves: None, |
| 655 | replaces: None, |
| 656 | awaiting: None, |
| 657 | by_host: false, |
| 658 | elsewhere: false, |
| 659 | navigates: false, |
| 660 | |
| 661 | |
| 662 | |
| 663 | /// Something that happens without a request. |
| 664 | /// |
| 665 | /// See [`Destination::Local`], and read it before reaching for this: what |
| 666 | /// happens is named by the member this action is set on, never by the |
| 667 | /// action itself. A [`Node::Act`] built straight from this says only "press |
| 668 | /// this and something local happens", which no renderer can perform. |
| 669 | /// |
| 670 | /// [`Method::Get`], for [`external`](Self::external)'s reason: nothing is |
| 671 | /// asked, so nothing reads the verb, and the safe one is the honest |
| 672 | /// default. [`params`](Self::params) still carries what the behaviour acts |
| 673 | /// on — which suggestion was picked, which notice was dismissed — because |
| 674 | /// that is a value and not an address. |
| 675 | |
| 676 | |
| 677 | Self |
| 678 | method: Get, |
| 679 | destination: Local, |
| 680 | params: new, |
| 681 | carried: new, |
| 682 | saves: None, |
| 683 | replaces: None, |
| 684 | awaiting: None, |
| 685 | by_host: false, |
| 686 | elsewhere: false, |
| 687 | navigates: false, |
| 688 | |
| 689 | |
| 690 | |
| 691 | /// Put this call's answer into the region with this id. |
| 692 | /// |
| 693 | /// For a route the description layer does not serve. See |
| 694 | /// [`replaces`](Self::replaces) before reaching for it. |
| 695 | |
| 696 | |
| 697 | self.replaces = Some; |
| 698 | self |
| 699 | |
| 700 | |
| 701 | /// Put this call's answer in place of the repeated element it sits in. |
| 702 | /// |
| 703 | /// [`Replaces::Enclosing`], and the same caveat as |
| 704 | /// [`replacing`](Self::replacing): only for a route the description layer |
| 705 | /// does not serve. |
| 706 | |
| 707 | |
| 708 | self.replaces = Some; |
| 709 | self |
| 710 | |
| 711 | |
| 712 | /// Say this call's effect is contained by nothing on screen. |
| 713 | /// |
| 714 | /// [`Replaces::Everything`], and the same caveat as |
| 715 | /// [`replacing`](Self::replacing): only for a route the description layer |
| 716 | /// does not serve. A described route answers |
| 717 | /// [`Outcome::Screen`](crate::Outcome::Screen) instead. |
| 718 | |
| 719 | |
| 720 | self.replaces = Some; |
| 721 | self |
| 722 | |
| 723 | |
| 724 | /// Keep the answer as a file with this name, rather than showing it. |
| 725 | |
| 726 | |
| 727 | self.saves = Some; |
| 728 | self |
| 729 | |
| 730 | |
| 731 | /// Say that this call waits on something which resolves once, with nothing |
| 732 | /// countable about the wait. |
| 733 | /// |
| 734 | /// The common case: a round trip to a payment provider, a report the server |
| 735 | /// assembles, a heavy local query. The renderer draws it indeterminate, |
| 736 | /// because manufacturing a figure for it is the prediction |
| 737 | /// [`layout::Awaiting`] refuses. |
| 738 | |
| 739 | pub const |
| 740 | self.awaiting = Some; |
| 741 | self |
| 742 | |
| 743 | |
| 744 | /// Say that it waits, and how much there is to get through. |
| 745 | /// |
| 746 | /// Only with a measured figure. An upload knows its file length; nothing |
| 747 | /// else here may guess one, since a renderer cannot tell a measurement from |
| 748 | /// an estimate once it is written down. |
| 749 | |
| 750 | pub const |
| 751 | self.awaiting = Some; |
| 752 | self |
| 753 | |
| 754 | |
| 755 | /// Whether this call waits on something. |
| 756 | |
| 757 | pub const |
| 758 | self.awaiting.is_some |
| 759 | |
| 760 | |
| 761 | /// Put this call's answer up in a mount of its own. |
| 762 | /// |
| 763 | /// See [`elsewhere`](Self::elsewhere) for what a mount is on each host and |
| 764 | /// for why a terminal is allowed to ignore it. |
| 765 | |
| 766 | pub const |
| 767 | self.elsewhere = true; |
| 768 | self |
| 769 | |
| 770 | |
| 771 | /// Performing this replaces the whole document. |
| 772 | /// |
| 773 | /// See [`navigates`](Self::navigates) for what each renderer does with it, |
| 774 | /// and reach for it where the act is a plain "go there": a search result |
| 775 | /// standing for a page, a name standing for the profile behind it. |
| 776 | |
| 777 | pub const |
| 778 | self.navigates = true; |
| 779 | self |
| 780 | |
| 781 | |
| 782 | /// The host makes this call, not the renderer. |
| 783 | /// |
| 784 | /// See [`by_host`](Self::by_host) for what it means and for the |
| 785 | /// portability it costs. Reach for it only where a renderer genuinely |
| 786 | /// cannot make the call, which today is one case: a destination the host |
| 787 | /// reaches through a sequence of requests rather than one. |
| 788 | |
| 789 | pub const |
| 790 | self.by_host = true; |
| 791 | self |
| 792 | |
| 793 | |
| 794 | /// The route this calls, if it calls one. |
| 795 | |
| 796 | |
| 797 | self.destination.route |
| 798 | |
| 799 | |
| 800 | /// Send a value along with the call. |
| 801 | /// |
| 802 | /// On a write this is the payload: what the control is telling the route. |
| 803 | /// On a read it is the address, because a read sends nothing and its values |
| 804 | /// are where it goes — so this lands in [`Self::carried`] rather than in |
| 805 | /// [`Self::params`], and a read's two bags are never both populated. |
| 806 | /// |
| 807 | /// That is what keeps the rule one sentence at the reading end: a filter is |
| 808 | /// in `carried` whichever verb offered it. |
| 809 | /// |
| 810 | /// A [`Destination::Local`] takes the write's side of that whatever its |
| 811 | /// method says. The read rule rests on the values being the address, and a |
| 812 | /// local action has no address for them to be: putting them in `carried` |
| 813 | /// would file them as the view a call was made under, and nothing is called. |
| 814 | /// So they are the payload — which suggestion was picked, which notice was |
| 815 | /// dismissed — and that is the bag every renderer reads for one. |
| 816 | |
| 817 | |
| 818 | if self.method.mutates || self.destination.is_local |
| 819 | self.params.insert; |
| 820 | else |
| 821 | self.carried.insert; |
| 822 | |
| 823 | self |
| 824 | |
| 825 | |
| 826 | /// Keep this control pointed at the view it was offered under. |
| 827 | /// |
| 828 | /// What a filtered screen puts on every control it draws. Distinct from |
| 829 | /// [`Self::with`] on a write, and the same thing as it on a read. |
| 830 | |
| 831 | |
| 832 | self.carried.insert; |
| 833 | self |
| 834 | |
| 835 | |
| 836 | |
| 837 | /// A small labelled thing: a badge, a chip, a tag. |
| 838 | /// |
| 839 | /// [`Node::Token`]. Extracted because a row can carry these now |
| 840 | /// ([`Row::tokens`], against `makeover-layout`'s `RowPart::Tokens`), and the |
| 841 | /// alternative was defining the same fields twice and watching them drift. |
| 842 | /// |
| 843 | /// The tone rides on the tag rather than on whatever holds it, which is what |
| 844 | /// lets a strip of them say different things: a neutral type and an amber |
| 845 | /// status, side by side in one row. |
| 846 | /// |
| 847 | /// No `Hash`, because it can hold an [`Action`], which holds [`Params`], which |
| 848 | /// is a `Vec`. Same derive set as `Action` for that reason. |
| 849 | |
| 850 | |
| 851 | /// Whether it answers a click, and whether it can be removed. |
| 852 | pub kind: Token, |
| 853 | /// What it says. |
| 854 | pub label: String, |
| 855 | /// What it is saying. |
| 856 | pub tone: Tone, |
| 857 | /// Whether it is currently held down. Only meaningful for a chip. |
| 858 | pub latched: bool, |
| 859 | /// What clicking it calls, if it answers a click. |
| 860 | pub action: , |
| 861 | /// The detail behind the label, for a renderer that has somewhere to put |
| 862 | /// it. |
| 863 | /// |
| 864 | /// A badge's label is short because a badge is small, and the shipped |
| 865 | /// goingson board says the short thing and carries the long one: "Blocked" |
| 866 | /// with the block depth behind it, "Unblocks 3" with the wording, "Cycle" |
| 867 | /// with the repair instruction. A described card said the label and |
| 868 | /// dropped the detail, so the description could not say what the shipped |
| 869 | /// markup already did. |
| 870 | /// |
| 871 | /// Standing detail, not a message: it is true whenever the tag is on |
| 872 | /// screen, which is what makes it a property of the tag rather than |
| 873 | /// something a response says. **Every renderer may drop it**, and dropping |
| 874 | /// is the graceful degradation this vocabulary keeps choosing rather than a |
| 875 | /// gap; what each one does is stated in its own docs. Never put anything |
| 876 | /// here that is the only place a fact appears. |
| 877 | pub hint: , |
| 878 | |
| 879 | |
| 880 | |
| 881 | /// A neutral badge: it says something and answers nothing. |
| 882 | |
| 883 | Self |
| 884 | kind: Badge, |
| 885 | label: label.into, |
| 886 | tone: Neutral, |
| 887 | latched: false, |
| 888 | action: None, |
| 889 | hint: None, |
| 890 | |
| 891 | |
| 892 | |
| 893 | /// A chip that calls a route when clicked. |
| 894 | /// |
| 895 | /// Not removable. A removable chip is a different control with a different |
| 896 | /// affordance, so it says so rather than being inferred from carrying an |
| 897 | /// action. |
| 898 | |
| 899 | Self |
| 900 | kind: Chip , |
| 901 | label: label.into, |
| 902 | tone: Neutral, |
| 903 | latched: false, |
| 904 | action: Some, |
| 905 | hint: None, |
| 906 | |
| 907 | |
| 908 | |
| 909 | /// A chip the reader can take off again. |
| 910 | /// |
| 911 | /// The affordance a removable chip has and a plain one does not, said |
| 912 | /// rather than inferred, which is the same reason [`chip`](Self::chip) is |
| 913 | /// not removable by virtue of carrying an action. |
| 914 | |
| 915 | Self |
| 916 | kind: Chip , |
| 917 | ..Selfchip |
| 918 | |
| 919 | |
| 920 | |
| 921 | /// Set what it is saying. |
| 922 | |
| 923 | pub const |
| 924 | self.tone = tone; |
| 925 | self |
| 926 | |
| 927 | |
| 928 | /// Hold it down. Only meaningful for a chip. |
| 929 | |
| 930 | pub const |
| 931 | self.latched = latched; |
| 932 | self |
| 933 | |
| 934 | |
| 935 | /// The detail behind the label; see [`hint`](Self::hint). |
| 936 | /// |
| 937 | /// A renderer with nowhere to put it drops it, so this must never be the |
| 938 | /// only place a fact appears. |
| 939 | |
| 940 | |
| 941 | self.hint = Some; |
| 942 | self |
| 943 | |
| 944 | |
| 945 | |
| 946 | /// One option offered by a field, owned. |
| 947 | /// |
| 948 | /// The borrowed original is `makeover-layout`'s [`layout::Choice`]. Two strings |
| 949 | /// rather than one for the reason recorded there: the submitted value and the |
| 950 | /// read label are different facts, and every renderer that collapsed them has |
| 951 | /// had to un-collapse them later. |
| 952 | |
| 953 | |
| 954 | /// What is submitted. |
| 955 | pub value: String, |
| 956 | /// What is read. |
| 957 | pub label: String, |
| 958 | /// Why it cannot be picked right now, when it cannot. |
| 959 | /// |
| 960 | /// The borrowed original is [`layout::Choice::unavailable`], and everything |
| 961 | /// it says applies. One member rather than a flag beside a reason, so an |
| 962 | /// option greyed out with no explanation stays unsayable here too. |
| 963 | pub unavailable: , |
| 964 | /// The line under the label that says what picking this means. |
| 965 | /// |
| 966 | /// The borrowed original is [`layout::Choice::detail`], where the |
| 967 | /// measurement and the per-host placement live. Here it matters for the |
| 968 | /// reason `picks` does not exist on the layout type: an owned mirror is |
| 969 | /// what a handler builds, and a tier list built from the database carries a |
| 970 | /// price and a description that were never `&'static str`. |
| 971 | pub detail: , |
| 972 | |
| 973 | |
| 974 | |
| 975 | /// An option whose submitted value is also its label. |
| 976 | |
| 977 | let value = value.into; |
| 978 | Self |
| 979 | label: value.clone, |
| 980 | value, |
| 981 | unavailable: None, |
| 982 | detail: None, |
| 983 | |
| 984 | |
| 985 | |
| 986 | /// An option that reads differently from what it submits. |
| 987 | |
| 988 | Self |
| 989 | value: value.into, |
| 990 | label: label.into, |
| 991 | unavailable: None, |
| 992 | detail: None, |
| 993 | |
| 994 | |
| 995 | |
| 996 | /// The same option, not pickable yet, and why. |
| 997 | |
| 998 | |
| 999 | self.unavailable = Some; |
| 1000 | self |
| 1001 | |
| 1002 | |
| 1003 | /// The same option, with the line that says what picking it means. |
| 1004 | /// |
| 1005 | /// See [`detail`](Self::detail). Two different sentences from |
| 1006 | /// [`unless`](Self::unless), and an option carrying both has said two |
| 1007 | /// things: what the tier is, and that it is not available yet. |
| 1008 | |
| 1009 | |
| 1010 | self.detail = Some; |
| 1011 | self |
| 1012 | |
| 1013 | |
| 1014 | /// Whether the option can be picked right now. |
| 1015 | |
| 1016 | pub const |
| 1017 | self.unavailable.is_none |
| 1018 | |
| 1019 | |
| 1020 | /// Borrow as the description layer's own type. |
| 1021 | |
| 1022 | |
| 1023 | let mut choice = new; |
| 1024 | if let Some = self.detail.as_deref |
| 1025 | choice = choice.detailing; |
| 1026 | |
| 1027 | match self.unavailable.as_deref |
| 1028 | Some => choice.unless, |
| 1029 | None => choice, |
| 1030 | |
| 1031 | |
| 1032 | |
| 1033 | |
| 1034 | /// One theme a picker offers, owned. |
| 1035 | /// |
| 1036 | /// The borrowed original is `makeover-layout`'s [`layout::ThemeChoice`], and |
| 1037 | /// the reason it is not [`Choice`] is recorded there: a theme is four facts and |
| 1038 | /// an option is two. The two extra ones — which group it sits in and how |
| 1039 | /// legible it measured — are resolved by the theme layer and neither survives |
| 1040 | /// being written into a label. |
| 1041 | /// |
| 1042 | /// Built from `makeover::ThemeOption` at each adopter. That conversion is the |
| 1043 | /// seam the layering costs: `quasi-router` takes only `makeover-layout`, which |
| 1044 | /// takes nothing at all, so the crate that reads theme files off disk is not in |
| 1045 | /// this graph and the app is what joins them. |
| 1046 | |
| 1047 | |
| 1048 | /// The id stored, and what the picker submits. |
| 1049 | pub id: String, |
| 1050 | /// What the picker reads. |
| 1051 | pub name: String, |
| 1052 | /// Which group it belongs to. |
| 1053 | pub variant: ThemeVariant, |
| 1054 | /// How legible its muted text measured. |
| 1055 | pub contrast: Contrast, |
| 1056 | |
| 1057 | |
| 1058 | |
| 1059 | /// A theme, with everything a picker needs to place and mark it. |
| 1060 | /// |
| 1061 | /// Every fact is an argument, matching [`layout::ThemeChoice::new`] and for |
| 1062 | /// its reason: a theme with no variant has no group and a theme with no |
| 1063 | /// tier has no badge, so both are the control rather than trimmings on it. |
| 1064 | |
| 1065 | id: impl , |
| 1066 | name: impl , |
| 1067 | variant: ThemeVariant, |
| 1068 | contrast: Contrast, |
| 1069 | |
| 1070 | Self |
| 1071 | id: id.into, |
| 1072 | name: name.into, |
| 1073 | variant, |
| 1074 | contrast, |
| 1075 | |
| 1076 | |
| 1077 | |
| 1078 | /// Borrow as the description layer's own type. |
| 1079 | |
| 1080 | |
| 1081 | new |
| 1082 | |
| 1083 | |
| 1084 | |
| 1085 | /// One entry in a field's suggestion list, owned. |
| 1086 | /// |
| 1087 | /// The borrowed original is `makeover-layout`'s [`layout::Candidate`], and the |
| 1088 | /// reason it is not [`Choice`] is recorded there: an option and a candidate are |
| 1089 | /// submitted the same way and **read differently**. An option is picked out of |
| 1090 | /// a set the user can see whole; a candidate is offered out of a set nobody can |
| 1091 | /// see, so it carries the line that tells it from its neighbours. |
| 1092 | /// |
| 1093 | /// # What this mirror carries that the borrowed type cannot |
| 1094 | /// |
| 1095 | /// [`picks`](Self::picks). What happens when a candidate is chosen is an |
| 1096 | /// [`Action`], and `Action` is not a word the description layer has — exactly |
| 1097 | /// as [`Field::suggests`] has no counterpart on [`layout::Field`]. So the |
| 1098 | /// member lives here, on the same terms and for the same reason. |
| 1099 | /// No `Hash`, unlike [`Choice`]: [`Action`] is not hashable and a candidate |
| 1100 | /// carrying one could not be. Nothing in the tree hashes a suggestion row. |
| 1101 | |
| 1102 | |
| 1103 | /// What is submitted, and what picking writes into the field by default. |
| 1104 | pub value: String, |
| 1105 | /// What is read. |
| 1106 | pub label: String, |
| 1107 | /// The second line: what orients this candidate among rows that read alike. |
| 1108 | /// |
| 1109 | /// The borrowed original is [`layout::Candidate::detail`]. [`None`] draws |
| 1110 | /// one line rather than an empty second one. |
| 1111 | pub detail: , |
| 1112 | /// What picking this candidate does instead of writing its value. |
| 1113 | /// |
| 1114 | /// **Picking is local by default, not by definition.** Absent an action, |
| 1115 | /// picking writes |
| 1116 | /// [`value`](Self::value) into the field that owns the list, which is what |
| 1117 | /// every site that exists today wants and what ownership buys. A candidate |
| 1118 | /// that carries one has that performed instead. |
| 1119 | /// |
| 1120 | /// The two measured sites are why. MNW's search box navigates — each |
| 1121 | /// candidate is a project, item or creator page, nothing is written into |
| 1122 | /// the box, and the typed value is discarded — so its candidates carry |
| 1123 | /// `Action::get(url)`. MNW's tag box adds a facet and re-reads, which is |
| 1124 | /// the same route its drill-down checkbox already calls, so the handover |
| 1125 | /// `choose()` in hand-written JS disappears rather than moving. Neither |
| 1126 | /// wants the one behaviour ownership gives for free. |
| 1127 | /// |
| 1128 | /// It is on this type rather than on [`Choice`] deliberately. `Choice` is |
| 1129 | /// the most-consumed struct in the vocabulary and a per-row action there |
| 1130 | /// would land on every option list in the tree the day it shipped. Confined |
| 1131 | /// here, the objection this raises against itself — that the route decides |
| 1132 | /// where a pick goes, per row, which is more than a description says about |
| 1133 | /// any other control — is confined with it. |
| 1134 | pub picks: , |
| 1135 | |
| 1136 | |
| 1137 | |
| 1138 | /// A candidate whose submitted value is also its label. |
| 1139 | |
| 1140 | let value = value.into; |
| 1141 | Self |
| 1142 | label: value.clone, |
| 1143 | value, |
| 1144 | detail: None, |
| 1145 | picks: None, |
| 1146 | |
| 1147 | |
| 1148 | |
| 1149 | /// A candidate that reads differently from what it submits. |
| 1150 | |
| 1151 | Self |
| 1152 | value: value.into, |
| 1153 | label: label.into, |
| 1154 | detail: None, |
| 1155 | picks: None, |
| 1156 | |
| 1157 | |
| 1158 | |
| 1159 | /// The same candidate, with the line that tells it from its neighbours. |
| 1160 | |
| 1161 | |
| 1162 | self.detail = Some; |
| 1163 | self |
| 1164 | |
| 1165 | |
| 1166 | /// The same candidate, picking it performs this instead of writing. |
| 1167 | |
| 1168 | |
| 1169 | self.picks = Some; |
| 1170 | self |
| 1171 | |
| 1172 | |
| 1173 | /// Borrow as the description layer's own type. |
| 1174 | /// |
| 1175 | /// [`picks`](Self::picks) does not survive the borrow, and cannot: the |
| 1176 | /// description layer has no [`Action`]. A renderer reading a candidate |
| 1177 | /// through this sees the row and not what picking it does, so a renderer |
| 1178 | /// that performs picks reads this type rather than the borrowed one. |
| 1179 | |
| 1180 | |
| 1181 | let candidate = new; |
| 1182 | match self.detail.as_deref |
| 1183 | Some => candidate.detailed, |
| 1184 | None => candidate, |
| 1185 | |
| 1186 | |
| 1187 | |
| 1188 | |
| 1189 | /// One entry in a file field's accept list, owned. |
| 1190 | /// |
| 1191 | /// The borrowed original is [`layout::Accepted`], and everything it says |
| 1192 | /// applies: three shapes because all three are in the measured sites, and a |
| 1193 | /// suffix names no family because a suffix-to-family table rots. This is the |
| 1194 | /// same split [`Choice`] makes, and for the same reason — a screen is built by |
| 1195 | /// a handler and outlives the strings it was built from. |
| 1196 | /// |
| 1197 | /// [`layout::Family`] is used directly rather than mirrored. It borrows nothing, |
| 1198 | /// so there is no owned counterpart to write and a second spelling would only be |
| 1199 | /// a second place to add the fourth family to. |
| 1200 | |
| 1201 | |
| 1202 | |
| 1203 | /// Every file of a family: `image/*` and its siblings. |
| 1204 | Family, |
| 1205 | /// One media type: `image/jpeg`, `text/csv`. |
| 1206 | Type, |
| 1207 | /// One file-name suffix, with its leading dot: `.zip`, `.tar.gz`. |
| 1208 | Suffix, |
| 1209 | |
| 1210 | |
| 1211 | |
| 1212 | /// Every file of a family. |
| 1213 | |
| 1214 | pub const |
| 1215 | SelfFamily |
| 1216 | |
| 1217 | |
| 1218 | /// One media type. |
| 1219 | |
| 1220 | |
| 1221 | SelfType |
| 1222 | |
| 1223 | |
| 1224 | /// One file-name suffix, written with its leading dot. |
| 1225 | |
| 1226 | |
| 1227 | SelfSuffix |
| 1228 | |
| 1229 | |
| 1230 | /// Borrow as the description layer's own type. |
| 1231 | |
| 1232 | |
| 1233 | match self |
| 1234 | SelfFamily => Family, |
| 1235 | SelfType => Type, |
| 1236 | SelfSuffix => Suffix, |
| 1237 | |
| 1238 | |
| 1239 | |
| 1240 | |
| 1241 | /// A picture and where it is. |
| 1242 | /// |
| 1243 | /// One type, since makeover-layout 0.42.0. The borrowed twin this used to |
| 1244 | /// mirror carried the same five fields and a single method, had no consumer |
| 1245 | /// anywhere in the makeover suite, and was built in exactly one place: here. |
| 1246 | /// What survives of that split is the rule which produced it, and [`Act`] still |
| 1247 | /// carries it: makeover-layout defers every address, so [`src`](Self::src) is |
| 1248 | /// this crate's and a renderer wanting the bytes comes here for them. |
| 1249 | |
| 1250 | |
| 1251 | /// Where the picture is. A URL, or whatever the host resolves. |
| 1252 | /// |
| 1253 | /// Never interpreted here. A renderer escapes it for wherever it is putting |
| 1254 | /// it, the way it does every other app-supplied string. |
| 1255 | pub src: String, |
| 1256 | /// What the picture says, for anything not showing it. |
| 1257 | /// |
| 1258 | /// Empty means decorative. `layout::Image` carries the argument for why |
| 1259 | /// this is a `String` and not an `Option<String>`. |
| 1260 | pub alt: String, |
| 1261 | /// A visible line under it, where the app wants one. |
| 1262 | pub caption: , |
| 1263 | /// How it sits in the box it is given. |
| 1264 | pub fit: Fit, |
| 1265 | /// The picture's own dimensions, where the app knows them. |
| 1266 | /// |
| 1267 | /// `layout::Image::intrinsic` carries the argument. The short form: without |
| 1268 | /// it a renderer cannot hold the picture's place, so the picture takes no |
| 1269 | /// room until it arrives and then shoves the page down. |
| 1270 | pub intrinsic: , |
| 1271 | /// Whether the picture is needed with the screen, or can arrive later. |
| 1272 | pub loading: Loading, |
| 1273 | |
| 1274 | |
| 1275 | |
| 1276 | /// A picture at a source, carrying its own proportions. |
| 1277 | |
| 1278 | Self |
| 1279 | src: src.into, |
| 1280 | alt: alt.into, |
| 1281 | caption: None, |
| 1282 | fit: Natural, |
| 1283 | intrinsic: None, |
| 1284 | loading: Eager, |
| 1285 | |
| 1286 | |
| 1287 | |
| 1288 | /// The picture's own dimensions, so a renderer can hold its place. |
| 1289 | |
| 1290 | pub const |
| 1291 | self.intrinsic = Some; |
| 1292 | self |
| 1293 | |
| 1294 | |
| 1295 | /// This picture is not on screen yet; it can arrive when it is near. |
| 1296 | |
| 1297 | pub const |
| 1298 | self.loading = Lazy; |
| 1299 | self |
| 1300 | |
| 1301 | |
| 1302 | /// A visible line under it. |
| 1303 | |
| 1304 | |
| 1305 | self.caption = Some; |
| 1306 | self |
| 1307 | |
| 1308 | |
| 1309 | /// How it sits in its box. |
| 1310 | |
| 1311 | pub const |
| 1312 | self.fit = fit; |
| 1313 | self |
| 1314 | |
| 1315 | |
| 1316 | /// Whether the alt text says anything. |
| 1317 | /// |
| 1318 | /// An empty `alt` is a claim that the picture adds nothing to the text |
| 1319 | /// beside it, so a renderer that cannot show the bytes draws nothing rather |
| 1320 | /// than standing in for it. Came off `layout::Image` when the two merged in |
| 1321 | /// makeover-layout 0.42.0. |
| 1322 | |
| 1323 | |
| 1324 | !self.alt.is_empty |
| 1325 | |
| 1326 | |
| 1327 | |
| 1328 | /// One figure with a caption, owned. |
| 1329 | /// |
| 1330 | /// The borrowed original is [`layout::Figure`], and everything it says applies: |
| 1331 | /// the value is text because only the app knows whether the number is a |
| 1332 | /// percentage, a duration or a ratio, and the tone is carried because no |
| 1333 | /// renderer can work out that a streak of zero is worth colouring. |
| 1334 | /// |
| 1335 | /// What it calls, if it calls anything, is not here. That is an address, which |
| 1336 | /// `makeover-layout` never names, and it rides beside the figure in |
| 1337 | /// [`Node::Stats`] the way [`Row::activate`] rides beside a row's parts. |
| 1338 | |
| 1339 | |
| 1340 | /// The number, formatted the way the app means it to read. |
| 1341 | pub value: String, |
| 1342 | /// What it counts. The caption under the value. |
| 1343 | pub caption: String, |
| 1344 | /// How the value has moved, if the app is tracking that. |
| 1345 | /// |
| 1346 | /// Mirrors `layout::Figure::change`, added there at 0.13.0. Text for the |
| 1347 | /// same reason [`value`](Self::value) is: only the app knows whether a move |
| 1348 | /// reads as `+12.5%`, `+3` or `2x`. |
| 1349 | /// |
| 1350 | /// This is what [`tone`](Self::tone) was for. Counted before adding it: the |
| 1351 | /// MNW server has four screens whose stat card is a label, a value and a |
| 1352 | /// delta, and on all four the delta is the toned part while the number |
| 1353 | /// itself is an ordinary fact. Without it the delta folds into the caption, |
| 1354 | /// which loses the tone and turns a second smaller line into a longer first |
| 1355 | /// one. |
| 1356 | pub change: , |
| 1357 | /// What the figure means. [`layout::Tone::Neutral`] is an ordinary fact. |
| 1358 | /// |
| 1359 | /// Applies to [`change`](Self::change) where there is one, and to the value |
| 1360 | /// where there is not. The renderer decides which element that lands on. |
| 1361 | pub tone: Tone, |
| 1362 | |
| 1363 | |
| 1364 | |
| 1365 | /// A figure that is an ordinary fact. |
| 1366 | |
| 1367 | Self |
| 1368 | value: value.into, |
| 1369 | caption: caption.into, |
| 1370 | change: None, |
| 1371 | tone: Neutral, |
| 1372 | |
| 1373 | |
| 1374 | |
| 1375 | /// How the value has moved. |
| 1376 | |
| 1377 | |
| 1378 | self.change = Some; |
| 1379 | self |
| 1380 | |
| 1381 | |
| 1382 | /// What the figure means. |
| 1383 | |
| 1384 | pub const |
| 1385 | self.tone = tone; |
| 1386 | self |
| 1387 | |
| 1388 | |
| 1389 | /// Borrow as the description layer's own type. |
| 1390 | |
| 1391 | |
| 1392 | Figure |
| 1393 | value: &self.value, |
| 1394 | caption: &self.caption, |
| 1395 | change: self.change.as_deref, |
| 1396 | tone: self.tone, |
| 1397 | |
| 1398 | |
| 1399 | |
| 1400 | |
| 1401 | /// What a list has that it is not showing. |
| 1402 | /// |
| 1403 | /// Deliberately not virtual scrolling, which is the neighbouring thing and is |
| 1404 | /// not a description concern: goingson's `virtual-scroller.js` windows rows the |
| 1405 | /// app already holds, which is a renderer performance technique. This is a fact |
| 1406 | /// about the data — there are rows that were never fetched — and only the thing |
| 1407 | /// that fetched them knows it. |
| 1408 | /// # The position is the description layer's, the addresses are this one's |
| 1409 | /// |
| 1410 | /// [`layout::Paging`] holds where the reader is and how big the set is, and it |
| 1411 | /// is the same [`layout::Window`] a carousel instantiates. What it cannot hold |
| 1412 | /// is the way to ask for the next part, because `makeover-layout` names no |
| 1413 | /// actions at all. So this is the pairing, the way [`Row`] pairs its parts with |
| 1414 | /// [`Row::activate`] and [`Node::Stats`] pairs a figure with an address. |
| 1415 | /// |
| 1416 | /// That split is the reason the two can share an implementation. A carousel's |
| 1417 | /// frames are already in the description and moving between them asks nobody |
| 1418 | /// anything; a page's rows were never fetched and moving costs a round trip. |
| 1419 | /// Presence is the difference, and it shows up here as whether there are |
| 1420 | /// actions rather than as a second copy of the arithmetic. |
| 1421 | /// |
| 1422 | /// # Both directions, and why neither is required |
| 1423 | /// |
| 1424 | /// A host that pages forward only supplies [`forward`](Self::forward) alone and |
| 1425 | /// gets a load-more control. One that pages both ways supplies both and gets |
| 1426 | /// prev/next. A renderer offers what it was given and never invents the other, |
| 1427 | /// because an address this crate made up would not resolve. |
| 1428 | |
| 1429 | |
| 1430 | /// Where the reader is, and how much there is. |
| 1431 | pub paging: Paging, |
| 1432 | /// What asking for the next part calls. |
| 1433 | pub forward: , |
| 1434 | /// What asking for the previous part calls. |
| 1435 | pub back: , |
| 1436 | /// The pages a reader may go straight to, each with its own address. |
| 1437 | /// |
| 1438 | /// Empty is the common case and is prev/next paging. |
| 1439 | /// |
| 1440 | /// # An address per page, not a count |
| 1441 | /// |
| 1442 | /// A renderer cannot build page 5's address out of |
| 1443 | /// [`forward`](Self::forward) and [`back`](Self::back) without knowing the |
| 1444 | /// address grammar, and that grammar is the private vocabulary a conversion |
| 1445 | /// exists to retire. So the router names the addresses and the renderer |
| 1446 | /// draws them, which is the split [`Row::activate`] and [`Node::Stats`] |
| 1447 | /// already make. A number alone would put URL construction in three hosts. |
| 1448 | /// |
| 1449 | /// # Which pages, and why that is the description's call too |
| 1450 | /// |
| 1451 | /// A set of 400 pages is not a strip of 400 controls, so somebody windows |
| 1452 | /// it, and it is not this crate: MNW's `build_pagination_range` already |
| 1453 | /// windows to five around the reader, and a renderer inventing its own |
| 1454 | /// would give a different answer per host for one list. What arrives here |
| 1455 | /// is the pages being offered, in the order they should read. |
| 1456 | /// |
| 1457 | /// Which of them the reader is on is not stated twice: |
| 1458 | /// [`paging`](Self::paging) says it, and a renderer marks the jump whose |
| 1459 | /// [`Jump::page`] matches. |
| 1460 | pub jumps: , |
| 1461 | |
| 1462 | |
| 1463 | /// One page a reader can go straight to. |
| 1464 | /// |
| 1465 | /// The number is carried rather than implied by position, because what a strip |
| 1466 | /// offers is a window around the reader -- pages 8 through 12 of 20 -- and an |
| 1467 | /// index into that list is not the page it names. |
| 1468 | |
| 1469 | |
| 1470 | /// Which page this reaches, counting from one, the way |
| 1471 | /// [`layout::Paging::page`] counts. |
| 1472 | pub page: usize, |
| 1473 | /// What going there calls. |
| 1474 | pub action: Action, |
| 1475 | |
| 1476 | |
| 1477 | |
| 1478 | /// Going to this page calls this route. |
| 1479 | |
| 1480 | pub const |
| 1481 | Self |
| 1482 | |
| 1483 | |
| 1484 | |
| 1485 | |
| 1486 | /// The first `shown` of something longer, and how to ask for more. |
| 1487 | /// |
| 1488 | /// The load-more shape. The window starts at the beginning and grows, so |
| 1489 | /// there is no page to number and no way back to offer. |
| 1490 | |
| 1491 | pub const |
| 1492 | Self |
| 1493 | paging: more, |
| 1494 | forward: Some, |
| 1495 | back: None, |
| 1496 | jumps: Vecnew, |
| 1497 | |
| 1498 | |
| 1499 | |
| 1500 | /// The first `shown` of something longer, with nothing to press. |
| 1501 | /// |
| 1502 | /// [`more`](Self::more) without the address, which is a real shape rather |
| 1503 | /// than a degenerate one: audiofiles' bulk-rename preview caps its table at |
| 1504 | /// fifty rows because fifty is all a modal can show, and the rename acts on |
| 1505 | /// every name whether or not it was drawn. There is nowhere to ask for the |
| 1506 | /// rest because the rest were never missing -- the cap is a rendering |
| 1507 | /// budget, and what the reader needs to know is that there are more than |
| 1508 | /// these. |
| 1509 | /// |
| 1510 | /// A constructor and not a struct literal, which is what those two sites |
| 1511 | /// were: a literal names every field, so it breaks on each one this type |
| 1512 | /// gains, and it broke on `jumps`. |
| 1513 | |
| 1514 | pub const |
| 1515 | Self |
| 1516 | paging: more, |
| 1517 | forward: None, |
| 1518 | back: None, |
| 1519 | jumps: Vecnew, |
| 1520 | |
| 1521 | |
| 1522 | |
| 1523 | /// One page of `per`, starting at `from`. |
| 1524 | /// |
| 1525 | /// Arrives with no addresses; [`forward`](Self::forward) and |
| 1526 | /// [`back`](Self::back) add whichever of them exists. A first page has no |
| 1527 | /// back and a last page has no forward, and saying so by leaving one off is |
| 1528 | /// how a renderer knows to draw the control disabled rather than absent. |
| 1529 | |
| 1530 | pub const |
| 1531 | Self |
| 1532 | paging: pages, |
| 1533 | forward: None, |
| 1534 | back: None, |
| 1535 | jumps: Vecnew, |
| 1536 | |
| 1537 | |
| 1538 | |
| 1539 | /// How many there are altogether. |
| 1540 | /// |
| 1541 | /// Left unsaid by a host that will not pay for the count, and then left |
| 1542 | /// unsaid for good: a total arriving on a later pass widens the text that |
| 1543 | /// prints it. See "First paint is final paint" in `makeover-layout`'s |
| 1544 | /// header, and "What a handler owes the first paint" in this crate's. |
| 1545 | |
| 1546 | pub const |
| 1547 | self.paging = self.paging.of; |
| 1548 | self |
| 1549 | |
| 1550 | |
| 1551 | /// What asking for the next part calls. |
| 1552 | |
| 1553 | |
| 1554 | self.forward = Some; |
| 1555 | self |
| 1556 | |
| 1557 | |
| 1558 | /// What asking for the previous part calls. |
| 1559 | |
| 1560 | |
| 1561 | self.back = Some; |
| 1562 | self |
| 1563 | |
| 1564 | |
| 1565 | /// Offer a jump straight to this page. |
| 1566 | /// |
| 1567 | /// See [`jumps`](Self::jumps). Adds rather than replaces, because a strip |
| 1568 | /// is built one page at a time out of whatever window the host chose, and |
| 1569 | /// the order of the calls is the order they read. |
| 1570 | |
| 1571 | |
| 1572 | self.jumps.push; |
| 1573 | self |
| 1574 | |
| 1575 | |
| 1576 | /// Whether one of the offered jumps is the page the reader is on. |
| 1577 | /// |
| 1578 | /// Asked by every renderer that draws a strip, so the comparison is here |
| 1579 | /// rather than three times: a host that windowed its pages badly can offer |
| 1580 | /// a strip the reader is not in, and each renderer answering that for |
| 1581 | /// itself is how they come to disagree about what "current" means. |
| 1582 | |
| 1583 | |
| 1584 | self.paging.page == Some |
| 1585 | |
| 1586 | |
| 1587 | /// Borrow as the description layer's own type. |
| 1588 | |
| 1589 | pub const |
| 1590 | self.paging |
| 1591 | |
| 1592 | |
| 1593 | |
| 1594 | /// Prose in a row part: what it says, and whether it is markdown. |
| 1595 | /// |
| 1596 | /// `secondary` has always been a `String`, and three call sites had markdown to |
| 1597 | /// put in it: the goingson projects card's description, the mail list's body |
| 1598 | /// preview, and a contact's note next. Each put the **source** in, so a row read |
| 1599 | /// `**Ships Q3.** See [the brief](https://...)` where the screen it stands in |
| 1600 | /// for reads the sentence. Flattening at the call site fixes what the user sees |
| 1601 | /// and loses the fact on the way: a renderer receiving the row cannot tell text |
| 1602 | /// an author typed from markdown somebody already flattened, so it cannot decide |
| 1603 | /// for itself, and the flattening is copied per site. |
| 1604 | /// |
| 1605 | /// This is [`Meter`]'s answer, not [`Node`]'s. The row still holds no node it |
| 1606 | /// holds a two-case value saying which of two things its string is. A webview |
| 1607 | /// renders the markdown inline, a terminal can emit bold, and a renderer that |
| 1608 | /// wants neither flattens it, each from the same description. |
| 1609 | /// |
| 1610 | /// [`Text`](Self::Text) is the default in every sense: `From<&str>` and |
| 1611 | /// `From<String>` both produce it, so `.secondary("...")` means what it always |
| 1612 | /// meant and no existing call site changes. |
| 1613 | |
| 1614 | |
| 1615 | /// Text as written. A renderer escapes it and draws it, and nothing in it |
| 1616 | /// is markup however it is punctuated. |
| 1617 | Text, |
| 1618 | /// Markdown source, carried as source for the reason [`Node::Rich`] does: |
| 1619 | /// every renderer has an honest answer because each renders it its own way, |
| 1620 | /// and nothing here is markup a renderer has to trust. |
| 1621 | Rich, |
| 1622 | |
| 1623 | |
| 1624 | /// How much of the markdown format a source may use. |
| 1625 | /// |
| 1626 | /// One of the two axes [`Node::Rich`] carries, and the one about *shape*. |
| 1627 | /// [`Trust`] is the other, about *provenance*. They correlate -- the sources an |
| 1628 | /// app does not vouch for are usually the ones it also wants to keep plain -- |
| 1629 | /// and they are not the same question, which is why fusing them was a mistake |
| 1630 | /// worth undoing: a platform's own policy page is prose it wrote and wants |
| 1631 | /// followed, and a creator's long-form description is a document it did not |
| 1632 | /// write and still wants tables in. |
| 1633 | /// |
| 1634 | /// Nothing here says *phrase*. One line of markdown inside a row is decided by |
| 1635 | /// where the node sits rather than by what it declares: a row part holds no |
| 1636 | /// node, and the run a cell holds is rendered inline whatever this says. See |
| 1637 | /// the renderer's row handling. |
| 1638 | |
| 1639 | |
| 1640 | /// Paragraphs, lists, emphasis, code and links. |
| 1641 | /// |
| 1642 | /// The default, and what ordinary prose needs. No tables, no footnotes, no |
| 1643 | /// task lists, no images -- a page's own sentence does not want them and a |
| 1644 | /// stranger's paragraph should not have them by default. |
| 1645 | |
| 1646 | Sentence, |
| 1647 | /// Everything the format has: tables, task lists, footnotes, strikethrough, |
| 1648 | /// smart punctuation and images. |
| 1649 | /// |
| 1650 | /// For a source that is a document rather than a sentence. A creator's |
| 1651 | /// long-form item description is the measured consumer. |
| 1652 | Document, |
| 1653 | |
| 1654 | |
| 1655 | /// How far the app vouches for a source. |
| 1656 | /// |
| 1657 | /// [`Node::Rich`]'s other axis. See [`Richness`] for why they are two. |
| 1658 | /// |
| 1659 | /// **The default is [`Untrusted`](Self::Untrusted)**, written out rather than |
| 1660 | /// derived, for the reason `Discovery::indexable` is: the unsafe direction has |
| 1661 | /// to be the one somebody types. A description that said nothing about |
| 1662 | /// provenance and got the permissive treatment would be a hole nobody could see |
| 1663 | /// in a diff. |
| 1664 | |
| 1665 | |
| 1666 | /// Somebody the app does not vouch for wrote it. |
| 1667 | /// |
| 1668 | /// A renderer hardens it: links get `nofollow`, raw markup in the source is |
| 1669 | /// dropped rather than passed through, and schemes a host would fetch are |
| 1670 | /// filtered. Forum posts, item descriptions, anything a reader typed. |
| 1671 | |
| 1672 | Untrusted, |
| 1673 | /// The app wrote it. |
| 1674 | /// |
| 1675 | /// The screen's own copy, in the same repository as the screen. A renderer |
| 1676 | /// takes it at its word: its links are the app's own and are followed, and |
| 1677 | /// its markup is the app's own. |
| 1678 | /// |
| 1679 | /// This is not a claim about the *reader*; it is a claim about the author. |
| 1680 | /// A string that reached the screen from a database is untrusted however |
| 1681 | /// well-behaved it has been. |
| 1682 | Trusted, |
| 1683 | |
| 1684 | |
| 1685 | |
| 1686 | /// Markdown, to be rendered by whoever draws it. |
| 1687 | |
| 1688 | SelfRich |
| 1689 | |
| 1690 | |
| 1691 | /// The string, whichever case this is. |
| 1692 | /// |
| 1693 | /// For a renderer that treats both the same, and for a test that does not |
| 1694 | /// care. A renderer that draws this without looking at the case is drawing |
| 1695 | /// markdown as text, which is the bug this type exists to make visible |
| 1696 | /// rather than impossible. |
| 1697 | |
| 1698 | |
| 1699 | match self |
| 1700 | SelfText | SelfRich => text, |
| 1701 | |
| 1702 | |
| 1703 | |
| 1704 | /// Whether there is anything to draw. |
| 1705 | |
| 1706 | |
| 1707 | self.source.is_empty |
| 1708 | |
| 1709 | |
| 1710 | |
| 1711 | |
| 1712 | |
| 1713 | SelfText |
| 1714 | |
| 1715 | |
| 1716 | |
| 1717 | |
| 1718 | |
| 1719 | SelfText |
| 1720 | |
| 1721 | |
| 1722 | |
| 1723 | |
| 1724 | |
| 1725 | SelfText |
| 1726 | |
| 1727 | |
| 1728 | |
| 1729 | /// How much of a set is done, owned. |
| 1730 | /// |
| 1731 | /// The borrowed original is [`layout::Meter`], which arrived at 0.10.0 for this. |
| 1732 | /// Before it, a screen with a progress bar concatenated the two numbers into its |
| 1733 | /// heading — "Subtasks 3/7" — which keeps both facts and loses the reading, the |
| 1734 | /// same way a toned status badge read as prose before [`Row::tokens`]. |
| 1735 | /// |
| 1736 | /// Its own struct as of 0.11.0, having been the inline payload of |
| 1737 | /// [`Node::Meter`]. Extracted for the reason [`Tag`] was: a row can carry one |
| 1738 | /// now ([`Row::meter`], against `makeover-layout`'s `RowPart::Proportion`), and |
| 1739 | /// the alternative was defining the same four fields twice and watching them |
| 1740 | /// drift. |
| 1741 | /// |
| 1742 | /// Carries the pair rather than a percentage for the reason [`layout::Meter`] |
| 1743 | /// gives: a bar that is full because it landed exactly and one that is full |
| 1744 | /// because it ran over are the same width and not the same fact. |
| 1745 | /// |
| 1746 | /// This carries a proportion, and one answer never carries motion. That is the |
| 1747 | /// whole of the restriction: **a meter does not tick**, so no renderer animates |
| 1748 | /// one between answers. |
| 1749 | /// |
| 1750 | /// The proportion need not be of a static set. Files written of files to write |
| 1751 | /// and subtasks done of subtasks are, and audiofiles' transport is not: it |
| 1752 | /// states a playback position against a duration, re-answered as the host |
| 1753 | /// redraws. That is the same member under the same rule, and the doc used to |
| 1754 | /// call the fact static, which was true of the first consumers and never of the |
| 1755 | /// restriction. |
| 1756 | /// |
| 1757 | /// Live progress is described by re-answering. Each answer states the |
| 1758 | /// proportion as it stood when the route was asked, and the host asks again |
| 1759 | /// through its runtime's `reload`, which is where the cadence belongs: how |
| 1760 | /// often a fact goes stale is a property of the app holding it. So an export |
| 1761 | /// reporting files written is a meter, re-answered, rather than something a |
| 1762 | /// description was refusing. |
| 1763 | /// |
| 1764 | /// What still has no proportion to state gets [`layout::Readiness::Pending`], |
| 1765 | /// and what has finished and wants saying gets a [`layout::Notice::Toast`]. |
| 1766 | |
| 1767 | |
| 1768 | /// How much is done. May exceed [`total`](Self::total). |
| 1769 | pub done: u32, |
| 1770 | /// How much there is to do. |
| 1771 | pub total: u32, |
| 1772 | /// What the proportion means. No renderer can derive this. |
| 1773 | pub tone: Tone, |
| 1774 | /// What is being counted: "subtasks", "tasks". The noun, not the ratio. |
| 1775 | pub label: , |
| 1776 | |
| 1777 | |
| 1778 | |
| 1779 | /// A proportion, untoned and unlabelled. |
| 1780 | |
| 1781 | pub const |
| 1782 | Self |
| 1783 | done, |
| 1784 | total, |
| 1785 | tone: Neutral, |
| 1786 | label: None, |
| 1787 | |
| 1788 | |
| 1789 | |
| 1790 | /// What the proportion means. |
| 1791 | |
| 1792 | pub const |
| 1793 | self.tone = tone; |
| 1794 | self |
| 1795 | |
| 1796 | |
| 1797 | /// What is being counted. The noun, not the ratio. |
| 1798 | |
| 1799 | |
| 1800 | self.label = Some; |
| 1801 | self |
| 1802 | |
| 1803 | |
| 1804 | /// Borrow as the description layer's own type. |
| 1805 | |
| 1806 | |
| 1807 | Meter |
| 1808 | done: self.done, |
| 1809 | total: self.total, |
| 1810 | tone: self.tone, |
| 1811 | label: self.label.as_deref, |
| 1812 | |
| 1813 | |
| 1814 | |
| 1815 | |
| 1816 | /// How a slider's position becomes its value, and how finely it moves. |
| 1817 | /// |
| 1818 | /// The borrowed original is [`layout::Curve`], and the reasoning lives there: |
| 1819 | /// the data of a slider is a fraction and a function taking numbers to numbers, |
| 1820 | /// so [`Field::min`] and [`Field::max`] are `f(0)` and `f(1)` rather than the |
| 1821 | /// control's extent. This is the owned mirror, holding its step as a `String` |
| 1822 | /// for the reason every other member here does. |
| 1823 | |
| 1824 | |
| 1825 | |
| 1826 | /// Constant slope. The default, and what every described range meant before |
| 1827 | /// the curve existed. |
| 1828 | Linear |
| 1829 | /// The granularity, in the value's own units. |
| 1830 | step: , |
| 1831 | , |
| 1832 | /// Constant ratio, for an extent spanning orders of magnitude. |
| 1833 | Logarithmic |
| 1834 | /// The granularity, in the value's own units. |
| 1835 | step: , |
| 1836 | , |
| 1837 | |
| 1838 | |
| 1839 | |
| 1840 | |
| 1841 | SelfLinear |
| 1842 | |
| 1843 | |
| 1844 | |
| 1845 | |
| 1846 | /// Read this curve as the description layer's own type. |
| 1847 | |
| 1848 | |
| 1849 | match self |
| 1850 | SelfLinear => Linear |
| 1851 | step: step.as_deref, |
| 1852 | , |
| 1853 | SelfLogarithmic => Logarithmic |
| 1854 | step: step.as_deref, |
| 1855 | , |
| 1856 | |
| 1857 | |
| 1858 | |
| 1859 | /// The same curve with its granularity replaced. |
| 1860 | |
| 1861 | |
| 1862 | match self |
| 1863 | SelfLinear => SelfLinear , |
| 1864 | SelfLogarithmic => SelfLogarithmic , |
| 1865 | |
| 1866 | |
| 1867 | |
| 1868 | |
| 1869 | /// A question answered zero or more times, with the reader adding and removing |
| 1870 | /// the slots. |
| 1871 | /// |
| 1872 | /// **A repeating group enters the vocabulary, submitting once.** Every other |
| 1873 | /// member of [`layout::FieldKind`] is one field holding one value or one |
| 1874 | /// choice, and nothing said "this question is answered N times". A description |
| 1875 | /// that needs one says it here, and every renderer draws the slots, the |
| 1876 | /// control that adds one and the control that takes one away. |
| 1877 | /// |
| 1878 | /// # What it is not |
| 1879 | /// |
| 1880 | /// Not a list of forms, which is N submits. This is one submit |
| 1881 | /// carrying N values under one question, which is what makes the two hard parts |
| 1882 | /// hard: the names have to come back apart, and a refusal has to be able to say |
| 1883 | /// *which* answer is wrong. |
| 1884 | /// |
| 1885 | /// Not [`Field::multiple`], which was the near miss and was rejected in the |
| 1886 | /// same ruling. That is [`layout::FieldKind::File`]'s pick-several and a |
| 1887 | /// multi-select: one control taking a set, with one value, one error and no |
| 1888 | /// slots for the reader to add. Widening it would have put two shapes behind |
| 1889 | /// one word and left every renderer disambiguating them from the kind beside |
| 1890 | /// it. |
| 1891 | /// |
| 1892 | /// # The names on the wire |
| 1893 | /// |
| 1894 | /// `name[0]`, `name[1]`, and [`Repeat::at`] is the only place that is spelled. |
| 1895 | /// Picked once here rather than per renderer, because the three of them have to |
| 1896 | /// agree with each other and with whatever reads the submission back: |
| 1897 | /// [`Params::repeated`](crate::Params::repeated) is that reader. |
| 1898 | /// |
| 1899 | /// The index rather than N values under one bare name, which the wire already |
| 1900 | /// allows and [`Params::get_all`](crate::Params::get_all) already reads. An |
| 1901 | /// index survives a slot the reader emptied and a slot a host dropped: an error |
| 1902 | /// reported against the third answer means the third box on the way back |
| 1903 | /// whatever happened to the second, where a positional list renumbers itself |
| 1904 | /// silently and attaches the message to a different value. |
| 1905 | /// |
| 1906 | /// Holes are legitimate for the same reason and every reader here tolerates |
| 1907 | /// them: a browser removing the second of three slots may leave `0` and `2` |
| 1908 | /// standing rather than renumbering, and the answers are still the answers. |
| 1909 | /// |
| 1910 | /// # One consumer, which is enough |
| 1911 | /// |
| 1912 | /// goingson's event form, whose `Event.reminder_offsets_seconds` is a |
| 1913 | /// `Vec<i64>` capped at eight by `sanitize_reminder_offsets`. Nothing else in |
| 1914 | /// the described screens submits a variable-length set under one question. |
| 1915 | /// |
| 1916 | /// # A slot is one question, or several named ones |
| 1917 | /// |
| 1918 | /// One question is the ordinary slot and the one goingson's reminders use: |
| 1919 | /// [`Instance::value`] is the answer and [`Instance::error`] is what is wrong |
| 1920 | /// with it. |
| 1921 | /// |
| 1922 | /// Several is [`Instance::parts`], and it exists because a picked-file queue is |
| 1923 | /// a slot that is a name, a size and a failure of its own. The growth this |
| 1924 | /// type's header left open is the one that was taken: `name[0].size` beside |
| 1925 | /// `name[0]`, so a slot that is one question submits exactly as it always did. |
| 1926 | /// [`Answer`] is one of the several and [`Progress`] is the per-slot status, |
| 1927 | /// which is the half [`Repeating`] deliberately does not carry. |
| 1928 | |
| 1929 | |
| 1930 | /// The answers standing right now, in the order they are asked. |
| 1931 | /// |
| 1932 | /// Empty is a question nobody has answered yet, which is what "zero or |
| 1933 | /// more" means and what a renderer draws as the add control alone. |
| 1934 | pub instances: , |
| 1935 | /// The fewest slots the reader may leave standing. |
| 1936 | /// |
| 1937 | /// `0` is the ordinary answer and is why removing the last slot is allowed. |
| 1938 | /// A question that must be answered at least once says `1`, which is |
| 1939 | /// [`Field::required`]'s reading for a repeating question: the flag is |
| 1940 | /// about one box holding a value, and this is about how many boxes there |
| 1941 | /// are. |
| 1942 | pub least: usize, |
| 1943 | /// The most slots the reader may add, if there is a ceiling. |
| 1944 | /// |
| 1945 | /// `None` is no ceiling. goingson's reminders carry `Some(8)`, which is the |
| 1946 | /// cap `sanitize_reminder_offsets` already enforces on the way in: stating |
| 1947 | /// it here is what stops the reader filling in a ninth slot that the write |
| 1948 | /// path silently drops. |
| 1949 | pub most: , |
| 1950 | /// The named questions each slot is made of, when a slot is several. |
| 1951 | /// |
| 1952 | /// Empty is a slot that is one question, which is every repeating question |
| 1953 | /// written before this member existed. See [`Question`] for why the shape |
| 1954 | /// is declared here and only the answers are per slot. |
| 1955 | pub parts: , |
| 1956 | /// What adds a slot to this question. |
| 1957 | /// |
| 1958 | /// [`Adds::Control`] is the ordinary answer and is a control of the |
| 1959 | /// question's own. [`Adds::Elsewhere`] is a question whose slots arrive |
| 1960 | /// from another control on the same screen, which is MNW's version queue: |
| 1961 | /// the reader picks files and each picked file is a slot, so a control |
| 1962 | /// offering a blank row has nothing to offer. |
| 1963 | pub add: Adds, |
| 1964 | /// What the control that takes one away is called. "Remove". |
| 1965 | pub remove: String, |
| 1966 | |
| 1967 | |
| 1968 | /// One answer to a repeating question. |
| 1969 | /// |
| 1970 | /// The value and the error, and nothing else. Both have the lifecycle |
| 1971 | /// [`Field::value`] and [`Field::error`] have and for the same reasons: what to |
| 1972 | /// re-offer after a refusal, and what whoever validated said about it. |
| 1973 | /// |
| 1974 | /// **This is the per-instance validation the ruling asked for.** |
| 1975 | /// [`Field::error`] is one string on one field, so a repeating question with |
| 1976 | /// only that could say the whole question was wrong and never which answer was. |
| 1977 | /// A message here belongs to this slot, and [`Field::error`] keeps the fact |
| 1978 | /// about the set: "at most eight reminders" is not about any one of them. |
| 1979 | |
| 1980 | |
| 1981 | /// What this slot holds, on [`Field::value`]'s terms. |
| 1982 | /// |
| 1983 | /// The slot's own answer, for a slot that is one question. A slot that is |
| 1984 | /// several leaves this `None` and fills [`parts`](Self::parts) instead: a |
| 1985 | /// slot answers one way or the other, never both, and |
| 1986 | /// [`grouped`](Self::grouped) is which one it is. |
| 1987 | pub value: , |
| 1988 | /// What is wrong with *this* answer. Supplied by whoever validated. |
| 1989 | /// |
| 1990 | /// About the slot as a whole either way. A slot that is several questions |
| 1991 | /// puts what is wrong with *one* of them on that [`Answer::error`], and |
| 1992 | /// keeps this for what is wrong with the slot: the upload that failed, not |
| 1993 | /// the name that was too long. |
| 1994 | pub error: , |
| 1995 | /// What this slot answered for each of [`Repeat::parts`], in their order. |
| 1996 | /// |
| 1997 | /// Empty is a slot that is one question, and also a slot of a grouped |
| 1998 | /// question that has answered nothing yet. [`Repeat::parts`] is what says |
| 1999 | /// which of the two, because that is where the shape lives. |
| 2000 | pub parts: , |
| 2001 | /// What this slot is called, when it is not called by its position. |
| 2002 | /// |
| 2003 | /// `None` is [`Repeat::ordinal`]: "Reminder 1", "Reminder 2", which is |
| 2004 | /// right for slots that differ only by where they are in the list. |
| 2005 | /// |
| 2006 | /// A picked file is the case that is not. A queue's slots are named by |
| 2007 | /// what is in them -- "track.wav" -- and numbering them would put a second, |
| 2008 | /// less useful name where the useful one goes. A slot that names itself |
| 2009 | /// keeps that name when the slots above it are removed, which is the whole |
| 2010 | /// reason it is on the slot rather than derived from its index. |
| 2011 | pub named: , |
| 2012 | /// How far the work on this slot has got, when work happens to it. |
| 2013 | /// |
| 2014 | /// [`Progress::Idle`] is a slot nothing is happening to, which is the |
| 2015 | /// default and every slot of a question that carries no work. |
| 2016 | pub progress: Progress, |
| 2017 | |
| 2018 | |
| 2019 | |
| 2020 | /// A slot holding this value and nothing wrong with it. |
| 2021 | |
| 2022 | |
| 2023 | Self |
| 2024 | value: Some, |
| 2025 | error: None, |
| 2026 | parts: Vecnew, |
| 2027 | named: None, |
| 2028 | progress: Idle, |
| 2029 | |
| 2030 | |
| 2031 | |
| 2032 | /// An empty slot. |
| 2033 | |
| 2034 | pub const |
| 2035 | Self |
| 2036 | value: None, |
| 2037 | error: None, |
| 2038 | parts: Vecnew, |
| 2039 | named: None, |
| 2040 | progress: Idle, |
| 2041 | |
| 2042 | |
| 2043 | |
| 2044 | /// A slot answering [`Repeat::parts`], in their order. |
| 2045 | /// |
| 2046 | /// [`value`](Self::value) stays `None`: the answers are on the parts, and a |
| 2047 | /// slot holding both would be two shapes behind one word, which is what |
| 2048 | /// [`Field::multiple`] was refused for. |
| 2049 | |
| 2050 | |
| 2051 | Self |
| 2052 | value: None, |
| 2053 | error: None, |
| 2054 | parts: parts.into_iter.collect, |
| 2055 | named: None, |
| 2056 | progress: Idle, |
| 2057 | |
| 2058 | |
| 2059 | |
| 2060 | /// What this slot answered for the `at`th of [`Repeat::parts`]. |
| 2061 | /// |
| 2062 | /// A slot that has not answered that far is blank there, which is what a |
| 2063 | /// slot the reader just added is. |
| 2064 | |
| 2065 | |
| 2066 | self.parts.get.cloned.unwrap_or_default |
| 2067 | |
| 2068 | |
| 2069 | /// The same slot, called this rather than called by its position. |
| 2070 | |
| 2071 | |
| 2072 | self.named = Some; |
| 2073 | self |
| 2074 | |
| 2075 | |
| 2076 | /// The same slot, with the work on it standing where this says. |
| 2077 | |
| 2078 | |
| 2079 | self.progress = progress; |
| 2080 | self |
| 2081 | |
| 2082 | |
| 2083 | /// The same slot, with this said about it. |
| 2084 | |
| 2085 | |
| 2086 | self.error = Some; |
| 2087 | self |
| 2088 | |
| 2089 | |
| 2090 | |
| 2091 | /// One of the named questions a slot is made of. |
| 2092 | /// |
| 2093 | /// A slot of a repeating field is ordinarily one answer to one question, and |
| 2094 | /// [`Instance::value`] is it. A picked-file queue is the shape that is not: a |
| 2095 | /// slot is a file, and a file is a name and a size and something that may have |
| 2096 | /// failed on its own. |
| 2097 | /// |
| 2098 | /// This is the *question* half and it lives on [`Repeat::parts`], declared once |
| 2099 | /// for the whole repeating question, for the reason [`Field::kind`] and the |
| 2100 | /// bounds live there: the question is what repeats, and only the answer is per |
| 2101 | /// slot. [`Answer`] is the other half. Declaring it once is also what gives a |
| 2102 | /// renderer the blank slot to offer when the reader adds one, which a shape |
| 2103 | /// living only on the answers could not. |
| 2104 | /// |
| 2105 | /// # The wire |
| 2106 | /// |
| 2107 | /// `name[0].size`, written by [`Repeat::part_at`] and read back by |
| 2108 | /// [`Repeat::part_of`] and [`Params::repeated_part`]. Beside `name[0]` rather |
| 2109 | /// than instead of it, which is the growth [`Repeat`]'s header sketched and |
| 2110 | /// left open: a slot that is one question still submits under the bare indexed |
| 2111 | /// name, so nothing written before this existed changed shape. |
| 2112 | /// |
| 2113 | /// [`Params::repeated_part`]: crate::Params::repeated_part |
| 2114 | |
| 2115 | |
| 2116 | /// What this question is called within the slot: the `size` of |
| 2117 | /// `name[0].size`. |
| 2118 | /// |
| 2119 | /// A suffix rather than a whole name. The repeating question's own name and |
| 2120 | /// the slot index are the caller's, and [`Repeat::part_at`] is the only |
| 2121 | /// place the three are joined. |
| 2122 | pub name: String, |
| 2123 | /// What it is called on screen. |
| 2124 | /// |
| 2125 | /// Not numbered. [`Repeat::ordinal`] numbers the slot, and numbering the |
| 2126 | /// questions inside it as well would read as "Name 2" for the second file. |
| 2127 | pub label: String, |
| 2128 | |
| 2129 | |
| 2130 | |
| 2131 | /// One named question of a slot. |
| 2132 | |
| 2133 | |
| 2134 | Self |
| 2135 | name: name.into, |
| 2136 | label: label.into, |
| 2137 | |
| 2138 | |
| 2139 | |
| 2140 | |
| 2141 | /// What one slot answered for one of [`Repeat::parts`]. |
| 2142 | /// |
| 2143 | /// The answer half of [`Question`], positional against it: the `n`th answer is |
| 2144 | /// to the `n`th question. Fewer answers than questions is a slot that has not |
| 2145 | /// answered the rest, which is what a slot the reader just added is, and every |
| 2146 | /// reader here tolerates it. |
| 2147 | |
| 2148 | |
| 2149 | /// What this question of this slot holds, on [`Field::value`]'s terms. |
| 2150 | pub value: , |
| 2151 | /// What is wrong with *this* question of this slot. |
| 2152 | /// |
| 2153 | /// The per-slot error one level finer. [`Instance::error`] stays what is |
| 2154 | /// wrong with the slot as a whole. |
| 2155 | pub error: , |
| 2156 | |
| 2157 | |
| 2158 | |
| 2159 | /// An answer holding this. |
| 2160 | |
| 2161 | |
| 2162 | Self |
| 2163 | value: Some, |
| 2164 | error: None, |
| 2165 | |
| 2166 | |
| 2167 | |
| 2168 | /// An unanswered question of a slot. |
| 2169 | |
| 2170 | pub const |
| 2171 | Self |
| 2172 | value: None, |
| 2173 | error: None, |
| 2174 | |
| 2175 | |
| 2176 | |
| 2177 | /// The same, with this said about it. |
| 2178 | |
| 2179 | |
| 2180 | self.error = Some; |
| 2181 | self |
| 2182 | |
| 2183 | |
| 2184 | |
| 2185 | /// What adds a slot to a repeating question. |
| 2186 | /// |
| 2187 | /// A repeating question used to assume one answer: the reader presses a |
| 2188 | /// control of the question's own and a blank slot appears. That is still the |
| 2189 | /// ordinary case and still the default. |
| 2190 | /// |
| 2191 | /// MNW's version-upload queue is the case it could not say. Its slots are |
| 2192 | /// picked files, made by the upload field above the table, and a blank row is |
| 2193 | /// not something a reader can fill: there is no way to type a file. A question |
| 2194 | /// there wants no add control at all, and no renderer may invent one. |
| 2195 | /// |
| 2196 | /// # Not [`Repeating::add`] |
| 2197 | /// |
| 2198 | /// That is an [`Act`], because a repeating *group*'s slots are the app's and |
| 2199 | /// adding one is a route. Neither of these is: a repeating field's slots live |
| 2200 | /// in the renderer's own view until they submit, so what makes one is either |
| 2201 | /// this renderer's own control or another control on the same screen. No |
| 2202 | /// route either way. |
| 2203 | |
| 2204 | |
| 2205 | /// A control of the question's own, called this. "Add reminder". |
| 2206 | Control, |
| 2207 | /// Another control on the screen, named by whatever the renderer addresses |
| 2208 | /// controls by, which in a form is the field's name. |
| 2209 | /// |
| 2210 | /// The question draws no add control of its own. What the named control |
| 2211 | /// does with the slots it makes is that control's business and this says |
| 2212 | /// nothing about it: a renderer that cannot reach the named control draws |
| 2213 | /// the slots it was given and no way to make more, which is the honest |
| 2214 | /// answer for a terminal asked to display a file queue. |
| 2215 | Elsewhere, |
| 2216 | |
| 2217 | |
| 2218 | |
| 2219 | |
| 2220 | SelfControl |
| 2221 | |
| 2222 | |
| 2223 | |
| 2224 | |
| 2225 | /// What the control is called, when the question offers one. |
| 2226 | /// |
| 2227 | /// `None` is a question whose slots come from elsewhere, and is every |
| 2228 | /// renderer's signal to draw no add control. |
| 2229 | |
| 2230 | |
| 2231 | match self |
| 2232 | SelfControl => Some, |
| 2233 | SelfElsewhere => None, |
| 2234 | |
| 2235 | |
| 2236 | |
| 2237 | /// The control the slots come from, when they come from another. |
| 2238 | |
| 2239 | |
| 2240 | match self |
| 2241 | SelfControl => None, |
| 2242 | SelfElsewhere => Some, |
| 2243 | |
| 2244 | |
| 2245 | |
| 2246 | |
| 2247 | /// How far the work on one slot has got. |
| 2248 | /// |
| 2249 | /// [`Instance::error`] says what is wrong with a slot and cannot say *when*: |
| 2250 | /// an answer that failed to upload and an answer nobody has started uploading |
| 2251 | /// are both "no value yet" to a renderer reading the slot alone. A queue of |
| 2252 | /// picked files is the screen that needs the difference, and needs it per slot |
| 2253 | /// rather than per question, because one file failing says nothing about the |
| 2254 | /// other six. |
| 2255 | /// |
| 2256 | /// Not [`layout::Readiness`], which is a region's word for whether its |
| 2257 | /// *content* arrived and carries an `Empty` that means nothing about a slot. |
| 2258 | /// Two meanings behind one word is what [`Field::multiple`] was refused for. |
| 2259 | |
| 2260 | |
| 2261 | /// Nothing is happening to this slot. |
| 2262 | /// |
| 2263 | /// The default, and every slot of every repeating question that carries no |
| 2264 | /// work of its own. |
| 2265 | |
| 2266 | Idle, |
| 2267 | /// Work on this slot began and has not finished. |
| 2268 | /// |
| 2269 | /// The [`Meter`] is how far, for a host that can say. `None` is work whose |
| 2270 | /// extent nobody can state yet, which is a file that has been handed over |
| 2271 | /// and has reported no bytes. |
| 2272 | Working, |
| 2273 | /// The work finished and this slot holds what it produced. |
| 2274 | Done, |
| 2275 | /// The work did not finish. |
| 2276 | /// |
| 2277 | /// What went wrong is [`Instance::error`]. Kept apart from it so that a |
| 2278 | /// slot may carry a message without being a failure, which is an ordinary |
| 2279 | /// validation refusal on a queue nobody has submitted yet. |
| 2280 | Failed, |
| 2281 | |
| 2282 | |
| 2283 | |
| 2284 | /// Whether this slot is waiting on work that has not finished. |
| 2285 | /// |
| 2286 | /// Asked by every renderer before it draws a slot's remove control: a slot |
| 2287 | /// mid-flight is one the reader may not pull out from under the work. |
| 2288 | |
| 2289 | pub const |
| 2290 | matches! |
| 2291 | |
| 2292 | |
| 2293 | /// Whether the work on this slot ended badly. |
| 2294 | |
| 2295 | pub const |
| 2296 | matches! |
| 2297 | |
| 2298 | |
| 2299 | |
| 2300 | |
| 2301 | /// What the control that adds a slot is called when nothing else is said. |
| 2302 | pub const ADD: &'static str = "Add"; |
| 2303 | /// What the control that removes one is called when nothing else is said. |
| 2304 | pub const REMOVE: &'static str = "Remove"; |
| 2305 | |
| 2306 | /// A question nobody has answered yet, with no floor and no ceiling. |
| 2307 | |
| 2308 | |
| 2309 | Self |
| 2310 | instances: Vecnew, |
| 2311 | least: 0, |
| 2312 | most: None, |
| 2313 | parts: Vecnew, |
| 2314 | add: default, |
| 2315 | remove: SelfREMOVE.to_owned, |
| 2316 | |
| 2317 | |
| 2318 | |
| 2319 | /// The named questions each slot is made of. |
| 2320 | /// |
| 2321 | /// A slot answers these rather than answering once itself, and |
| 2322 | /// [`Instance::grouped`] is how one supplies them. |
| 2323 | |
| 2324 | |
| 2325 | self.parts = parts.into_iter.collect; |
| 2326 | self |
| 2327 | |
| 2328 | |
| 2329 | /// The slots this question already stands in, supplied whole. |
| 2330 | /// |
| 2331 | /// [`answered`](Self::answered) for a grouped question, where a slot is an |
| 2332 | /// [`Instance::grouped`] rather than a single value, and for any slot that |
| 2333 | /// carries a [`Progress`] the plain values cannot express. |
| 2334 | |
| 2335 | |
| 2336 | self.instances = instances.into_iter.collect; |
| 2337 | self |
| 2338 | |
| 2339 | |
| 2340 | /// Whether a slot of this question is several questions rather than one. |
| 2341 | |
| 2342 | |
| 2343 | !self.parts.is_empty |
| 2344 | |
| 2345 | |
| 2346 | /// A question already answered these many times. |
| 2347 | /// |
| 2348 | /// What a form being offered for editing carries: one slot per value the |
| 2349 | /// record holds. |
| 2350 | |
| 2351 | |
| 2352 | Self |
| 2353 | instances: values.into_iter.map.collect, |
| 2354 | ..Selfnew |
| 2355 | |
| 2356 | |
| 2357 | |
| 2358 | /// Leave at least this many slots standing. |
| 2359 | |
| 2360 | pub const |
| 2361 | self.least = least; |
| 2362 | self |
| 2363 | |
| 2364 | |
| 2365 | /// Take at most this many answers. |
| 2366 | |
| 2367 | pub const |
| 2368 | self.most = Some; |
| 2369 | self |
| 2370 | |
| 2371 | |
| 2372 | /// What the control that adds a slot is called. |
| 2373 | |
| 2374 | |
| 2375 | self.add = Control; |
| 2376 | self |
| 2377 | |
| 2378 | |
| 2379 | /// The slots come from another control on the screen, named here. |
| 2380 | /// |
| 2381 | /// The question offers no add control of its own. See [`Adds::Elsewhere`]. |
| 2382 | |
| 2383 | |
| 2384 | self.add = Elsewhere; |
| 2385 | self |
| 2386 | |
| 2387 | |
| 2388 | /// What the control that takes a slot away is called. |
| 2389 | |
| 2390 | |
| 2391 | self.remove = label.into; |
| 2392 | self |
| 2393 | |
| 2394 | |
| 2395 | /// Say what is wrong with one answer, leaving the rest alone. |
| 2396 | /// |
| 2397 | /// Grows the list to reach it, because a refusal naming the fourth answer |
| 2398 | /// of a form that came back with three is a description bug worth seeing on |
| 2399 | /// the screen rather than a message silently dropped. |
| 2400 | |
| 2401 | |
| 2402 | if self.instances.len <= at |
| 2403 | self.instances.resize; |
| 2404 | |
| 2405 | self.instances.error = Some; |
| 2406 | self |
| 2407 | |
| 2408 | |
| 2409 | /// The name the `at`th slot submits under: `name[at]`. |
| 2410 | /// |
| 2411 | /// The one place the wire naming is spelled. See the type's header for why |
| 2412 | /// it is an index rather than N values under one name. |
| 2413 | |
| 2414 | |
| 2415 | format! |
| 2416 | |
| 2417 | |
| 2418 | /// The name the `part` of the `at`th slot submits under: `name[at].part`. |
| 2419 | /// |
| 2420 | /// [`at`](Self::at) with a part on the end, and the only place that join is |
| 2421 | /// spelled, for [`at`](Self::at)'s reason: the three renderers and whatever |
| 2422 | /// reads the submission back have to agree, and [`part_of`](Self::part_of) |
| 2423 | /// is the reader. |
| 2424 | |
| 2425 | |
| 2426 | format! |
| 2427 | |
| 2428 | |
| 2429 | /// The question, the slot and the part a wire name belongs to, if it is |
| 2430 | /// one. |
| 2431 | /// |
| 2432 | /// [`part_at`](Self::part_at) read backwards. `None` for a bare indexed |
| 2433 | /// name, which [`instance_of`](Self::instance_of) is the reader for, and |
| 2434 | /// `None` for every ordinary field name: the two never both answer, so a |
| 2435 | /// host may ask either of any name it holds. |
| 2436 | |
| 2437 | |
| 2438 | let = wire.split_once?; |
| 2439 | if part.is_empty || part.contains |
| 2440 | return None; |
| 2441 | |
| 2442 | let = Selfinstance_of?; |
| 2443 | Some |
| 2444 | |
| 2445 | |
| 2446 | /// The question and the slot a wire name belongs to, if it is one. |
| 2447 | /// |
| 2448 | /// [`at`](Self::at) read backwards, for a host holding a name and asking |
| 2449 | /// what it is. `None` for every ordinary field name, which is what makes it |
| 2450 | /// safe to ask of any of them. |
| 2451 | |
| 2452 | |
| 2453 | let = wire.split_once?; |
| 2454 | let index = rest.strip_suffix?; |
| 2455 | // Refused rather than parsed loosely: `name[+1]` and `name[ 1]` both |
| 2456 | // parse as 1 through `str::parse` on some inputs a caller would not |
| 2457 | // expect, and a name this did not write is not a slot. |
| 2458 | if index.is_empty || !index.bytes.all |
| 2459 | return None; |
| 2460 | |
| 2461 | Some |
| 2462 | |
| 2463 | |
| 2464 | /// What the `at`th slot is called on screen: "Reminder 2". |
| 2465 | /// |
| 2466 | /// Counted from one, because it is read by a person. Spelled here so the |
| 2467 | /// three renderers cannot number the same slot differently, which is the |
| 2468 | /// same reason [`at`](Self::at) is here: an error reported against the |
| 2469 | /// third answer has to name the third box on every host. |
| 2470 | |
| 2471 | |
| 2472 | format! |
| 2473 | |
| 2474 | |
| 2475 | /// Whether another slot may be added when this many are standing. |
| 2476 | |
| 2477 | |
| 2478 | self.most.is_none_or |
| 2479 | |
| 2480 | |
| 2481 | /// Whether a slot may be taken away when this many are standing. |
| 2482 | |
| 2483 | pub const |
| 2484 | standing > self.least |
| 2485 | |
| 2486 | |
| 2487 | /// How many slots stand before the reader has touched anything. |
| 2488 | /// |
| 2489 | /// The described count, floored at [`least`](Self::least): a question that |
| 2490 | /// must be answered twice opens with two boxes rather than with none and a |
| 2491 | /// refusal on submit. |
| 2492 | |
| 2493 | |
| 2494 | self.instances.len.max |
| 2495 | |
| 2496 | |
| 2497 | /// What the `at`th slot holds, if the description offered anything. |
| 2498 | |
| 2499 | |
| 2500 | self.instances.get?.value.as_deref |
| 2501 | |
| 2502 | |
| 2503 | /// What is wrong with the `at`th answer, if anything is. |
| 2504 | |
| 2505 | |
| 2506 | self.instances.get?.error.as_deref |
| 2507 | |
| 2508 | |
| 2509 | |
| 2510 | /// One field of a form, owned. |
| 2511 | /// |
| 2512 | /// The borrowed original is [`layout::Field`], and everything it says about |
| 2513 | /// what a field carries applies unchanged, with one addition that does not |
| 2514 | /// travel down to it: [`value`](Self::value). |
| 2515 | /// |
| 2516 | /// # Why the value lives here and not in `makeover-layout` |
| 2517 | /// |
| 2518 | /// [`layout::Field`] refuses to carry the current value, and that refusal is |
| 2519 | /// right: an immediate-mode renderer writes through a `&mut String` the app |
| 2520 | /// owns, and a terminal keeps an edit buffer, so a description carrying a live |
| 2521 | /// value would need a way to write it back and would then be a form model. |
| 2522 | /// |
| 2523 | /// What is carried here is not a live value. It is what to re-offer after a |
| 2524 | /// submission was refused, and it has [`error`](Self::error)'s lifecycle rather |
| 2525 | /// than a live value's: per-submission, one way, supplied by whoever validated, |
| 2526 | /// gone on the next request. `error` already sits in this struct on exactly |
| 2527 | /// those terms. |
| 2528 | /// |
| 2529 | /// The reason it is this crate's field and not the vocabulary's is that only a |
| 2530 | /// stateless request and response destroys the value. In egui and in a terminal |
| 2531 | /// the buffer never went anywhere, so nothing is lost and there is nothing to |
| 2532 | /// re-offer. This is the layer where the loss happens, so this is the layer that |
| 2533 | /// repairs it. |
| 2534 | /// |
| 2535 | /// # A field's described state is its value, and the caret is the renderer's |
| 2536 | /// |
| 2537 | /// Nothing here carries a caret position, and nothing in [`layout::FieldKind`] |
| 2538 | /// does either. A description names the field and, where it has one, its |
| 2539 | /// completion source. Where the caret sits is how a renderer decides what to |
| 2540 | /// offer from that source. |
| 2541 | /// |
| 2542 | /// The question came from goingson's `search.js`, whose completion list depends |
| 2543 | /// on which token the caret is inside rather than on the value: it reads |
| 2544 | /// `selectionStart`, listens for caret moves that change nothing else, and |
| 2545 | /// writes the caret back when a suggestion is applied. That is a real |
| 2546 | /// dependency, and it still does not belong here. A caret is where the user is |
| 2547 | /// pointing inside a control, the same class of fact as a scroll offset and a |
| 2548 | /// focus position, and this stack already puts those in the renderer's view |
| 2549 | /// rather than in the description (`quasi-tui`'s `View`). |
| 2550 | /// |
| 2551 | /// Growing this struct to (value, caret) was rejected: it is the most-consumed |
| 2552 | /// member in the vocabulary, every renderer would owe it an answer, and a |
| 2553 | /// terminal's answer would be a second cursor concept beside the one the runtime |
| 2554 | /// already holds. The measured demand was one file. |
| 2555 | /// |
| 2556 | /// Reversible if a second consumer appears that needs the caret described rather |
| 2557 | /// than held, such as a completion that has to survive a fragment swap. That is |
| 2558 | /// a member here and a cascade, the same shape as every other addition. |
| 2559 | /// |
| 2560 | /// No `Hash`, for the reason [`Tag`] has none: it can hold an [`Action`], which |
| 2561 | /// holds [`Params`], which is a `Vec`. |
| 2562 | |
| 2563 | |
| 2564 | /// What kind of value it takes. |
| 2565 | pub kind: FieldKind, |
| 2566 | /// The name the value is submitted under, and the name the handler reads |
| 2567 | /// back out of [`Params`]. |
| 2568 | /// |
| 2569 | /// The *lower* end's name for a [`layout::FieldKind::Interval`], whose upper |
| 2570 | /// end is [`upper_name`](Self::upper_name). |
| 2571 | pub name: String, |
| 2572 | /// The name a [`layout::FieldKind::Interval`]'s upper end is submitted |
| 2573 | /// under. |
| 2574 | /// |
| 2575 | /// The borrowed original is [`layout::Field::upper_name`], and everything it |
| 2576 | /// says applies: stated rather than derived, because the two measured sites |
| 2577 | /// disagree about affix order, and which member a name sits in is what says |
| 2578 | /// which end it is. |
| 2579 | /// |
| 2580 | /// `None` for every other kind. [`Field::interval`] is what makes an |
| 2581 | /// interval without one unsayable. |
| 2582 | pub upper_name: , |
| 2583 | /// What the user is asked for. |
| 2584 | pub label: String, |
| 2585 | /// Standing help. |
| 2586 | pub hint: , |
| 2587 | /// What is currently wrong with the value. Supplied by whoever validated; |
| 2588 | /// nothing here decides that a value is wrong. |
| 2589 | pub error: , |
| 2590 | /// A consequence of the answer the user has given, carrying its own tone. |
| 2591 | /// |
| 2592 | /// The third message channel, between [`hint`](Self::hint) and |
| 2593 | /// [`error`](Self::error) and overlapping neither: the value is acceptable |
| 2594 | /// and choosing it costs something worth saying. It does not make the field |
| 2595 | /// [`invalid`](Self::invalid). |
| 2596 | /// |
| 2597 | /// Precedence for a renderer with room for one line, decided in |
| 2598 | /// [`layout::Field::note`]: error, then note, then hint. |
| 2599 | pub note: , |
| 2600 | /// Ghost text shown while the field is empty. |
| 2601 | pub placeholder: , |
| 2602 | /// The options offered, in order. Empty for kinds that offer none. |
| 2603 | pub options: , |
| 2604 | /// The themes offered, in the order they are offered. |
| 2605 | /// |
| 2606 | /// The borrowed original is [`layout::Field::themes`], and everything it |
| 2607 | /// says applies. Empty for every kind |
| 2608 | /// [`layout::FieldKind::offers_themes`] rejects, and a real answer for the |
| 2609 | /// one that accepts it. |
| 2610 | /// |
| 2611 | /// **The order is the grouping**, and nothing here sorts. The order arrives |
| 2612 | /// from whoever measured the tiers — `makeover::theme_options` is what |
| 2613 | /// produces it — and re-sorting at this layer would be deciding a question |
| 2614 | /// it cannot see the inputs to. |
| 2615 | pub themes: , |
| 2616 | /// The entry that follows the ambient mode instead of naming a theme. |
| 2617 | /// |
| 2618 | /// The borrowed original is [`layout::Field::follows`]. A [`Choice`] rather |
| 2619 | /// than a bare label because the value belongs to the app's own store, and |
| 2620 | /// `None` is a real answer for a host with no ambient mode to follow. |
| 2621 | pub follows: , |
| 2622 | /// What a file field takes. Empty for kinds that take no files, and also a |
| 2623 | /// real answer for one that does: a field listing nothing takes any file. |
| 2624 | /// |
| 2625 | /// The borrowed original is [`layout::Field::accept`]. It filters the picker |
| 2626 | /// and it says which disclosure the field earns — a preview, a duration — |
| 2627 | /// which is why it is a list of [`Accepted`] rather than the comma-joined |
| 2628 | /// string a template holds. |
| 2629 | pub accept: , |
| 2630 | /// Whether more than one file may be picked at once. |
| 2631 | /// |
| 2632 | /// The borrowed original is [`layout::Field::multiple`]. Read only by a kind |
| 2633 | /// [`layout::FieldKind::takes_files`] accepts. |
| 2634 | pub multiple: bool, |
| 2635 | /// Whether the form refuses to submit without it. |
| 2636 | pub required: bool, |
| 2637 | /// The longest the value may be, in characters. |
| 2638 | /// |
| 2639 | /// The borrowed original's [`layout::Field::max_length`], and everything it |
| 2640 | /// says applies: the description carries the rule, the renderer emits its |
| 2641 | /// host's idiom, and deciding a value is wrong stays with whoever validated. |
| 2642 | pub max_length: , |
| 2643 | /// The lowest value accepted, written the way the host writes one. |
| 2644 | pub min: , |
| 2645 | /// The highest value accepted. See [`min`](Self::min). |
| 2646 | pub max: , |
| 2647 | /// The granularity a *typed* value moves in, written the way the host writes |
| 2648 | /// one. |
| 2649 | /// |
| 2650 | /// The borrowed original is [`layout::Field::step`]. Absent means the |
| 2651 | /// host's own granularity, which is a real answer rather than a missing |
| 2652 | /// one. A [`layout::FieldKind::Range`] keeps its own on |
| 2653 | /// [`curve`](Self::curve) instead, as of makeover-layout 0.32.0. |
| 2654 | pub step: , |
| 2655 | /// How a slider's position becomes its value, and how finely it moves. |
| 2656 | /// |
| 2657 | /// The borrowed original is [`layout::Field::curve`]. A |
| 2658 | /// [`layout::FieldKind::Range`] reads its granularity here; every other |
| 2659 | /// kind reads [`step`](Self::step). See [`Curve`]. |
| 2660 | pub curve: Curve, |
| 2661 | /// What the number is measured in: `s`, `ms`, `dB`, `GiB`. |
| 2662 | /// |
| 2663 | /// The borrowed original is [`layout::Field::unit`], and everything it says |
| 2664 | /// applies. A fact about the value rather than part of the question's name, |
| 2665 | /// which is the distinction the member exists for: the two come apart the |
| 2666 | /// moment a handler reads a field back instead of a renderer drawing it. |
| 2667 | /// |
| 2668 | /// Read only by a kind [`layout::FieldKind::measurable`] accepts. The symbol |
| 2669 | /// alone, no brackets and no leading space; the spacing is the renderer's. |
| 2670 | pub unit: , |
| 2671 | /// Whether the field lives behind a "more options" disclosure. |
| 2672 | pub extended: bool, |
| 2673 | /// Whether this local wall-clock value is submitted as an absolute instant. |
| 2674 | /// |
| 2675 | /// The borrowed original is [`layout::Field::as_instant`], and everything it |
| 2676 | /// says applies. A [`layout::FieldKind::DateTime`] asks for a time the way a |
| 2677 | /// person says one, which names a different moment in each zone; this says |
| 2678 | /// the description wants the renderer to convert it, because the renderer is |
| 2679 | /// the only party that knows what its host's clock and zone are. |
| 2680 | /// |
| 2681 | /// No wire contract moves when a site adopts it. The route was already |
| 2682 | /// receiving an instant; what changes is who computed it. |
| 2683 | pub as_instant: bool, |
| 2684 | /// How much of its row the control asks for. |
| 2685 | /// |
| 2686 | /// Fill is determined at the description stage. [`Column::width`] has said |
| 2687 | /// this about a table cell since the beginning and [`layout::Share`] says |
| 2688 | /// it about a region, so the vocabulary already accepted that an app has |
| 2689 | /// an opinion about which of several things expands. A leaf control having |
| 2690 | /// no way to say it was an inconsistency in where the line sat rather than |
| 2691 | /// a principle being upheld, and this is the correction. |
| 2692 | /// |
| 2693 | /// What it is not is a measurement. [`layout::Width`] is an intent — |
| 2694 | /// content-sized, fixed, or take the rest — and the actual floor stays with |
| 2695 | /// `makeover-geometry`, which is the same division `Column` makes. A field |
| 2696 | /// that carried pixels would be the thing this replaces: audiofiles' |
| 2697 | /// toolbar kept a measured `trailing_width` in renderer memory, corrected |
| 2698 | /// it a frame late, and needed two constants to survive the first frame, |
| 2699 | /// all to say [`Fill`](layout::Width::Fill). |
| 2700 | /// |
| 2701 | /// [`layout::Share`]: crate::layout::Share |
| 2702 | pub width: Width, |
| 2703 | /// What to put back in the box: what was submitted, when a submission was |
| 2704 | /// refused and the form is being offered again. |
| 2705 | /// |
| 2706 | /// `None` on a first showing, which is every form that is not answering a |
| 2707 | /// refusal. A checkbox is here by presence, the way HTML submits one: a |
| 2708 | /// value means ticked and `None` means not. |
| 2709 | /// |
| 2710 | /// A [`layout::FieldKind::Secret`] never gets one. [`Field::value`] refuses |
| 2711 | /// to set it and every renderer refuses to emit it, so the guarantee does |
| 2712 | /// not rest on either alone. |
| 2713 | pub value: , |
| 2714 | /// What to put back in the *upper* box of a |
| 2715 | /// [`layout::FieldKind::Interval`], on the same terms as |
| 2716 | /// [`value`](Self::value). |
| 2717 | /// |
| 2718 | /// A second value rather than a separator convention inside the first. An |
| 2719 | /// interval submits two names, so a refusal has two values to hand back, and |
| 2720 | /// joining them into one string would make this layer own a delimiter that |
| 2721 | /// any value could contain. |
| 2722 | /// |
| 2723 | /// Either end may be absent while the other stands, which is what an |
| 2724 | /// open-ended interval is: "over 120 BPM" is a lower end and no upper one, |
| 2725 | /// and it is a real answer rather than a half-filled form. |
| 2726 | /// |
| 2727 | /// `None` for every other kind, and never set for a |
| 2728 | /// [`layout::FieldKind::Secret`] for [`value`](Self::value)'s reason. |
| 2729 | pub upper_value: , |
| 2730 | /// What setting this calls, for a control that writes as it is set rather |
| 2731 | /// than waiting for a submit. |
| 2732 | /// |
| 2733 | /// A field inside a [`Node::Form`] submits with the form and needs nothing |
| 2734 | /// here. A settings toggle is the other kind: there is no submit, and |
| 2735 | /// setting the control *is* the write. goingson had 13 of these and |
| 2736 | /// reached them through `dispatch.js`, 109 lines of its own event |
| 2737 | /// plumbing, because nothing in the description could say it. No version |
| 2738 | /// of spinning up an app quickly has each app hand-rolling a dispatcher. |
| 2739 | /// |
| 2740 | /// The route receives the value under this field's [`name`](Self::name), |
| 2741 | /// which is the same name a submit would have sent it under. Nothing else |
| 2742 | /// changes about the field. |
| 2743 | /// |
| 2744 | /// # What it is for |
| 2745 | /// |
| 2746 | /// A value that is written as it is set, where no commit point is wanted: a |
| 2747 | /// draft field that autosaves, a setting, a per-field select on a detail |
| 2748 | /// view. The reader alters one control, and that alteration is the whole |
| 2749 | /// interaction. |
| 2750 | /// |
| 2751 | /// # What it is not for |
| 2752 | /// |
| 2753 | /// **Applying a value to a selection.** That wants a commit affordance, per |
| 2754 | /// wiki `explicit-commit-affordance`: the reader ticks rows, chooses a |
| 2755 | /// value, and the write lands on every row ticked. That is a large enough |
| 2756 | /// act to deserve a control that says so before it happens. |
| 2757 | /// |
| 2758 | /// **Anything that does not write.** A filter, a sort, a re-ask: a control |
| 2759 | /// whose action fetches a different view of data it leaves alone. Saying it |
| 2760 | /// here states something false about the control in every renderer that |
| 2761 | /// reads the description, and the falsehood is invisible because the |
| 2762 | /// request still goes out and the screen still updates. |
| 2763 | /// |
| 2764 | /// # Why the name is enough |
| 2765 | /// |
| 2766 | /// This is the only member on [`Field`] carrying an [`Action`] of its own, |
| 2767 | /// so a field without it is written by its form's submit and a field with |
| 2768 | /// it writes by itself. The presence of the member is the statement. There |
| 2769 | /// is nothing to add and no second member saying a field does not write. |
| 2770 | /// A [`Consult`] carries an action too and is not a counterexample: it |
| 2771 | /// names a question about the value, and the question is what its type |
| 2772 | /// says. |
| 2773 | /// |
| 2774 | /// # When it fires: the change is *complete*, not on the way to it |
| 2775 | /// |
| 2776 | /// A value the reader builds up, typed into or dragged across, writes |
| 2777 | /// once, when they are finished with the control: a webview on the |
| 2778 | /// browser's `change` event, a terminal when the caret walks off the box, |
| 2779 | /// egui on `lost_focus` or `drag_stopped`. A value chosen in one go (a |
| 2780 | /// select, a radio, a file) was complete the moment it changed and writes |
| 2781 | /// then. |
| 2782 | /// |
| 2783 | /// **This was already what a webview did and what the other two did not.** |
| 2784 | /// `quasi-webview` emits `hx-trigger="change"`, so it has meant this since |
| 2785 | /// the member existed; `quasi-immediate` fired on every frame the buffer |
| 2786 | /// differed and `quasi-tui` on every keystroke. So a search box was one |
| 2787 | /// request per letter on two hosts and one per search on the third, and |
| 2788 | /// audiofiles' bounded `row_height` posted a row height of 3 on the way to |
| 2789 | /// 30, outside the bounds the field's own hint states. |
| 2790 | /// |
| 2791 | /// Leaving a control nobody altered writes nothing, which is the other half |
| 2792 | /// of what `change` promises: walking through a form must not write every |
| 2793 | /// box it passes. |
| 2794 | /// |
| 2795 | /// # A question asked while typing is [`consults`](Self::consults) |
| 2796 | /// |
| 2797 | /// Nothing above costs live search anything, and that is what makes the |
| 2798 | /// rule coherent rather than a restriction. A box that asks a route about |
| 2799 | /// what is being typed carries a [`Consult`], which has its own |
| 2800 | /// [`after`](Consult::after): a wait the description states rather than a |
| 2801 | /// number each renderer picks. Writes complete; questions debounce. |
| 2802 | pub writes: , |
| 2803 | /// What this asks while the user is still typing, and how long it waits. |
| 2804 | /// |
| 2805 | /// See [`Consult`]. Distinct from [`writes`](Self::writes) in the two |
| 2806 | /// ways that matter: it fires while the value is still being written rather |
| 2807 | /// than once it settles, and what comes back is an answer about the value |
| 2808 | /// rather than the result of writing it. |
| 2809 | /// |
| 2810 | /// # Several, because one box can raise more than one question |
| 2811 | /// |
| 2812 | /// `N8` on MNW's discover screen: `#search-input` carries `hx- |
| 2813 | /// get="/discover/results"` with one wait, and the suggestion list beside |
| 2814 | /// it is a hand-written `fetch('/discover/suggestions?q=…')` with another. |
| 2815 | /// Two questions about one value, asked at two rates, and only one of them |
| 2816 | /// was sayable — so the other stayed as JS. |
| 2817 | /// |
| 2818 | /// Order is the description's and each renderer keeps it, though nothing |
| 2819 | /// depends on it: the answers land where each |
| 2820 | /// [`Action::replacing`] says, and two consults pointing at one place is a |
| 2821 | /// description arguing with itself rather than an ordering question. |
| 2822 | /// |
| 2823 | /// # "Typing" is the common case, not the rule |
| 2824 | /// |
| 2825 | /// A select, a radio, a checkbox and a slider all consult, and the |
| 2826 | /// majority of the measured sites are selects: a folder picker that re- |
| 2827 | /// reads a list of mail asks a route about a value and stores nothing, |
| 2828 | /// which is this member entire. It read as typing-only because the first |
| 2829 | /// four sites were wizard boxes and the webview hung the question on |
| 2830 | /// `keyup`, which a select never raises. |
| 2831 | /// |
| 2832 | /// Each renderer raises it in its host's idiom, the way it already does for |
| 2833 | /// [`Slot::consults`], and the wait stays the description's: |
| 2834 | /// [`Consult::after`] of zero is a real number for a control chosen in one |
| 2835 | /// gesture rather than a meaningless one. |
| 2836 | pub consults: , |
| 2837 | /// The question whose answer is this field's own list of candidates. |
| 2838 | /// |
| 2839 | /// The field owns the list. MNW's discover screen spends ~120 lines of |
| 2840 | /// `page-discover.js` on exactly that wiring, none of which is about |
| 2841 | /// suggestions. |
| 2842 | /// |
| 2843 | /// # Why a [`Consult`] and not a second kind of member |
| 2844 | /// |
| 2845 | /// A suggestion source is a route asked as the value is typed, with a wait |
| 2846 | /// and a floor, which is [`Consult`] entire. What is added here is not a |
| 2847 | /// second mechanism but an owner: the answer to *this* question is a list |
| 2848 | /// of candidates for *this* value, and every renderer therefore knows where |
| 2849 | /// to draw it, what it is called, and what picking one does. |
| 2850 | /// |
| 2851 | /// # What comes back, and this one is described |
| 2852 | /// |
| 2853 | /// [`Outcome::Suggestions`](crate::Outcome::Suggestions), a list of |
| 2854 | /// [`Candidate`] — its own type rather than the [`Choice`] that |
| 2855 | /// [`options`](Self::options) carries. Both |
| 2856 | /// submit one string and read as another; a candidate also says what tells |
| 2857 | /// it apart from a row that reads alike, and what picking it does. |
| 2858 | /// Deliberately unlike |
| 2859 | /// [`consults`](Self::consults), where the answer is undescribed and the |
| 2860 | /// renderer picks the wire format: that works for a verdict landing in a |
| 2861 | /// region a description already named, and it cannot work here. A list |
| 2862 | /// nobody described is a list a terminal cannot draw, and one route |
| 2863 | /// answering three wire formats is the per-host branching this stack |
| 2864 | /// exists to end. |
| 2865 | /// |
| 2866 | /// # Picking is local by default |
| 2867 | /// |
| 2868 | /// [`Destination::Local`] says a suggestion's pick action sets the field. |
| 2869 | /// Ownership is what makes that sayable without an action at all: the list |
| 2870 | /// belongs to this field, so picking an entry writes its |
| 2871 | /// [`Candidate::value`] into this field, and no renderer has to be told |
| 2872 | /// which box to write to. Moving the highlight is local for the same |
| 2873 | /// reason. |
| 2874 | /// |
| 2875 | /// By default and not by definition after the two sites this member was |
| 2876 | /// designed from were held against it and neither picked locally. A |
| 2877 | /// candidate carrying |
| 2878 | /// [`picks`](Candidate::picks) has that action performed instead. Local |
| 2879 | /// stays the default, so every site that exists today is unchanged and |
| 2880 | /// nothing that already works has to say anything new. |
| 2881 | /// |
| 2882 | /// # Addressed by the field's name |
| 2883 | /// |
| 2884 | /// No id is authored anywhere. The field already has a |
| 2885 | /// [`name`](Self::name) — what a submit sends the value under, what a |
| 2886 | /// [`Consult`] sends it under — and that is what the answer names. A |
| 2887 | /// renderer that needs a document id derives one from it, which is the |
| 2888 | /// difference between a list a field owns and two elements a description |
| 2889 | /// has to keep pointing at each other. |
| 2890 | /// |
| 2891 | /// Beside [`consults`](Self::consults) rather than inside it: MNW's |
| 2892 | /// discover box asks two questions about one value, and only one of them is |
| 2893 | /// its suggestions. The other re-reads the results under the current |
| 2894 | /// filters and lands in a region, which is what [`consults`](Self::consults) |
| 2895 | /// has always been for. |
| 2896 | /// |
| 2897 | /// [`Destination::Local`]: crate::Destination::Local |
| 2898 | pub suggests: , |
| 2899 | /// Whether what is in this control belongs to the reader rather than to the |
| 2900 | /// answer that drew it. |
| 2901 | /// |
| 2902 | /// Say it, one member, on the field. |
| 2903 | /// |
| 2904 | /// The fact is that the server never sent this value and cannot send it |
| 2905 | /// again, so a redraw that clears the control loses something only the |
| 2906 | /// reader had. A tag typeahead is the measured case: type three letters, |
| 2907 | /// tick an unrelated facet, and the surrounding region is swapped out of |
| 2908 | /// band with the box in it. |
| 2909 | /// |
| 2910 | /// # Why it has to be said rather than assumed |
| 2911 | /// |
| 2912 | /// Because only one host's default is destructive, which is the usual |
| 2913 | /// reason a fact enters this vocabulary. A terminal keeps its own buffer |
| 2914 | /// and egui keeps widget state by id, so both were already right; a browser |
| 2915 | /// replaces the element and takes what was typed with it. |
| 2916 | /// |
| 2917 | /// # Why not on the region |
| 2918 | /// |
| 2919 | /// Which is what the original task asked for, and it cannot be |
| 2920 | /// implemented. Preservation is per-element, so a renderer told "redraw |
| 2921 | /// this region but keep the reader's half" has no way to know *which* |
| 2922 | /// elements hold reader state, and "preserve every input in here" would |
| 2923 | /// keep a facet control the answer legitimately reset. The field is the |
| 2924 | /// only place that knows. |
| 2925 | /// |
| 2926 | /// # Why not inferred |
| 2927 | /// |
| 2928 | /// A rule like "no value, plus [`suggests`](Self::suggests) or |
| 2929 | /// [`consults`](Self::consults), means reader-owned" would cover the |
| 2930 | /// measured site exactly and cost nothing to write. Declined in the same |
| 2931 | /// ruling: it changes behaviour when a field gains or loses an unrelated |
| 2932 | /// member, so a description that starts preserving because somebody added a |
| 2933 | /// consult is a surprise nobody wrote down. |
| 2934 | /// |
| 2935 | /// # What it is not |
| 2936 | /// |
| 2937 | /// Not [`value`](Self::value), which is what the answer offers and what a |
| 2938 | /// refusal re-offers. A field can have both: the answer says what it |
| 2939 | /// starts as, and this says nobody may take it away afterwards. |
| 2940 | pub keeps_value: bool, |
| 2941 | /// The slots this question is answered in, when it is answered more than |
| 2942 | /// once. |
| 2943 | /// |
| 2944 | /// `None` is the ordinary field, asked once and answering once, which is |
| 2945 | /// every other field in the tree. See |
| 2946 | /// [`Repeat`] for the wire naming, the per-slot errors and what this |
| 2947 | /// deliberately is not. |
| 2948 | /// |
| 2949 | /// The members around it keep their meanings and apply to every slot: the |
| 2950 | /// [`kind`](Self::kind), the bounds, the [`placeholder`](Self::placeholder) |
| 2951 | /// and the [`hint`](Self::hint) describe the question, and the question is |
| 2952 | /// what repeats. The two that do not are [`value`](Self::value) and |
| 2953 | /// [`error`](Self::error), which are per-answer and live on the |
| 2954 | /// [`Instance`]; a repeating field's own `error` is what is wrong with the |
| 2955 | /// *set*, which is the fact "at most eight reminders" belongs to. |
| 2956 | /// |
| 2957 | /// [`Field::instance`] is how a renderer gets one slot as an ordinary |
| 2958 | /// field, so that everything a renderer already does to a field it does to |
| 2959 | /// each slot without a second emitter. |
| 2960 | pub repeats: , |
| 2961 | /// What brings this field out, when it is not simply out. |
| 2962 | /// |
| 2963 | /// Found by the consumer `079a011e` was ruled for. The region carries the |
| 2964 | /// condition and that is the shape for a block of several things; a form's |
| 2965 | /// questions are a flat list, so a *single* conditional question inside |
| 2966 | /// one had nowhere to put the same fact. goingson's event form is the |
| 2967 | /// measured site: `initTzKindConfig` shows one box, "Anchored to", on one |
| 2968 | /// of the three zone kinds, and the box has to submit with the form around |
| 2969 | /// it. |
| 2970 | /// |
| 2971 | /// The condition sits on the thing revealed, which is the direction |
| 2972 | /// [`Reveal`] was ruled in: what is refused there is the condition living |
| 2973 | /// on the *watched* control, and that is refused here too. A renderer |
| 2974 | /// answers this exactly as it answers a region's, from what it already |
| 2975 | /// holds, and makes no request. |
| 2976 | /// |
| 2977 | /// A hidden field keeps its value and still submits it, which is what a |
| 2978 | /// browser does with an input inside a hidden element and what the client |
| 2979 | /// renderers already do with a region the reader has closed. |
| 2980 | /// |
| 2981 | /// `None` is the ordinary question, which is every other field in the tree. |
| 2982 | pub revealed_by: , |
| 2983 | |
| 2984 | |
| 2985 | /// A question asked while the reader is still working, rather than when they |
| 2986 | /// are done. |
| 2987 | /// |
| 2988 | /// N14 and N8: the description names the **source** — a route and how long to |
| 2989 | /// wait — and says nothing about what comes back. Both gaps were filed |
| 2990 | /// separately, one asking for "pending / ok / taken" and one for a suggestion |
| 2991 | /// list, and they are the same member: a field that consults something as it |
| 2992 | /// is written. Naming the two shapes instead would have put two mechanisms in |
| 2993 | /// the vocabulary for one idea, which is how a vocabulary drifts. |
| 2994 | /// |
| 2995 | /// # Two places it sits, one mechanism |
| 2996 | /// |
| 2997 | /// [`Field::consults`] asks about one control's value. [`Slot::consults`] asks |
| 2998 | /// about the values of every question inside a region, which is the pricing |
| 2999 | /// calculator's shape: five dials that are peers, and a panel that recomputes |
| 3000 | /// when any of them moves. Neither is about typing -- a select is the |
| 3001 | /// commonest asker of both. — and decided as |
| 3002 | /// *this* type rather than as a second member, because a route, a debounce and |
| 3003 | /// a floor is `Consult` entire and a second timing mechanism for the same idea |
| 3004 | /// is the drift the paragraph above is about. |
| 3005 | /// |
| 3006 | /// The three fields read the same in both places, against the value that just |
| 3007 | /// moved: [`after`](Self::after) is how long it must stand still, |
| 3008 | /// [`at_least`](Self::at_least) is how much of it there must be, and |
| 3009 | /// [`sends`](Self::sends) is what rides along *beyond* what the position |
| 3010 | /// already gathers. What the position gathers differs, and that is the whole of |
| 3011 | /// the difference: a field sends its own value, and a region sends the values |
| 3012 | /// of the questions it contains. |
| 3013 | /// |
| 3014 | /// # What comes back is not described, deliberately |
| 3015 | /// |
| 3016 | /// The route answers with whatever it answers with, and the renderer decides |
| 3017 | /// the wire format: a fragment for a webview, values for a terminal or egui. |
| 3018 | /// A three-way verdict and a list of suggestions are then the same member with |
| 3019 | /// two different routes behind it, and adding a third kind of answer costs |
| 3020 | /// nothing here. |
| 3021 | /// |
| 3022 | /// Where the answer lands is [`Action::replacing`], which already says that |
| 3023 | /// about every other action. Nothing new is needed to point a verdict at the |
| 3024 | /// status line beside the box. |
| 3025 | /// |
| 3026 | /// # The accepted costs, so they are not re-argued |
| 3027 | /// |
| 3028 | /// A field points at a route for the first time in the vocabulary, and a host |
| 3029 | /// with nothing to ask — no HTTP, no local responder — cannot honour one. Both |
| 3030 | /// were weighed and taken: the alternative was leaving four wizard fields and |
| 3031 | /// two comboboxes as host code forever. |
| 3032 | /// |
| 3033 | /// # Two numbers, not one |
| 3034 | /// |
| 3035 | /// [`after`](Self::after) says the value has stopped moving and |
| 3036 | /// [`at_least`](Self::at_least) says there is enough of it to be worth asking |
| 3037 | /// about. Both are the description's for the same reason — a renderer picking |
| 3038 | /// either is a renderer the other renderers disagree with — and the measured |
| 3039 | /// sites carry both: MNW's tag typeahead waits 150ms above two characters and |
| 3040 | /// its search box 200ms above two, while the four wizard fields wait 500ms and |
| 3041 | /// ask about a single letter, because one letter can already be taken. |
| 3042 | |
| 3043 | |
| 3044 | /// The route asked, receiving the value under the field's |
| 3045 | /// [`name`](Field::name) — the same name a submit would send it under. |
| 3046 | pub action: Action, |
| 3047 | /// How long the value must stand still before asking. |
| 3048 | /// |
| 3049 | /// A property of the question rather than of the renderer, which is the |
| 3050 | /// half quasi-tui went without: its runtime fires a |
| 3051 | /// [`writes`](Field::writes) write on every keystroke and its own comment |
| 3052 | /// says why it cannot do otherwise — "nothing in the description says a |
| 3053 | /// delay is allowed", and a number the renderer picked would be a number |
| 3054 | /// the webview and the terminal disagree about. |
| 3055 | pub after: Duration, |
| 3056 | /// How many characters the value must carry before the route is asked at |
| 3057 | /// all, counted in `char`s. |
| 3058 | /// |
| 3059 | /// A floor rather than a wait, and the two are not the same question: a |
| 3060 | /// debounce says the value has stopped moving, and this says there is |
| 3061 | /// enough of it to be worth asking about. Both measured sites spell both |
| 3062 | /// numbers and they differ: MNW's tag typeahead waits 150ms and refuses |
| 3063 | /// under two characters, its search box waits 200ms and refuses the same. |
| 3064 | /// |
| 3065 | /// It is the description's for the reason the wait is: a route answering |
| 3066 | /// `a%` over every project and item in the catalogue is the expensive |
| 3067 | /// question, and neither renderer nor route can know how expensive without |
| 3068 | /// being told. Both MNW suggestion routes guard emptiness and nothing else, |
| 3069 | /// so a described field that dropped this would ask them on the first |
| 3070 | /// letter, which is the query the floor exists to refuse. |
| 3071 | /// |
| 3072 | /// `0`, the default, asks whatever is there. |
| 3073 | pub at_least: usize, |
| 3074 | /// Other controls whose values ride along with the question, named the way |
| 3075 | /// a submit names them. |
| 3076 | /// |
| 3077 | /// `N8`. A box that asks about its own value alone is the common case and |
| 3078 | /// leaves this empty. MNW's discover search is the other one: the results |
| 3079 | /// it re-reads are the results *under the current filters*, so the question |
| 3080 | /// is not "what matches `q`" but "what matches `q`, in this mode, sorted |
| 3081 | /// this way, under these tags". Asked without them the route answers about |
| 3082 | /// a screen the user is not looking at. |
| 3083 | /// |
| 3084 | /// # Named by field name, not by a group |
| 3085 | /// |
| 3086 | /// The shipped markup groups them with `hx-include=".discover-filter"`, a |
| 3087 | /// CSS class, which is the option this rejects. A class is a fact about the |
| 3088 | /// document and a terminal has none; naming the fields names something |
| 3089 | /// every host already has, since [`Field::name`] is what a submit sends a |
| 3090 | /// value under and what a [`Consult`] sends the typed value under. So a |
| 3091 | /// route reads all of them out of one bag, under the names it already |
| 3092 | /// expects, whichever host asked. |
| 3093 | /// |
| 3094 | /// A name that no field on the screen carries sends nothing. That is a |
| 3095 | /// description bug and every renderer treats it as one value fewer rather |
| 3096 | /// than an error, for [`Screen::replace`]'s reason: a miss is worth being |
| 3097 | /// able to see, and is not worth refusing to draw a screen over. |
| 3098 | /// |
| 3099 | /// # On a region, this is what rides along from *outside* it |
| 3100 | /// |
| 3101 | /// [`Slot::consults`] gathers the questions the region contains, by |
| 3102 | /// containment rather than by name, so the common region consult leaves |
| 3103 | /// this empty too. A dial that sits outside the panel it recomputes is what |
| 3104 | /// names itself here, and it names itself the same way: by |
| 3105 | /// [`Field::name`], which is the one address every host has. |
| 3106 | pub sends: , |
| 3107 | |
| 3108 | |
| 3109 | |
| 3110 | /// What the four measured sites already wait, and what |
| 3111 | /// [`Field::consults`] uses when nothing else is said. |
| 3112 | /// |
| 3113 | /// All four MNW wizard fields spell `delay:500ms` by hand, so this is the |
| 3114 | /// corpus' own number rather than a chosen one. |
| 3115 | pub const SETTLES: Duration = from_millis; |
| 3116 | |
| 3117 | /// Ask this route once the value has stood still for [`SETTLES`](Self::SETTLES). |
| 3118 | |
| 3119 | pub const |
| 3120 | Self |
| 3121 | action, |
| 3122 | after: SelfSETTLES, |
| 3123 | at_least: 0, |
| 3124 | sends: Vecnew, |
| 3125 | |
| 3126 | |
| 3127 | |
| 3128 | /// Ask this route the moment the value moves. |
| 3129 | /// |
| 3130 | /// [`new`](Self::new)'s wait is a typing wait, and a control chosen in one |
| 3131 | /// gesture has nothing to wait out: a select is at its next value or its |
| 3132 | /// last one, never on the way between them. Zero is a real number here |
| 3133 | /// rather than a missing one, which is why this is a constructor and not |
| 3134 | /// an `Option`. |
| 3135 | /// |
| 3136 | /// Not the default for a discrete kind. The description says the wait, the |
| 3137 | /// renderer says the event ([`Field::consults`]), and a kind deciding the |
| 3138 | /// wait would be the third party to the same question -- a radio group |
| 3139 | /// inside a [`Slot::consults`] already waits on purpose. |
| 3140 | |
| 3141 | pub const |
| 3142 | Selfnew.after |
| 3143 | |
| 3144 | |
| 3145 | /// Ask this route once the value has stood still for `after`. |
| 3146 | |
| 3147 | pub const |
| 3148 | self.after = after; |
| 3149 | self |
| 3150 | |
| 3151 | |
| 3152 | /// Whether a value has enough in it to be worth asking about. |
| 3153 | /// |
| 3154 | /// The floor stated once rather than in each renderer: three copies of |
| 3155 | /// `chars().count() >= n` are three places to disagree about whether the |
| 3156 | /// count is bytes or characters, and the terminal is the host where that |
| 3157 | /// difference is a wrong answer rather than a slow one. |
| 3158 | /// |
| 3159 | /// Says nothing about the wait, which is the half a renderer cannot be |
| 3160 | /// spared: htmx delays it, the TUI hands it to the host, egui keys it on a |
| 3161 | /// deadline it repaints for. |
| 3162 | /// |
| 3163 | /// On a [`Slot::consults`] the value is the one that just moved, never the |
| 3164 | /// gathered set. A floor over a set has no meaning a reader could predict — |
| 3165 | /// five dials holding one character each are not five characters — and the |
| 3166 | /// browser says the same thing in its own words, where the filter reads |
| 3167 | /// `event.target.value`. |
| 3168 | |
| 3169 | |
| 3170 | value.chars.count >= self.at_least |
| 3171 | |
| 3172 | |
| 3173 | /// Do not ask at all until the value carries `chars` characters. |
| 3174 | /// |
| 3175 | /// See [`at_least`](Self::at_least). Combines with |
| 3176 | /// [`after`](Self::after): the value has to be long enough *and* to have |
| 3177 | /// stood still. |
| 3178 | |
| 3179 | pub const |
| 3180 | self.at_least = chars; |
| 3181 | self |
| 3182 | |
| 3183 | |
| 3184 | /// Send these other fields' values with the question. |
| 3185 | /// |
| 3186 | /// See [`sends`](Self::sends). The typed value goes under the asking |
| 3187 | /// field's own name whether or not this is set, so a call names only what |
| 3188 | /// it needs *beside* that. |
| 3189 | /// |
| 3190 | /// Replaces rather than appends, which is the reading a builder has to |
| 3191 | /// have: a second call is a correction of the set, and a set built up over |
| 3192 | /// two calls would depend on where in the chain each one sat. |
| 3193 | |
| 3194 | |
| 3195 | self.sends = names.into_iter.map.collect; |
| 3196 | self |
| 3197 | |
| 3198 | |
| 3199 | |
| 3200 | |
| 3201 | /// A plain optional field of the given kind. |
| 3202 | |
| 3203 | Self |
| 3204 | kind, |
| 3205 | name: name.into, |
| 3206 | upper_name: None, |
| 3207 | label: label.into, |
| 3208 | hint: None, |
| 3209 | error: None, |
| 3210 | note: None, |
| 3211 | placeholder: None, |
| 3212 | options: Vecnew, |
| 3213 | themes: Vecnew, |
| 3214 | follows: None, |
| 3215 | accept: Vecnew, |
| 3216 | multiple: false, |
| 3217 | required: false, |
| 3218 | max_length: None, |
| 3219 | min: None, |
| 3220 | max: None, |
| 3221 | step: None, |
| 3222 | curve: Linear , |
| 3223 | unit: None, |
| 3224 | extended: false, |
| 3225 | as_instant: false, |
| 3226 | // `Fill`, matching `Column::new` and matching what every renderer |
| 3227 | // did before this member existed. A default of `Content` would have |
| 3228 | // been the tidier reading and would have silently narrowed every |
| 3229 | // described field in every app on the day it landed, which is the |
| 3230 | // one thing an additive member must not do. |
| 3231 | width: Fill, |
| 3232 | value: None, |
| 3233 | upper_value: None, |
| 3234 | writes: None, |
| 3235 | consults: Vecnew, |
| 3236 | suggests: None, |
| 3237 | keeps_value: false, |
| 3238 | repeats: None, |
| 3239 | revealed_by: None, |
| 3240 | |
| 3241 | |
| 3242 | |
| 3243 | /// A file field taking the given accept list. |
| 3244 | /// |
| 3245 | /// [`layout::Field::upload`]'s counterpart and it earns a constructor for |
| 3246 | /// the same reason: a file field with no list is not broken, it is one that |
| 3247 | /// takes anything, so an accidental omission looks exactly like a deliberate |
| 3248 | /// choice unless the list is an argument. Pass an empty slice to mean any |
| 3249 | /// file and mean it. |
| 3250 | |
| 3251 | |
| 3252 | name: impl , |
| 3253 | label: impl , |
| 3254 | accept: impl , |
| 3255 | |
| 3256 | Self |
| 3257 | accept: accept.into_iter.collect, |
| 3258 | ..Selfnew |
| 3259 | |
| 3260 | |
| 3261 | |
| 3262 | /// Say that what is in this control is the reader's and survives a redraw. |
| 3263 | /// |
| 3264 | /// See [`keeps_value`](Self::keeps_value). The measured case is a box the |
| 3265 | /// reader types into whose surrounding region is swapped by something else |
| 3266 | /// on the screen. |
| 3267 | |
| 3268 | pub const |
| 3269 | self.keeps_value = true; |
| 3270 | self |
| 3271 | |
| 3272 | |
| 3273 | /// Several files at once, not one. |
| 3274 | |
| 3275 | pub const |
| 3276 | self.multiple = true; |
| 3277 | self |
| 3278 | |
| 3279 | |
| 3280 | /// Whether anything in [`accept`](Self::accept) names a media family. |
| 3281 | /// |
| 3282 | /// [`layout::Field::accepts_media`] asked of the owned form: the question a |
| 3283 | /// renderer asks before it keeps room for a preview, answered of the whole |
| 3284 | /// list because a dropzone taking `image/*,video/*` has two families and |
| 3285 | /// still has a disclosure to offer. |
| 3286 | |
| 3287 | |
| 3288 | self.accept |
| 3289 | .iter |
| 3290 | .any |
| 3291 | |
| 3292 | |
| 3293 | /// How much of its row this asks for. |
| 3294 | |
| 3295 | pub const |
| 3296 | self.width = width; |
| 3297 | self |
| 3298 | |
| 3299 | |
| 3300 | /// Changing this writes, without waiting for a submit. |
| 3301 | |
| 3302 | |
| 3303 | self.writes = Some; |
| 3304 | self |
| 3305 | |
| 3306 | |
| 3307 | /// Ask this route about the value as the reader works on it, once it has |
| 3308 | /// stood still for [`Consult::SETTLES`]. |
| 3309 | /// |
| 3310 | /// Not only while it is typed: a select, a radio or a slider asks the same |
| 3311 | /// question, and [`consulting`](Self::consulting) with |
| 3312 | /// [`Consult::after`] of zero is what one of those usually wants, since its |
| 3313 | /// value was complete the moment it moved. |
| 3314 | /// |
| 3315 | /// Point the answer somewhere with [`Action::replacing`]; what it contains |
| 3316 | /// is between the route and the renderer. Use [`consulting`](Self::consulting) |
| 3317 | /// for a different interval, or for a floor under which nothing is asked at |
| 3318 | /// all — see [`Consult::at_least`]. |
| 3319 | /// |
| 3320 | /// Adds a question rather than replacing the ones already asked, which is |
| 3321 | /// what a field with several of them needs and what a builder that took the |
| 3322 | /// last call would make unwritable. |
| 3323 | |
| 3324 | |
| 3325 | self.consults.push; |
| 3326 | self |
| 3327 | |
| 3328 | |
| 3329 | /// [`consults`](Self::consults) with the wait, the floor or what rides |
| 3330 | /// along named. |
| 3331 | /// |
| 3332 | /// Adds, for [`consults`](Self::consults)' reason. Chain it twice for a box |
| 3333 | /// that asks two routes at two rates, which is what MNW's discover search |
| 3334 | /// does. |
| 3335 | |
| 3336 | |
| 3337 | self.consults.push; |
| 3338 | self |
| 3339 | |
| 3340 | |
| 3341 | /// This field owns a suggestion list, filled from this route once the value |
| 3342 | /// has stood still for [`Consult::SETTLES`]. |
| 3343 | /// |
| 3344 | /// [`suggests`](Self::suggests). Use [`suggesting`](Self::suggesting) to |
| 3345 | /// name the wait, the floor, or what rides along — both measured MNW sites |
| 3346 | /// spell a floor, and a suggestion route asked on the first letter is the |
| 3347 | /// `ILIKE 'a%'` over the whole catalogue the floor exists to refuse. |
| 3348 | /// |
| 3349 | /// Replaces rather than adds, unlike [`consults`](Self::consults): a field |
| 3350 | /// owns one list, and a second call is a description changing its mind |
| 3351 | /// rather than asking a second question. |
| 3352 | |
| 3353 | |
| 3354 | self.suggests = Some; |
| 3355 | self |
| 3356 | |
| 3357 | |
| 3358 | /// [`suggests`](Self::suggests) with the wait, the floor or what rides |
| 3359 | /// along named. |
| 3360 | |
| 3361 | |
| 3362 | self.suggests = Some; |
| 3363 | self |
| 3364 | |
| 3365 | |
| 3366 | /// This question is answered zero or more times, in slots the reader adds |
| 3367 | /// and removes. |
| 3368 | /// |
| 3369 | /// See [`Repeat`], which carries the answers standing now, the floor and |
| 3370 | /// ceiling on how many there may be, and what the two controls are called. |
| 3371 | |
| 3372 | |
| 3373 | self.repeats = Some; |
| 3374 | self |
| 3375 | |
| 3376 | |
| 3377 | /// This question only applies while another control holds a value. |
| 3378 | /// |
| 3379 | /// [`revealed_by`](Self::revealed_by). The counterpart of |
| 3380 | /// [`Slot::revealed_by`], for the one question inside a form rather than |
| 3381 | /// for a block of several things, and it is answered the same way by every |
| 3382 | /// renderer. |
| 3383 | /// |
| 3384 | /// Calling it twice replaces the condition, which is the reading every |
| 3385 | /// builder here has: the second call is a correction. |
| 3386 | /// |
| 3387 | /// ``` |
| 3388 | /// use quasi_router::{Field, Reveal, layout::FieldKind}; |
| 3389 | /// |
| 3390 | /// let zone = Field::new(FieldKind::Text, "timezone", "Anchored to") |
| 3391 | /// .revealed_by(Reveal::holding("tz_kind", "local")); |
| 3392 | /// |
| 3393 | /// assert!(zone.revealed(Some("local"))); |
| 3394 | /// assert!(!zone.revealed(Some("relative"))); |
| 3395 | /// assert_eq!(zone.watches(), Some("tz_kind")); |
| 3396 | /// ``` |
| 3397 | |
| 3398 | |
| 3399 | self.revealed_by = Some; |
| 3400 | self |
| 3401 | |
| 3402 | |
| 3403 | /// Whether this question applies, given what its control is holding. |
| 3404 | /// |
| 3405 | /// `true` for a field that named no condition, which is nearly all of |
| 3406 | /// them. [`Slot::revealed`]'s counterpart, and what a renderer does with |
| 3407 | /// `false` is the renderer's on the same terms. |
| 3408 | |
| 3409 | |
| 3410 | self.revealed_by |
| 3411 | .as_ref |
| 3412 | .is_none_or |
| 3413 | |
| 3414 | |
| 3415 | /// The name of the control this question is watching, if it watches one. |
| 3416 | |
| 3417 | |
| 3418 | self.revealed_by |
| 3419 | .as_ref |
| 3420 | .map |
| 3421 | |
| 3422 | |
| 3423 | /// One slot of a repeating question, as an ordinary field. |
| 3424 | /// |
| 3425 | /// The name is [`Repeat::at`] of this field's, the label is |
| 3426 | /// [`Repeat::ordinal`] of this field's, and the value and the error are |
| 3427 | /// that slot's own. Everything else is the question's and is carried |
| 3428 | /// through unchanged, which is the point: a renderer draws a slot with |
| 3429 | /// whatever it already does to a field, and there is no second field |
| 3430 | /// emitter anywhere in the stack. |
| 3431 | /// |
| 3432 | /// [`repeats`](Self::repeats) is cleared on the way out, so a renderer that |
| 3433 | /// loops over the slots cannot recurse into them. |
| 3434 | /// |
| 3435 | /// A slot past the end of what the description offered is an empty box |
| 3436 | /// under the right name, which is exactly what a slot the reader has just |
| 3437 | /// added is. That is why this takes an index rather than an |
| 3438 | /// [`Instance`]: the reader's slots outnumber the description's the moment |
| 3439 | /// the add control is pressed, and both are drawn the same way. |
| 3440 | /// |
| 3441 | /// Answered for a field that repeats nothing too, and the answer is the |
| 3442 | /// field itself with its name indexed. Nothing calls it that way, and |
| 3443 | /// refusing would make every renderer branch before it could loop. |
| 3444 | |
| 3445 | |
| 3446 | let held = self |
| 3447 | .repeats |
| 3448 | .as_ref |
| 3449 | .and_then; |
| 3450 | Self |
| 3451 | name: at, |
| 3452 | label: held |
| 3453 | .and_then |
| 3454 | .unwrap_or_else, |
| 3455 | value: held.and_then, |
| 3456 | error: held.and_then, |
| 3457 | repeats: None, |
| 3458 | // The condition belongs to the question, and a renderer that has |
| 3459 | // reached the slots has already answered it once for the whole |
| 3460 | // group. A slot carrying it would have every renderer asking the |
| 3461 | // same question once per box. |
| 3462 | revealed_by: None, |
| 3463 | ..self.clone |
| 3464 | |
| 3465 | |
| 3466 | |
| 3467 | /// One named question of one slot, as an ordinary field. |
| 3468 | /// |
| 3469 | /// [`instance`](Self::instance) one level finer, and for its reason: a |
| 3470 | /// renderer draws a part with whatever it already does to a field, so a |
| 3471 | /// slot that is several questions needs no second emitter either. The name |
| 3472 | /// is [`Repeat::part_at`], the label is the part's own and is not numbered, |
| 3473 | /// and the value and the error are the part's. |
| 3474 | /// |
| 3475 | /// [`repeats`](Self::repeats) is cleared for |
| 3476 | /// [`instance`](Self::instance)'s reason, and so is |
| 3477 | /// [`revealed_by`](Self::revealed_by). |
| 3478 | |
| 3479 | |
| 3480 | let repeat = self.repeats.as_ref; |
| 3481 | let question = repeat.and_then; |
| 3482 | let answered = repeat |
| 3483 | .and_then |
| 3484 | .map |
| 3485 | .unwrap_or_default; |
| 3486 | Self |
| 3487 | name: part_at |
| 3488 | &self.name, |
| 3489 | at, |
| 3490 | question.map_or, |
| 3491 | , |
| 3492 | label: question.map_or_else, |
| 3493 | value: answered.value, |
| 3494 | error: answered.error, |
| 3495 | repeats: None, |
| 3496 | revealed_by: None, |
| 3497 | ..self.clone |
| 3498 | |
| 3499 | |
| 3500 | |
| 3501 | /// Every field one slot draws: the slot itself, or one per part. |
| 3502 | /// |
| 3503 | /// The hinge the three renderers turn on, so that none of them carries the |
| 3504 | /// branch. A slot of an ordinary repeating question is one field and this |
| 3505 | /// answers one; a slot of a grouped one is [`Repeat::parts`] fields in |
| 3506 | /// their order. Four walks in the TUI alone read this -- the height, the |
| 3507 | /// drawing, the caret and the submitted names -- and they agree because |
| 3508 | /// they ask the same question rather than each looping the parts. |
| 3509 | |
| 3510 | |
| 3511 | match self.repeats.as_ref |
| 3512 | Some if repeat.grouped => |
| 3513 | .map |
| 3514 | .collect, |
| 3515 | _ => vec!, |
| 3516 | |
| 3517 | |
| 3518 | |
| 3519 | /// How many slots this question stands in right now, as described. |
| 3520 | /// |
| 3521 | /// `1` for the ordinary field, which is the count a caller with no interest |
| 3522 | /// in repetition can loop over without asking whether there is any. |
| 3523 | |
| 3524 | |
| 3525 | self.repeats.as_ref.map_or |
| 3526 | |
| 3527 | |
| 3528 | /// This field as a control asks it: a question, and nothing that writes. |
| 3529 | /// |
| 3530 | /// What every renderer draws for a member of [`Act::asks`]. A field there |
| 3531 | /// is answered by the press that asked for it, so a |
| 3532 | /// [`writes`](Self::writes) route on it would fire a second write for the |
| 3533 | /// same value. [`consults`](Self::consults) survives: asking whether a tag |
| 3534 | /// slug is taken is a question about the value, not a write of it. |
| 3535 | |
| 3536 | |
| 3537 | Self |
| 3538 | writes: None, |
| 3539 | ..self.clone |
| 3540 | |
| 3541 | |
| 3542 | |
| 3543 | /// A select offering the given options. |
| 3544 | /// The options offered, appended to the ones already there. |
| 3545 | /// |
| 3546 | /// The accreting half of [`select`](Self::select) and [`radio`](Self::radio), |
| 3547 | /// which take the whole list as an argument. Beside them for the reason |
| 3548 | /// [`Table::column`] sits beside `Table::new`: a caller with no expression |
| 3549 | /// to hold a list in still has to be able to offer options, and `options` |
| 3550 | /// was reachable only by assigning the field. |
| 3551 | /// |
| 3552 | /// Says nothing about the kind. A kind that offers no options ignores them, |
| 3553 | /// which is what [`layout::FieldKind::offers_options`] already decides. |
| 3554 | |
| 3555 | |
| 3556 | self.options.extend; |
| 3557 | self |
| 3558 | |
| 3559 | |
| 3560 | /// Offer one more option, chaining. |
| 3561 | /// |
| 3562 | /// The accreting half of [`Self::options`], which takes the whole list. The |
| 3563 | /// fifth constructor of exactly this shape, after [`Table::column`], |
| 3564 | /// [`Row::cell`], [`Self::options`] itself and [`Node::figure`]: a caller |
| 3565 | /// building its options one at a time has no expression to hold a list in. |
| 3566 | /// MNW's repository bar is the site -- its ref chooser offers a branch or a |
| 3567 | /// tag per ref, and which of the two decides only the label. |
| 3568 | |
| 3569 | |
| 3570 | self.options.push; |
| 3571 | self |
| 3572 | |
| 3573 | |
| 3574 | |
| 3575 | Self |
| 3576 | options, |
| 3577 | ..Selfnew |
| 3578 | |
| 3579 | |
| 3580 | |
| 3581 | /// A radio group offering the given options. |
| 3582 | |
| 3583 | Self |
| 3584 | options, |
| 3585 | ..Selfnew |
| 3586 | |
| 3587 | |
| 3588 | |
| 3589 | /// A theme picker over the themes the host resolved. |
| 3590 | /// |
| 3591 | /// A constructor for [`layout::Field::theme`]'s reason: the list is the one |
| 3592 | /// thing this kind takes that a call site can get wrong by *substitution*, |
| 3593 | /// since [`options`](Self::options) is right there and reads as if it would |
| 3594 | /// work. A renderer walking `options` for a theme picker draws an empty |
| 3595 | /// control. |
| 3596 | /// |
| 3597 | /// [`following`](Self::following) is a builder rather than a fourth |
| 3598 | /// argument: a picker with no follow-the-system row is a real picker. |
| 3599 | |
| 3600 | name: impl , |
| 3601 | label: impl , |
| 3602 | themes: , |
| 3603 | |
| 3604 | Self |
| 3605 | themes, |
| 3606 | ..Selfnew |
| 3607 | |
| 3608 | |
| 3609 | |
| 3610 | /// Offer these themes, chaining. |
| 3611 | /// |
| 3612 | /// The accreting half of [`theme`](Self::theme), which takes the whole list |
| 3613 | /// as an argument, and the same gap [`options`](Self::options) fills beside |
| 3614 | /// [`select`](Self::select): a caller with no expression to hold a list in |
| 3615 | /// still has to be able to offer themes. GoingsOn's Appearance section is |
| 3616 | /// the site. |
| 3617 | /// |
| 3618 | /// Says nothing about the kind, exactly as `options` does not. A renderer |
| 3619 | /// reads these only for [`layout::FieldKind::Theme`]. |
| 3620 | |
| 3621 | |
| 3622 | self.themes.extend; |
| 3623 | self |
| 3624 | |
| 3625 | |
| 3626 | /// The same picker, offering a row that tracks the ambient mode. |
| 3627 | /// |
| 3628 | /// The [`Choice`] carries the value the app's own store spells it with — |
| 3629 | /// `makeover::FOLLOW` for every store in the family today, and none of them |
| 3630 | /// is obliged to keep it. |
| 3631 | |
| 3632 | |
| 3633 | self.follows = Some; |
| 3634 | self |
| 3635 | |
| 3636 | |
| 3637 | /// A bounded number the user drags across its whole extent. |
| 3638 | /// |
| 3639 | /// The bounds are arguments for [`layout::Field::range`]'s reason: they are |
| 3640 | /// not a rule the answer is checked against, they are the control, so a |
| 3641 | /// range that forgot them has nothing to slide across. |
| 3642 | |
| 3643 | name: impl , |
| 3644 | label: impl , |
| 3645 | min: impl , |
| 3646 | max: impl , |
| 3647 | |
| 3648 | Self |
| 3649 | min: Some, |
| 3650 | max: Some, |
| 3651 | ..Selfnew |
| 3652 | |
| 3653 | |
| 3654 | |
| 3655 | /// One question with two ends, taking the name each end submits under. |
| 3656 | /// |
| 3657 | /// The names are arguments for [`layout::Field::interval`]'s reason: an |
| 3658 | /// interval built without the second one has an upper end with nowhere to be |
| 3659 | /// submitted, and nothing downstream can invent a name for it. |
| 3660 | /// |
| 3661 | /// The extent stays optional, unlike [`range`](Self::range)'s. An interval's |
| 3662 | /// bounds are a rule each end is checked against rather than the control, so |
| 3663 | /// a missing one is an open end rather than a control with nothing to slide |
| 3664 | /// across. |
| 3665 | |
| 3666 | name: impl , |
| 3667 | upper_name: impl , |
| 3668 | label: impl , |
| 3669 | |
| 3670 | Self |
| 3671 | upper_name: Some, |
| 3672 | ..Selfnew |
| 3673 | |
| 3674 | |
| 3675 | |
| 3676 | /// The name the upper end submits under, for an interval built by hand. |
| 3677 | /// |
| 3678 | /// [`interval`](Self::interval) is still the entry point a hand-written |
| 3679 | /// caller should take, and its doc says why the names are arguments there. |
| 3680 | /// This exists because a description cannot reach a three-argument |
| 3681 | /// constructor: `quasi-declare`'s `field` production says a kind, a name |
| 3682 | /// and a label, and everything else about a field is a setting in its body. |
| 3683 | /// audiofiles' filter panel is the site -- six intervals, and without this |
| 3684 | /// the only described spelling was the whole `Field` written out as an |
| 3685 | /// aggregate, which is the expression the form exists to refuse. |
| 3686 | /// |
| 3687 | /// Pairs with [`upper_value`](Self::upper_value), which names the same end's |
| 3688 | /// answer. |
| 3689 | |
| 3690 | |
| 3691 | self.upper_name = Some; |
| 3692 | self |
| 3693 | |
| 3694 | |
| 3695 | /// The granularity the value moves in. |
| 3696 | /// |
| 3697 | /// A builder rather than a fourth argument to [`range`](Self::range): the |
| 3698 | /// host's own granularity is a real answer, and a stepped |
| 3699 | /// [`layout::FieldKind::Number`] wants this too. |
| 3700 | |
| 3701 | |
| 3702 | let step = step.into; |
| 3703 | // Routed to wherever this kind keeps its granularity, rather than made |
| 3704 | // two builders. A range's lives on its curve as of makeover-layout |
| 3705 | // 0.32.0 and a typed value's stays here, and a call site saying "this |
| 3706 | // moves in steps of 0.001" means the same thing either way. |
| 3707 | if self.kind == Range |
| 3708 | self.curve = take.with_step; |
| 3709 | else |
| 3710 | self.step = Some; |
| 3711 | |
| 3712 | self |
| 3713 | |
| 3714 | |
| 3715 | /// How this slider's position becomes its value. |
| 3716 | /// |
| 3717 | /// [`layout::FieldKind::Range`]'s, and it carries the granularity with it: |
| 3718 | /// passing a curve replaces whatever [`step`](Self::step) had set. |
| 3719 | |
| 3720 | |
| 3721 | self.curve = curve; |
| 3722 | self |
| 3723 | |
| 3724 | |
| 3725 | /// What the number is measured in. |
| 3726 | /// |
| 3727 | /// The symbol alone -- `s`, not `(s)` and not ` s`. Where it is drawn and |
| 3728 | /// how it is spaced is the renderer's, which is why the description carries |
| 3729 | /// neither. |
| 3730 | |
| 3731 | |
| 3732 | self.unit = Some; |
| 3733 | self |
| 3734 | |
| 3735 | |
| 3736 | /// The form refuses to submit without it. |
| 3737 | |
| 3738 | |
| 3739 | self.required = true; |
| 3740 | self |
| 3741 | |
| 3742 | |
| 3743 | /// The wall-clock value is submitted as the moment it names. |
| 3744 | /// |
| 3745 | /// See [`as_instant`](Self::as_instant) for what it asks and who answers |
| 3746 | /// it. A [`layout::FieldKind::DateTime`]'s; sayable and ignored elsewhere. |
| 3747 | |
| 3748 | |
| 3749 | self.as_instant = true; |
| 3750 | self |
| 3751 | |
| 3752 | |
| 3753 | /// Standing help, shown whether or not anything is wrong. |
| 3754 | |
| 3755 | |
| 3756 | self.hint = Some; |
| 3757 | self |
| 3758 | |
| 3759 | |
| 3760 | /// What is wrong with the value now. |
| 3761 | |
| 3762 | |
| 3763 | self.error = Some; |
| 3764 | self |
| 3765 | |
| 3766 | |
| 3767 | /// Ghost text shown while the field is empty. |
| 3768 | /// |
| 3769 | /// Was reachable only by assigning the field, which is |
| 3770 | /// The missing-builder defect met again and on the largest field in the |
| 3771 | /// vocabulary: 54 sites across the three shape trees fall out of a builder |
| 3772 | /// chain to write it, which is more than any other member here. |
| 3773 | /// |
| 3774 | /// Not [`hint`](Self::hint), and the two are worth keeping apart. A hint is |
| 3775 | /// standing help that survives the reader typing; a placeholder is a sample |
| 3776 | /// answer that disappears the moment they do, so it cannot carry anything |
| 3777 | /// they will need later. A renderer that had only one of them would have to |
| 3778 | /// pick which behaviour to give it. |
| 3779 | /// |
| 3780 | |
| 3781 | |
| 3782 | self.placeholder = Some; |
| 3783 | self |
| 3784 | |
| 3785 | |
| 3786 | /// The longest the value may be, in characters. |
| 3787 | /// |
| 3788 | /// The description carries the rule and the renderer emits its host's |
| 3789 | /// idiom; deciding that a value is wrong stays with whoever validated it, |
| 3790 | /// which is [`max_length`](Self::max_length)'s own contract. |
| 3791 | |
| 3792 | pub const |
| 3793 | self.max_length = Some; |
| 3794 | self |
| 3795 | |
| 3796 | |
| 3797 | /// The extent a value is accepted within, both ends. |
| 3798 | /// |
| 3799 | /// One builder for the pair rather than two, on |
| 3800 | /// [`Row::ticking`]'s grounds: a bound is only meaningful against the |
| 3801 | /// other one, and two setters can disagree. [`Field::range`] already takes |
| 3802 | /// both together for exactly this reason, and this is how every other kind |
| 3803 | /// reaches what a range gets from its constructor. |
| 3804 | /// |
| 3805 | /// Written the way the host writes a value, which is what |
| 3806 | /// [`min`](Self::min) says: these are strings because a date bound and a |
| 3807 | /// number bound are the same fact about a field and only one of them is a |
| 3808 | /// number. |
| 3809 | /// |
| 3810 | |
| 3811 | |
| 3812 | self.min = Some; |
| 3813 | self.max = Some; |
| 3814 | self |
| 3815 | |
| 3816 | |
| 3817 | /// The lowest value accepted, leaving the upper end open. |
| 3818 | /// |
| 3819 | /// [`within`](Self::within) is the ordinary spelling and sets both. This |
| 3820 | /// exists because an open end is a real answer rather than a missing one -- |
| 3821 | /// [`Field::interval`] says so in as many words -- and a field with a floor |
| 3822 | /// and no ceiling cannot be written by a builder that insists on the pair. |
| 3823 | |
| 3824 | |
| 3825 | self.min = Some; |
| 3826 | self |
| 3827 | |
| 3828 | |
| 3829 | /// The highest value accepted, leaving the lower end open. |
| 3830 | /// |
| 3831 | /// See [`at_least`](Self::at_least). |
| 3832 | |
| 3833 | |
| 3834 | self.max = Some; |
| 3835 | self |
| 3836 | |
| 3837 | |
| 3838 | /// Put the field behind a "more options" disclosure. |
| 3839 | /// |
| 3840 | /// Presence is the whole of it, which is why it takes no argument: a |
| 3841 | /// description says a field is secondary or says nothing, and |
| 3842 | /// `extended(false)` would be a way of writing the default twice. |
| 3843 | |
| 3844 | pub const |
| 3845 | self.extended = true; |
| 3846 | self |
| 3847 | |
| 3848 | |
| 3849 | /// What the answer the user has given costs, in the tone it deserves. |
| 3850 | /// |
| 3851 | /// Not a validation failure: the field stays valid and submittable. See |
| 3852 | /// [`note`](Self::note). |
| 3853 | |
| 3854 | |
| 3855 | self.note = Some; |
| 3856 | self |
| 3857 | |
| 3858 | |
| 3859 | /// Whether the field is currently reporting a problem. |
| 3860 | |
| 3861 | |
| 3862 | self.error.is_some |
| 3863 | |
| 3864 | |
| 3865 | /// Put this back in the box when the form is offered again. |
| 3866 | /// |
| 3867 | /// A [`layout::FieldKind::Secret`] keeps `None` whatever it is handed. A |
| 3868 | /// password that comes back down the wire is a password in a page, in a |
| 3869 | /// proxy log and in a browser cache, and the field kind exists to say so. |
| 3870 | /// Silently rather than by a `Result`, because there is no answer a caller |
| 3871 | /// could give that would make echoing it right. |
| 3872 | |
| 3873 | |
| 3874 | if self.kind != Secret |
| 3875 | self.value = Some; |
| 3876 | |
| 3877 | self |
| 3878 | |
| 3879 | |
| 3880 | /// Put this back in the upper box of an interval when the form is offered |
| 3881 | /// again. |
| 3882 | /// |
| 3883 | /// [`value`](Self::value)'s counterpart and it refuses a |
| 3884 | /// [`layout::FieldKind::Secret`] on the same terms, though no secret is an |
| 3885 | /// interval: the guarantee is written where the value is set rather than |
| 3886 | /// where the kinds happen not to overlap today. |
| 3887 | |
| 3888 | |
| 3889 | if self.kind != Secret |
| 3890 | self.upper_value = Some; |
| 3891 | |
| 3892 | self |
| 3893 | |
| 3894 | |
| 3895 | /// Re-offer whatever was submitted under this field's name. |
| 3896 | /// |
| 3897 | /// What a refused write calls, with the [`Params`](crate::Params) it was |
| 3898 | /// refusing. A name with nothing under it stays empty, which is what an |
| 3899 | /// unticked checkbox and an untouched box both are. |
| 3900 | |
| 3901 | |
| 3902 | let filled = match params.get |
| 3903 | Some => |
| 3904 | let value = value.to_owned; |
| 3905 | self.value |
| 3906 | |
| 3907 | None => self, |
| 3908 | ; |
| 3909 | // An interval was submitted under two names, so re-offering it reads |
| 3910 | // both. Either end staying empty is a real answer rather than a |
| 3911 | // half-filled form: "over 120 BPM" has no upper end. |
| 3912 | let Some = filled.upper_name.clone else |
| 3913 | return filled; |
| 3914 | ; |
| 3915 | match params.get |
| 3916 | Some => |
| 3917 | let value = value.to_owned; |
| 3918 | filled.upper_value |
| 3919 | |
| 3920 | None => filled, |
| 3921 | |
| 3922 | |
| 3923 | |
| 3924 | /// Read this field as the description layer's own type. |
| 3925 | /// |
| 3926 | /// A callback rather than a return, because [`layout::Field`] holds its |
| 3927 | /// options as a slice and ours holds them as owned values, so the borrowed |
| 3928 | /// slice has to live somewhere for the duration of the read. Building it |
| 3929 | /// here means one allocation at the renderer's boundary instead of the |
| 3930 | /// borrow leaking into every caller's signature. |
| 3931 | |
| 3932 | let options: = self.options.iter.map.collect; |
| 3933 | let themes: = |
| 3934 | self.themes.iter.map.collect; |
| 3935 | let accept: = |
| 3936 | self.accept.iter.map.collect; |
| 3937 | f |
| 3938 | kind: self.kind, |
| 3939 | name: &self.name, |
| 3940 | upper_name: self.upper_name.as_deref, |
| 3941 | label: &self.label, |
| 3942 | hint: self.hint.as_deref, |
| 3943 | error: self.error.as_deref, |
| 3944 | note: self.note.as_ref.map, |
| 3945 | placeholder: self.placeholder.as_deref, |
| 3946 | options: &options, |
| 3947 | themes: &themes, |
| 3948 | follows: self.follows.as_ref.map, |
| 3949 | accept: &accept, |
| 3950 | multiple: self.multiple, |
| 3951 | required: self.required, |
| 3952 | max_length: self.max_length, |
| 3953 | min: self.min.as_deref, |
| 3954 | max: self.max.as_deref, |
| 3955 | step: self.step.as_deref, |
| 3956 | curve: self.curve.as_layout, |
| 3957 | unit: self.unit.as_deref, |
| 3958 | extended: self.extended, |
| 3959 | as_instant: self.as_instant, |
| 3960 | |
| 3961 | |
| 3962 | |
| 3963 | |
| 3964 | /// One run of source code, and what it is. |
| 3965 | /// |
| 3966 | /// The unit [`Node::Code`] is a list of, and the whole reason that node carries |
| 3967 | /// runs rather than a string: the app classified the source, and this is the |
| 3968 | /// classification crossing the seam. Decision `19d7602d`, 2026-09-02. |
| 3969 | /// |
| 3970 | /// Owned, for the reason stated at the top of this file: a router's answer |
| 3971 | /// outlives its handler, and the text here is usually built from state rather |
| 3972 | /// than found in it. [`layout::Syntax`] is `Copy`, so only the text allocates. |
| 3973 | /// |
| 3974 | /// # Why the text is carried and not an index |
| 3975 | /// |
| 3976 | /// A `(range, syntax)` pair over one source string would allocate less, and it |
| 3977 | /// was rejected: the ranges are byte offsets into a string the renderer must |
| 3978 | /// then re-slice, so every renderer would have to handle a range that does not |
| 3979 | /// land on a character boundary, and two of the three would get it wrong on the |
| 3980 | /// first file with a multi-byte character in it. The run carrying its own text |
| 3981 | /// cannot express that bug. |
| 3982 | |
| 3983 | |
| 3984 | /// The characters, exactly as they appear in the source. |
| 3985 | /// |
| 3986 | /// Including whitespace. A classifier that strips it hands the renderer a |
| 3987 | /// file it cannot lay out, and indentation is most of what makes source |
| 3988 | /// readable. |
| 3989 | pub text: String, |
| 3990 | /// What the run is. |
| 3991 | pub syntax: Syntax, |
| 3992 | |
| 3993 | |
| 3994 | |
| 3995 | /// A run of ordinary code. |
| 3996 | /// |
| 3997 | /// [`layout::Syntax::Plain`], which is what a classifier that ran and found |
| 3998 | /// nothing says, and what a host with no classifier at all says about a |
| 3999 | /// whole file. |
| 4000 | |
| 4001 | |
| 4002 | Self |
| 4003 | text: text.into, |
| 4004 | syntax: Plain, |
| 4005 | |
| 4006 | |
| 4007 | |
| 4008 | /// A run of a stated class. |
| 4009 | |
| 4010 | |
| 4011 | Self |
| 4012 | text: text.into, |
| 4013 | syntax, |
| 4014 | |
| 4015 | |
| 4016 | |
| 4017 | |
| 4018 | /// One column of a table, owned. |
| 4019 | /// |
| 4020 | /// The borrowed original is [`layout::Column`]. The `name` is both the heading |
| 4021 | /// and the address a cell is found by, which is what replaces addressing |
| 4022 | /// columns by position. |
| 4023 | /// No `Hash`, for the reason [`Tag`] and [`Field`] have none: it can hold an |
| 4024 | /// [`Action`], which holds [`Params`], which is a `Vec`. |
| 4025 | |
| 4026 | |
| 4027 | /// The heading, and the name the cell is addressed by. |
| 4028 | pub name: String, |
| 4029 | /// How much room it asks for. |
| 4030 | pub width: Width, |
| 4031 | /// What it is worth when room runs out. |
| 4032 | pub priority: Priority, |
| 4033 | /// Which way the table is ordered by this column, if it is. |
| 4034 | pub sorted: , |
| 4035 | /// What pressing this heading calls. |
| 4036 | /// |
| 4037 | /// `makeover-layout` carries `Column::sortable`, a bare bool, because it |
| 4038 | /// cannot name an address; here the address *is* the sortability, so the |
| 4039 | /// two collapse into one field and cannot disagree. |
| 4040 | /// [`as_layout`](Self::as_layout) sets the bool from whether this is here. |
| 4041 | /// |
| 4042 | /// Reordering a table is a control that writes with no surrounding submit, |
| 4043 | /// which is `14612ed8`'s shape, and the renderer treats it the same way. |
| 4044 | pub reorder: , |
| 4045 | |
| 4046 | |
| 4047 | |
| 4048 | /// A column that absorbs slack and drops after the optional ones. |
| 4049 | |
| 4050 | Self |
| 4051 | name: name.into, |
| 4052 | width: Fill, |
| 4053 | priority: Secondary, |
| 4054 | sorted: None, |
| 4055 | reorder: None, |
| 4056 | |
| 4057 | |
| 4058 | |
| 4059 | /// Pressing this heading reorders the table. |
| 4060 | |
| 4061 | |
| 4062 | self.reorder = Some; |
| 4063 | self |
| 4064 | |
| 4065 | |
| 4066 | /// The table is currently ordered by this column, this way. |
| 4067 | |
| 4068 | pub const |
| 4069 | self.sorted = Some; |
| 4070 | self |
| 4071 | |
| 4072 | |
| 4073 | /// Set how much room it asks for. |
| 4074 | |
| 4075 | |
| 4076 | self.width = width; |
| 4077 | self |
| 4078 | |
| 4079 | |
| 4080 | /// Set what it is worth when room runs out. |
| 4081 | |
| 4082 | |
| 4083 | self.priority = priority; |
| 4084 | self |
| 4085 | |
| 4086 | |
| 4087 | /// Borrow as the description layer's own type. |
| 4088 | |
| 4089 | |
| 4090 | Column |
| 4091 | name: &self.name, |
| 4092 | width: self.width, |
| 4093 | priority: self.priority, |
| 4094 | sortable: self.reorder.is_some, |
| 4095 | sorted: self.sorted, |
| 4096 | |
| 4097 | |
| 4098 | |
| 4099 | |
| 4100 | /// Which region this is, owned. |
| 4101 | /// |
| 4102 | /// The borrowed original is [`layout::Region`], and only one member borrows: |
| 4103 | /// [`layout::Region::Handover`] carries a name the app owns and this crate never |
| 4104 | /// interprets. |
| 4105 | /// |
| 4106 | /// # Two members are gone, and a row says what they said |
| 4107 | /// |
| 4108 | /// `Split` was two panes side by side and `Columns` was a row of peers. Both |
| 4109 | /// were arrangement spelled as containment: they said nothing about scroll, |
| 4110 | /// depth or what may be inside, only how the room is divided. A [`Run`] whose |
| 4111 | /// members carry a [`layout::Width`] says the same thing and says more of it, |
| 4112 | /// because the row also carries a [`layout::Fallback`] and so can state what |
| 4113 | /// happens when the room runs out, which neither variant could. Split is a run |
| 4114 | /// of [`Content`](layout::Width::Content) then [`Fill`](layout::Width::Fill); |
| 4115 | /// Columns is a run of fills, which divide equally by that type's own rule. |
| 4116 | /// Ruled by Max 2026-09-07 on quasicoherent `b1d4c5d7`, built as `cf981aaa`. |
| 4117 | /// |
| 4118 | /// [`Sidebar`](Self::Sidebar) was measured with them and stays, because it is |
| 4119 | /// not the same kind of fact: it marks *which* region is the side of a |
| 4120 | /// [`layout::Arrangement::SidebarContent`] screen, which `quasi-tui` and |
| 4121 | /// `quasi-immediate` both match on to place it, and no width on a row member |
| 4122 | /// can say that. `Band`, `Pane`, `Group`, `TabGroup` and `Modal` are untouched |
| 4123 | /// for their own reasons: the first is a separate judgement, and the rest are |
| 4124 | /// containment, visibility and z-order rather than arrangement. |
| 4125 | |
| 4126 | |
| 4127 | /// A full-width strip with a title slot and an actions cluster. |
| 4128 | Band, |
| 4129 | /// A persistent column beside the content, holding navigation. |
| 4130 | Sidebar, |
| 4131 | /// A region of content with its own scroll. |
| 4132 | Pane, |
| 4133 | /// Things that belong together, and nothing else. |
| 4134 | /// |
| 4135 | /// The block a [`Heading::Section`](layout::Heading::Section) names, which |
| 4136 | /// had no container until makeover-layout 0.27.0: a section heading is a |
| 4137 | /// leaf beside the things it names, so a description could say a section had |
| 4138 | /// started and never that one had ended. See [`layout::Region::Group`] for |
| 4139 | /// the count behind it. |
| 4140 | /// |
| 4141 | /// Claims no scroll and no depth of its own, which is what separates it from |
| 4142 | /// [`Pane`](Self::Pane) -- the member apps reached for instead, and the |
| 4143 | /// reason 28 of the 45 regions in the described screens were panes. A group |
| 4144 | /// in a pane is in a well; a group on the page is on the page. |
| 4145 | /// |
| 4146 | /// The heading is an ordinary node in the body rather than a field here, so |
| 4147 | /// a group of related toggles with no heading stays legal. |
| 4148 | Group, |
| 4149 | /// A set of panes, one visible at a time, and a strip that chooses |
| 4150 | /// between them. |
| 4151 | /// |
| 4152 | /// Says nothing about where the strip sits. A row over the panes, a column |
| 4153 | /// beside them, a wrapped run of links under them: all three are the same |
| 4154 | /// member drawn by a renderer that knows its host, the way the strip's |
| 4155 | /// overflow is. MNW's settings sub-nav is a column on a wide viewport and a |
| 4156 | /// wrapped row below the breakpoint, one component and one CSS breakpoint, |
| 4157 | /// and it is a tab group on every count this member makes. |
| 4158 | TabGroup, |
| 4159 | /// Content over a scrim, taking input until dismissed. |
| 4160 | /// |
| 4161 | /// A modal this screen *contains*, which is how a confirmation is drawn: it |
| 4162 | /// arrives with the screen and goes when the screen goes. The app-level one |
| 4163 | /// is [`Outcome::Over`](crate::Outcome::Over), which draws a whole screen |
| 4164 | /// over whatever is under it and is reachable from screens that know |
| 4165 | /// nothing about it. |
| 4166 | Modal, |
| 4167 | /// A place, and nothing else. The app fills it per host. |
| 4168 | /// |
| 4169 | /// Decision 4: the renderer hands the space over and the app puts a JS |
| 4170 | /// component, an egui closure or a TUI widget in it. The rejected |
| 4171 | /// alternative was giving the placeholder its own route and fetching a |
| 4172 | /// fragment for it, which is uniform on paper and wrong in currency: a byte |
| 4173 | /// payload is not what egui or a terminal wants. |
| 4174 | /// |
| 4175 | /// All three renderers offer the fill in their own currency: |
| 4176 | /// `Webview::with_fill` takes markup, `Tui::with_fill` takes a drawing that |
| 4177 | /// answers a height and paints into a rect, and `Immediate::with_fill` |
| 4178 | /// takes a closure over a `Ui`. Each is keyed by [`Slot::id`] rather than |
| 4179 | /// by the name here, since a screen of N rows each carrying a fill shares |
| 4180 | /// one name and has N ids. |
| 4181 | /// |
| 4182 | /// # A handover now, a widget eventually |
| 4183 | /// |
| 4184 | /// A behaviour with no described form takes this member rather than |
| 4185 | /// growing the vocabulary with members two of the three renderers could |
| 4186 | /// only degrade -- scrub, rate and chapters were the rejected shape -- and |
| 4187 | /// the surface is not ceded to the host permanently either. Once |
| 4188 | /// [`Widget`](Self::Widget) has grown, a behaviour already implemented |
| 4189 | /// once and tested behind a fill is a candidate to become one, and the |
| 4190 | /// media player is the first of them. |
| 4191 | /// |
| 4192 | /// The revisit trigger is the widget work rather than a count of consumers. |
| 4193 | /// |
| 4194 | /// # What separates it from [`Ceded`](Self::Ceded) |
| 4195 | /// |
| 4196 | /// A fill is owed here in every host's currency. See |
| 4197 | /// [`layout::Region::Handover`]; the short form is that a renderer handed |
| 4198 | /// one of these and given no fill is looking at a hole the app meant to |
| 4199 | /// fill, and should say so rather than draw an empty box. |
| 4200 | Handover |
| 4201 | /// What the app calls it. Never interpreted here. |
| 4202 | name: String, |
| 4203 | , |
| 4204 | /// A place the app fills, that no host is owed a fill for. |
| 4205 | /// |
| 4206 | /// The other half of what was one opaque member. A chart, a waveform, a |
| 4207 | /// rendered picture of domain data: the app has ruled the space is not the |
| 4208 | /// description's to fill and is not going to become so, so a renderer with |
| 4209 | /// nothing to put here draws nothing and is right to. |
| 4210 | /// |
| 4211 | /// See [`layout::Region::Ceded`]. The distinction is worth two members |
| 4212 | /// because the two want opposite behaviour from a renderer that cannot |
| 4213 | /// fill them. |
| 4214 | Ceded |
| 4215 | /// What the app calls it. Never interpreted here. |
| 4216 | name: String, |
| 4217 | , |
| 4218 | /// A named assembly of things the description already says. |
| 4219 | /// |
| 4220 | /// The third tier, and the one member that is a name *and* contents. See |
| 4221 | /// [`layout::Region::Widget`] for what separates it from the two either |
| 4222 | /// side of it; the short form is that a primitive has to be drawable by |
| 4223 | /// every host from scratch and a handover carries nothing under it, and a |
| 4224 | /// carousel is neither. |
| 4225 | /// |
| 4226 | /// The body is the assembly and it is ordinary description: a renderer that |
| 4227 | /// does not recognise the name walks it and draws primitives, which is why |
| 4228 | /// naming one costs no renderer release. Contrast |
| 4229 | /// [`Handover`](Self::Handover), whose body a renderer can draw but whose |
| 4230 | /// *fill* only the host has. |
| 4231 | Widget |
| 4232 | /// What the assembly is called. Never interpreted here, and a renderer |
| 4233 | /// is free not to know it. |
| 4234 | name: String, |
| 4235 | , |
| 4236 | |
| 4237 | |
| 4238 | |
| 4239 | /// A place the app fills itself, and owes every host a fill for. |
| 4240 | /// |
| 4241 | /// Beside [`Slot::handover`], which builds the region and its id in one |
| 4242 | /// call. This is the kind on its own, which is what a caller holding the id |
| 4243 | /// separately needs: the three kinds that carry a name are the three a bare |
| 4244 | /// variant cannot spell. |
| 4245 | |
| 4246 | |
| 4247 | SelfHandover |
| 4248 | |
| 4249 | |
| 4250 | /// A place the app fills itself, that no host is owed a fill for. |
| 4251 | /// |
| 4252 | /// [`Slot::ceded`]'s kind on its own, for the same reason [`handover`] has |
| 4253 | /// one. |
| 4254 | /// |
| 4255 | /// [`handover`]: Self::handover |
| 4256 | |
| 4257 | |
| 4258 | SelfCeded |
| 4259 | |
| 4260 | |
| 4261 | /// Borrow as the description layer's own type. |
| 4262 | |
| 4263 | |
| 4264 | match self |
| 4265 | SelfBand => Band, |
| 4266 | SelfSidebar => Sidebar, |
| 4267 | SelfPane => Pane, |
| 4268 | SelfGroup => Group, |
| 4269 | SelfTabGroup => TabGroup, |
| 4270 | SelfModal => Modal, |
| 4271 | SelfHandover => Handover , |
| 4272 | SelfCeded => Ceded , |
| 4273 | SelfWidget => Widget , |
| 4274 | |
| 4275 | |
| 4276 | |
| 4277 | /// Whether the description can say anything about the contents. |
| 4278 | |
| 4279 | |
| 4280 | self.as_layout.described |
| 4281 | |
| 4282 | |
| 4283 | /// How the region sits on what is behind it. |
| 4284 | |
| 4285 | |
| 4286 | self.as_layout.depth |
| 4287 | |
| 4288 | |
| 4289 | |
| 4290 | /// A node, and what it is worth when there is not room for everything. |
| 4291 | /// |
| 4292 | /// The other half of "Any width, one answer" (`makeover-layout` 0.27.4). A |
| 4293 | /// renderer narrows by raising a cutoff over a declared total order, never by |
| 4294 | /// counting what fits, and the only placement that can declare its rank |
| 4295 | /// otherwise is a [`Column`]. So anything that is not a table hand-rolls its |
| 4296 | /// responsiveness, and hand-rolling means measuring, and measuring means |
| 4297 | /// keeping the measurement, which is how a layout becomes a function of the |
| 4298 | /// width you came from. audiofiles had four of those and one genuine |
| 4299 | /// measure-and-correct loop before its screens were described. |
| 4300 | /// |
| 4301 | /// # Why the rank sits here and not on the node |
| 4302 | /// |
| 4303 | /// [`Column`] already settled it: the priority is on the column and not on the |
| 4304 | /// cell's contents. The same node at [`layout::Priority::Essential`] in one |
| 4305 | /// band and [`layout::Priority::Optional`] in another is an ordinary thing to |
| 4306 | /// want, and a rank welded to the node could not say it. |
| 4307 | /// |
| 4308 | /// # Why not a wrapping [`Node`] member |
| 4309 | /// |
| 4310 | /// A `Node::Optional { priority, node }` would have been additive and needed |
| 4311 | /// no type change here, and it was refused. A renderer that has not learned a |
| 4312 | /// member draws it as one muted stand-in line, which is the right failure for |
| 4313 | /// an unknown leaf and exactly the wrong one for a member whose whole job is |
| 4314 | /// deciding what disappears: content wrapped for narrowing would go invisible |
| 4315 | /// in any renderer that had not caught up. Changing the type instead makes |
| 4316 | /// every renderer fail to compile, which is the failure that gets fixed. |
| 4317 | |
| 4318 | |
| 4319 | /// The thing itself. |
| 4320 | pub node: Node, |
| 4321 | /// What it is worth when room runs out. |
| 4322 | /// |
| 4323 | /// [`layout::Priority::Essential`] by default, which never drops. That is |
| 4324 | /// what makes the rank additive at a call site rather than a re-reading of |
| 4325 | /// every screen in the tree. |
| 4326 | pub priority: Priority, |
| 4327 | /// How much of the row it asks for. |
| 4328 | /// |
| 4329 | /// The other half of what a [`Column`] has always been able to say. A rank |
| 4330 | /// answers "what goes first when there is not room for everything"; this |
| 4331 | /// answers "how is the room divided while there is". A row of two panes |
| 4332 | /// where the left takes what it needs and the right absorbs the rest is |
| 4333 | /// the shape [`RegionKind`] used to spell as a variant, and the variants |
| 4334 | /// are gone because this says it (quasicoherent `cf981aaa`, Max's ruling |
| 4335 | /// on `b1d4c5d7`). |
| 4336 | /// |
| 4337 | /// **[`layout::Width::Content`] by default, because that is what a member |
| 4338 | /// of a run already drew.** A run is a flex row whose members have |
| 4339 | /// `min-width: min-content` and no grow, a terminal gives each what it |
| 4340 | /// asks for, and egui allocates what a galley needs. So every member |
| 4341 | /// written before this existed keeps its drawing, and a screen opts into |
| 4342 | /// dividing the room by saying so. |
| 4343 | /// |
| 4344 | /// **Read on a run, ignored in a body.** A [`Run`] is a row and its |
| 4345 | /// members divide a width between them; a [`Slot::body`] is a stack and |
| 4346 | /// its members each get the whole of it, so there is nothing for a width |
| 4347 | /// to divide. The field is on `Ranked` rather than on the run's own list |
| 4348 | /// because the rank is, and splitting one of the two into a second type |
| 4349 | /// would be two spellings of a member. |
| 4350 | pub width: Width, |
| 4351 | |
| 4352 | |
| 4353 | |
| 4354 | /// A node that never drops and takes what it needs. |
| 4355 | |
| 4356 | pub const |
| 4357 | Self |
| 4358 | node, |
| 4359 | priority: Essential, |
| 4360 | width: Content, |
| 4361 | |
| 4362 | |
| 4363 | |
| 4364 | /// A node, and what it is worth. |
| 4365 | |
| 4366 | pub const |
| 4367 | Self |
| 4368 | node, |
| 4369 | priority, |
| 4370 | width: Content, |
| 4371 | |
| 4372 | |
| 4373 | |
| 4374 | /// A node, what it is worth, and how much of the row it asks for. |
| 4375 | /// |
| 4376 | /// Beside [`Self::worth`] rather than replacing it: a width is the thing |
| 4377 | /// most members have no opinion about, and a constructor that demanded one |
| 4378 | /// would make every strip and every band say `Content` to mean "as before". |
| 4379 | |
| 4380 | pub const |
| 4381 | Self |
| 4382 | node, |
| 4383 | priority, |
| 4384 | width, |
| 4385 | |
| 4386 | |
| 4387 | |
| 4388 | /// Whether a region narrowed to this cutoff still shows it. |
| 4389 | /// |
| 4390 | /// [`layout::Column::kept_at`] said for a region member, and it has to be |
| 4391 | /// the same comparison or a screen's table and the band above it would |
| 4392 | /// disappear at different points. A renderer raises the cutoff and asks |
| 4393 | /// this; nothing counts, so inserting a member changes what is emitted |
| 4394 | /// rather than changing which member vanishes. |
| 4395 | |
| 4396 | pub const |
| 4397 | >= |
| 4398 | |
| 4399 | |
| 4400 | |
| 4401 | /// The cutoffs a region narrows through, weakest first. |
| 4402 | /// |
| 4403 | /// The same sequence `makeover-tui`'s table keeps, and it is here rather than |
| 4404 | /// in each renderer so the three cannot drift into disagreeing about the order |
| 4405 | /// things go. [`layout::Priority`] is `#[non_exhaustive]`, so a tier added |
| 4406 | /// upstream has to be added here in its place in the sequence: the cost of |
| 4407 | /// missing one is a member that drops later than it should, which is visible, |
| 4408 | /// rather than a build that stops. |
| 4409 | /// |
| 4410 | /// *When* to raise the cutoff is the renderer's, and deliberately. A browser |
| 4411 | /// answers it with `@media` against CSS pixels, a terminal against cells, egui |
| 4412 | /// against points; those are three different units for one question and the |
| 4413 | /// description holds none of them. |
| 4414 | pub const CUTOFFS: = |
| 4415 | Optional, |
| 4416 | Secondary, |
| 4417 | Essential, |
| 4418 | ]; |
| 4419 | |
| 4420 | |
| 4421 | |
| 4422 | Selfnew |
| 4423 | |
| 4424 | |
| 4425 | |
| 4426 | /// The members that share one row, and what the row does without enough of it. |
| 4427 | /// |
| 4428 | /// The gap it closes is goingson's, and it is worth stating exactly because |
| 4429 | /// the fix looks like a stylesheet bug and is not. `styles.css:702` pinned |
| 4430 | /// every `.page-header` over the pill strip with `position: absolute`, which |
| 4431 | /// is the one construction the description layer cannot audit: the toolbar |
| 4432 | /// left the flow, contributed no width to the row it shared, and so nothing |
| 4433 | /// could collide with it and nothing prevented the collision. At 913 CSS |
| 4434 | /// pixels the help control clipped off the edge; at 700 the search field sat |
| 4435 | /// on top of the "Contacts" pill; at 560 the new-contact button left the |
| 4436 | /// viewport and could not be reached at all. |
| 4437 | /// |
| 4438 | /// The rule that construction broke is the first of the four: every described |
| 4439 | /// member is in flow. What was missing was any way to say the thing the |
| 4440 | /// stylesheet was asserting -- that a [`RegionKind::Band`] and a |
| 4441 | /// [`RegionKind::TabGroup`]'s strip occupy one row. This says it. |
| 4442 | /// |
| 4443 | /// # What it does not carry |
| 4444 | /// |
| 4445 | /// **A size.** Not a minimum, not a breakpoint, not a count. The description |
| 4446 | /// says what the row holds and each renderer derives the minimum in its own |
| 4447 | /// units -- a webview from `min-content` under a container query, a terminal |
| 4448 | /// from cell widths, egui from the galley -- and composes them by the flow. |
| 4449 | /// A derived minimum cannot rot and an authored one always does. The single |
| 4450 | /// hardcoded number in the whole mechanism is makeover-geometry's 44px contact |
| 4451 | /// patch, which is a density fact and already lives there. |
| 4452 | /// |
| 4453 | /// **[`layout::Room`].** Whether the row is tight is measured, per render, by |
| 4454 | /// whoever is rendering. Nothing here authors it. |
| 4455 | |
| 4456 | |
| 4457 | /// What the row does when it is [`layout::Room::Tight`]. |
| 4458 | /// |
| 4459 | /// No `Default`, here or upstream, and that is the point rather than an |
| 4460 | /// omission: a row cannot be described without saying what it does when it |
| 4461 | /// runs out of room. A default would be this crate guessing, and the guess |
| 4462 | /// would be silently wrong on exactly the screens that made the guess |
| 4463 | /// necessary. |
| 4464 | pub fallback: Fallback, |
| 4465 | /// What shares the row, after whatever the region puts in it itself. |
| 4466 | /// |
| 4467 | /// A [`RegionKind::TabGroup`] puts its strip there, generated from its |
| 4468 | /// children's [`labels`](Slot::labels), so a tab group's run is the strip |
| 4469 | /// and then these. Every other region puts nothing there, so its run is |
| 4470 | /// exactly these members in order. |
| 4471 | /// |
| 4472 | /// That asymmetry is what lets the tab-group case be described without a |
| 4473 | /// new [`layout::Region`] member and without the strip becoming a node. |
| 4474 | /// Describing the strip in its own right is a separate question, filed as |
| 4475 | /// makeover-layout `978d24f8`; it is not needed to say what shares a row |
| 4476 | /// with one. |
| 4477 | /// |
| 4478 | /// [`Ranked`] rather than [`Node`], because [`layout::Fallback::Shed`] and |
| 4479 | /// [`layout::Fallback::Menu`] both read a [`layout::Priority`] per member |
| 4480 | /// and the type already carries one. `Wrap` and `Stack` keep every member, |
| 4481 | /// so they ignore it, which is why the rank is not conditional on the |
| 4482 | /// fallback. |
| 4483 | pub members: , |
| 4484 | |
| 4485 | |
| 4486 | |
| 4487 | /// A row that holds nothing yet, and knows what it does when it is tight. |
| 4488 | |
| 4489 | pub const |
| 4490 | Self |
| 4491 | fallback, |
| 4492 | members: Vecnew, |
| 4493 | |
| 4494 | |
| 4495 | |
| 4496 | /// Put a node in the row, saying what it is worth when the row is tight. |
| 4497 | /// |
| 4498 | /// The member goes on the row and not on the region, and that is the whole |
| 4499 | /// of how rule 2 is held now. There is no receiver here that could be |
| 4500 | /// missing a fallback: reaching this method at all means holding a `Run`, |
| 4501 | /// and the only way to hold one is to have said what it does when it runs |
| 4502 | /// out of room. The rule is carried by the type rather than by a runtime |
| 4503 | /// check, which is what lets a row be handed to a function that knows |
| 4504 | /// nothing about where it came from. |
| 4505 | /// |
| 4506 | /// That handing-off is not hypothetical. Of the 70 places in the tree that |
| 4507 | /// put something in a row, 25 sit in a function that did not declare the |
| 4508 | /// row: audiofiles' toolbar declares it once in `body` and then threads it |
| 4509 | /// through `here`, `holding`, `leaving`, `looking` and `frames`, each of |
| 4510 | /// which adds members and none of which has any business restating what the |
| 4511 | /// bar does when it is tight. Under the old shape those 25 were correct |
| 4512 | /// only by convention, and a helper called on a region with no row was a |
| 4513 | /// panic no compiler could see coming. A `Run` parameter makes the same |
| 4514 | /// five functions say in their signatures what they were already relying |
| 4515 | /// on. |
| 4516 | /// |
| 4517 | /// The node goes in the row rather than in a region's |
| 4518 | /// [`body`](Slot::body), and the two are different places: the body stacks |
| 4519 | /// down the region, the run lies across its leading row. goingson's toolbar |
| 4520 | /// belongs here, beside the tab strip it was overlapping. |
| 4521 | |
| 4522 | |
| 4523 | self.members.push; |
| 4524 | self |
| 4525 | |
| 4526 | |
| 4527 | /// Put a node in the row, saying what it is worth and how much it asks for. |
| 4528 | /// |
| 4529 | /// [`Self::beside`] with the second half of what a [`Column`] says. Two |
| 4530 | /// methods rather than one with a width argument, for [`Ranked::sized`]'s |
| 4531 | /// reason: most members have no opinion about the division, and the ones |
| 4532 | /// that do are the two-pane rows the retired [`RegionKind`] variants used |
| 4533 | /// to spell. |
| 4534 | |
| 4535 | |
| 4536 | self.members.push; |
| 4537 | self |
| 4538 | |
| 4539 | |
| 4540 | /// Whether this row keeps every member it was given. |
| 4541 | /// |
| 4542 | /// True for [`Wrap`](layout::Fallback::Wrap) and |
| 4543 | /// [`Stack`](layout::Fallback::Stack), which rearrange; false for |
| 4544 | /// [`Shed`](layout::Fallback::Shed) and [`Menu`](layout::Fallback::Menu), |
| 4545 | /// which read [`Ranked::priority`] and take members out of the row. A |
| 4546 | /// renderer asks this before it bothers computing a cutoff. |
| 4547 | /// |
| 4548 | /// An unrecognised fallback reads as keeping everything. `Fallback` is |
| 4549 | /// `#[non_exhaustive]`, and the safe reading of a member this crate has not |
| 4550 | /// been taught is the one that draws too much rather than the one that |
| 4551 | /// silently removes something. |
| 4552 | |
| 4553 | pub const |
| 4554 | !matches! |
| 4555 | self.fallback, |
| 4556 | Shed | Menu |
| 4557 | |
| 4558 | |
| 4559 | |
| 4560 | /// The members a row narrowed to this cutoff still shows, in order. |
| 4561 | |
| 4562 | |
| 4563 | if self.keeps_every_member |
| 4564 | return self.members.iter.collect; |
| 4565 | |
| 4566 | self.members.iter.filter.collect |
| 4567 | |
| 4568 | |
| 4569 | |
| 4570 | /// A fallback on its own is the row that has said what it does and holds |
| 4571 | /// nothing yet. |
| 4572 | /// |
| 4573 | /// Here so that [`Slot::across`] reads as one thing at both of its call shapes. |
| 4574 | /// A tab strip that only needs the fallback writes `across(Fallback::Menu)` and |
| 4575 | /// a band that has members writes `across(Run::new(..).beside(..))`, and |
| 4576 | /// neither has to know that the other exists. Note which direction this goes: |
| 4577 | /// a fallback becomes a row, and a row is never anything but a row. There is |
| 4578 | /// no conversion that hands back a `Run` without one being named, because that |
| 4579 | /// is the description this vocabulary exists to make unwritable. |
| 4580 | |
| 4581 | |
| 4582 | Selfnew |
| 4583 | |
| 4584 | |
| 4585 | |
| 4586 | /// What a control has to be holding for the region that names it to be out. |
| 4587 | /// |
| 4588 | /// The four shapes the eight measured sites need, and no more: a ticked box, |
| 4589 | /// an unticked one, one value, or one of several. A predicate would cover all |
| 4590 | /// four and could not be read by a renderer that has to draw "not applicable |
| 4591 | /// right now" as words, which is the reading the shape was ruled on. |
| 4592 | /// |
| 4593 | /// The value a control holds is read the way a form reads it: a checkbox is |
| 4594 | /// there by presence, per [`Field::value`], so an unticked box holds nothing |
| 4595 | /// and an empty box holds nothing either. That is one convention across the |
| 4596 | /// three renderers rather than three readings of "empty". |
| 4597 | |
| 4598 | |
| 4599 | /// Anything at all: a ticked box, a filled-in answer. |
| 4600 | Anything, |
| 4601 | /// Nothing: an unticked box, a box the reader has cleared, a select |
| 4602 | /// resting on an empty option. |
| 4603 | Nothing, |
| 4604 | /// Exactly this value. |
| 4605 | Value, |
| 4606 | /// Any one of these values. |
| 4607 | /// |
| 4608 | /// goingson's zone picker is the site: it is out on three of the four |
| 4609 | /// `TzKind` values, and saying that as three regions with one condition |
| 4610 | /// each would put the same body on the screen three times. |
| 4611 | OneOf, |
| 4612 | |
| 4613 | |
| 4614 | /// What brings a region out, said by the region. |
| 4615 | /// |
| 4616 | /// A region names the control it is watching and the value that brings it out, |
| 4617 | /// and a renderer evaluates that without knowing what pressed what. |
| 4618 | /// |
| 4619 | /// # Why it is here rather than on the control |
| 4620 | /// |
| 4621 | /// The host with no cursor decided it. On a terminal a hidden region is not |
| 4622 | /// hidden the way a browser hides one: the honest reading is "this region is |
| 4623 | /// not applicable right now", which is a property of the region, and a |
| 4624 | /// renderer is free to dim it, omit it or explain it. Put the condition on the |
| 4625 | /// control and a terminal asking whether a section applies has to search |
| 4626 | /// outward across every control on the screen to find out. A region gated by |
| 4627 | /// two controls has one home for the condition under this shape and none under |
| 4628 | /// the other. |
| 4629 | /// |
| 4630 | /// Both alternatives were considered and rejected in the same ruling: the |
| 4631 | /// condition on the control, and both ends joined by a back-pointer, which |
| 4632 | /// saves every renderer a search and costs two members to keep in agreement |
| 4633 | /// plus a rule for what happens when they disagree. |
| 4634 | /// |
| 4635 | /// # What it is not |
| 4636 | /// |
| 4637 | /// It is not [`Field::writes`] plus [`Action::replaces`]. That expresses the |
| 4638 | /// same reveal and buys a round trip for it, and on a form the reader is |
| 4639 | /// midway through it re-renders a region holding uncommitted values. |
| 4640 | /// |
| 4641 | /// It is not a press either. A toggle that flips on every press is |
| 4642 | /// unconditional and does not track a value, which is why all six MNW sites |
| 4643 | /// wrap one in a function that reads `.checked` or `.value` first. |
| 4644 | |
| 4645 | |
| 4646 | /// The control being watched, by [`Field::name`]. |
| 4647 | /// |
| 4648 | /// By name rather than by id, for [`Prefill::field`]'s reason: the name is |
| 4649 | /// what the description carries and what a submit sends the value under, |
| 4650 | /// while an id is scoped per form instance by whoever is emitting. |
| 4651 | /// |
| 4652 | /// A name no control on the screen carries leaves the region holding a |
| 4653 | /// condition nothing can satisfy. Every renderer draws that the same way, |
| 4654 | /// as a region that is not applicable, which is a description bug and |
| 4655 | /// reports itself as one. |
| 4656 | pub control: String, |
| 4657 | /// What that control has to hold. |
| 4658 | pub when: Held, |
| 4659 | |
| 4660 | |
| 4661 | |
| 4662 | /// Out while the box is ticked. |
| 4663 | |
| 4664 | |
| 4665 | Self |
| 4666 | control: control.into, |
| 4667 | when: Anything, |
| 4668 | |
| 4669 | |
| 4670 | |
| 4671 | /// Out while the box is not ticked. |
| 4672 | |
| 4673 | |
| 4674 | Self |
| 4675 | control: control.into, |
| 4676 | when: Nothing, |
| 4677 | |
| 4678 | |
| 4679 | |
| 4680 | /// Out while the control holds this value. |
| 4681 | |
| 4682 | |
| 4683 | Self |
| 4684 | control: control.into, |
| 4685 | when: Value, |
| 4686 | |
| 4687 | |
| 4688 | |
| 4689 | /// Out while the control holds any one of these values. |
| 4690 | |
| 4691 | |
| 4692 | control: impl , |
| 4693 | values: impl , |
| 4694 | |
| 4695 | Self |
| 4696 | control: control.into, |
| 4697 | when: OneOf, |
| 4698 | |
| 4699 | |
| 4700 | |
| 4701 | /// Whether a control holding this is holding what the region asked for. |
| 4702 | /// |
| 4703 | /// Here rather than in each renderer, so the three cannot come apart over |
| 4704 | /// what an empty value means. [`None`] and `Some("")` are one answer: |
| 4705 | /// nothing is held. A terminal's unticked checkbox is an empty buffer, a |
| 4706 | /// browser's is an element with no value to read, and both are the same |
| 4707 | /// fact about the form. |
| 4708 | |
| 4709 | |
| 4710 | let held = held.filter; |
| 4711 | match &self.when |
| 4712 | Anything => held.is_some, |
| 4713 | Nothing => held.is_none, |
| 4714 | Value => held == Some, |
| 4715 | OneOf => held.is_some_and, |
| 4716 | |
| 4717 | |
| 4718 | |
| 4719 | |
| 4720 | /// One member of a region that shows its members one at a time. |
| 4721 | /// |
| 4722 | /// # Why the label is here and not on the region |
| 4723 | /// |
| 4724 | /// A label is the name of the control that reveals a frame: the tab's text, the |
| 4725 | /// disclosure's summary. Only a region that discloses or steps through its |
| 4726 | /// members draws one, so only such a region can hold one, and that is what this |
| 4727 | /// type is for. |
| 4728 | /// |
| 4729 | /// It used to sit on [`Slot`], where any region could carry it and only some |
| 4730 | /// would ever render it. That is not a lint waiting to be written. A member is |
| 4731 | /// built before it is placed, so nothing about the member can say whether its |
| 4732 | /// label will be drawn, and the parent that decides may be in another |
| 4733 | /// `declare!` entirely -- which is exactly how MNW's `/use-cases` lost the |
| 4734 | /// titles of nine cards for weeks with a green suite (quasicoherent `2cdc6761`, |
| 4735 | /// wiki `quasi-declare-form` section 22). Max ruled it should not be |
| 4736 | /// representable, so the label moved to the only place that draws it. |
| 4737 | |
| 4738 | |
| 4739 | /// What the control that reveals this frame is called. |
| 4740 | /// |
| 4741 | /// Optional because a carousel's frames are [`Node::Image`] and have |
| 4742 | /// nowhere to put one, which is correct rather than a gap: a photograph has |
| 4743 | /// a caption, not a tab name. A renderer draws a strip when every frame is |
| 4744 | /// named and a prev/next row when they are not, which is |
| 4745 | /// [`Slot::labels`]'s all-or-nothing rule. |
| 4746 | pub label: , |
| 4747 | /// The member itself, ranked as any other member is. |
| 4748 | pub member: Ranked, |
| 4749 | |
| 4750 | |
| 4751 | |
| 4752 | /// A frame with a name, which is what a tab or a disclosure is. |
| 4753 | |
| 4754 | |
| 4755 | Self |
| 4756 | label: Some, |
| 4757 | member: new, |
| 4758 | |
| 4759 | |
| 4760 | |
| 4761 | /// A frame with no name, which is what a carousel's are. |
| 4762 | |
| 4763 | |
| 4764 | Self |
| 4765 | label: None, |
| 4766 | member: new, |
| 4767 | |
| 4768 | |
| 4769 | |
| 4770 | |
| 4771 | /// Which of a selective region's members are up at once. |
| 4772 | /// |
| 4773 | /// [`layout::Showing`]'s two selective members, and only those. The third, |
| 4774 | /// `All`, is the other arm of [`Body`], so a selective body cannot say it: this |
| 4775 | /// exists so `Selective { showing: Showing::All, .. }` is unspellable rather |
| 4776 | /// than merely wrong. |
| 4777 | |
| 4778 | |
| 4779 | /// Exactly one. A carousel, a tab group. |
| 4780 | One, |
| 4781 | /// One, or none. A disclosure, which is closed until it is opened. |
| 4782 | AtMostOne, |
| 4783 | |
| 4784 | |
| 4785 | |
| 4786 | /// The vocabulary's word for this, for a renderer that asks in those terms. |
| 4787 | |
| 4788 | pub const |
| 4789 | match self |
| 4790 | SelfOne => One, |
| 4791 | SelfAtMostOne => AtMostOne, |
| 4792 | |
| 4793 | |
| 4794 | |
| 4795 | |
| 4796 | /// A region's members, and how many of them are up. |
| 4797 | /// |
| 4798 | /// Two arms, because there are two shapes of region and they do not hold the |
| 4799 | /// same thing. A region showing everything holds ranked members. A region |
| 4800 | /// showing one at a time holds frames, each with the name of the control that |
| 4801 | /// reveals it, plus which one is up. |
| 4802 | /// |
| 4803 | /// Collapsing `body`, `showing` and `shown` into one field is what makes the |
| 4804 | /// combinations that never meant anything unspellable: a label on a member |
| 4805 | /// nothing reveals, and a `shown` index on a region that shows everything. Both |
| 4806 | /// were fields that some regions read and others silently ignored. |
| 4807 | |
| 4808 | |
| 4809 | /// Every member, in order. What every region did before `Showing` existed. |
| 4810 | All, |
| 4811 | /// One member at a time, each named for the control that reveals it. |
| 4812 | Selective |
| 4813 | /// Whether showing nothing is a legal resting place. |
| 4814 | picks: Picks, |
| 4815 | /// Which frame the region opens on. `None` under [`Picks::AtMostOne`] |
| 4816 | /// is the closed state; read it through [`Slot::current`], which is |
| 4817 | /// where an index past the end is dealt with. |
| 4818 | shown: , |
| 4819 | /// The frames, in order. |
| 4820 | frames: , |
| 4821 | , |
| 4822 | |
| 4823 | |
| 4824 | |
| 4825 | /// How many members there are, whichever shape this is. |
| 4826 | |
| 4827 | |
| 4828 | match self |
| 4829 | SelfAll => members.len, |
| 4830 | SelfSelective => frames.len, |
| 4831 | |
| 4832 | |
| 4833 | |
| 4834 | /// Whether the region draws nothing at all. |
| 4835 | |
| 4836 | |
| 4837 | self.len == 0 |
| 4838 | |
| 4839 | |
| 4840 | /// Every member in order, with its frame taken off if it had one. |
| 4841 | /// |
| 4842 | /// What a renderer walks when it is drawing content rather than chrome. The |
| 4843 | /// chrome is the caller's other question, answered by [`Slot::labels`] and |
| 4844 | /// [`Slot::current`]. |
| 4845 | + '_ |
| 4846 | // Two iterator types, so they are boxed into one. A region's member |
| 4847 | // count is small and this is not on any hot path: the residual seam |
| 4848 | // exists so that a served screen walks no `Node` tree at all. |
| 4849 | let iter: = match self |
| 4850 | SelfAll => Boxnew, |
| 4851 | SelfSelective => Boxnew, |
| 4852 | ; |
| 4853 | iter |
| 4854 | |
| 4855 | |
| 4856 | /// The first member, whichever shape the body is. |
| 4857 | |
| 4858 | |
| 4859 | self.get |
| 4860 | |
| 4861 | |
| 4862 | /// One member by position, whichever shape the body is. |
| 4863 | |
| 4864 | |
| 4865 | match self |
| 4866 | SelfAll => members.get, |
| 4867 | SelfSelective => frames.get.map, |
| 4868 | |
| 4869 | |
| 4870 | |
| 4871 | /// Every member in order. The same as [`members`](Self::members), under the |
| 4872 | /// name a caller reaches for when it is walking a list. |
| 4873 | + '_ |
| 4874 | self.members |
| 4875 | |
| 4876 | |
| 4877 | /// Add one member, keeping whichever shape the body already has. |
| 4878 | |
| 4879 | match self |
| 4880 | SelfAll => members.push, |
| 4881 | SelfSelective => frames.push |
| 4882 | label: None, |
| 4883 | member, |
| 4884 | , |
| 4885 | |
| 4886 | |
| 4887 | |
| 4888 | /// Add several, in order. |
| 4889 | |
| 4890 | for member in members |
| 4891 | self.push; |
| 4892 | |
| 4893 | |
| 4894 | |
| 4895 | /// Every member, mutably, under the name a caller walking a list reaches |
| 4896 | /// for. |
| 4897 | + '_ |
| 4898 | self.members_mut |
| 4899 | |
| 4900 | |
| 4901 | /// Drop every member, keeping the shape. |
| 4902 | /// |
| 4903 | /// A selective body that is emptied stays selective: what it shows one at a |
| 4904 | /// time is a fact about the region, not about how many members it happens to |
| 4905 | /// hold right now. |
| 4906 | |
| 4907 | match self |
| 4908 | SelfAll => members.clear, |
| 4909 | SelfSelective => frames.clear, |
| 4910 | |
| 4911 | |
| 4912 | |
| 4913 | /// Every member, mutably. |
| 4914 | + '_ |
| 4915 | let iter: = match self |
| 4916 | SelfAll => Boxnew, |
| 4917 | SelfSelective => |
| 4918 | Boxnew |
| 4919 | |
| 4920 | ; |
| 4921 | iter |
| 4922 | |
| 4923 | |
| 4924 | /// The vocabulary's word for how much of this is up at once. |
| 4925 | |
| 4926 | pub const |
| 4927 | match self |
| 4928 | SelfAll => All, |
| 4929 | SelfSelective => picks.showing, |
| 4930 | |
| 4931 | |
| 4932 | |
| 4933 | /// Which member the region opens on, before clamping. |
| 4934 | |
| 4935 | pub const |
| 4936 | match self |
| 4937 | SelfAll => None, |
| 4938 | SelfSelective => *shown, |
| 4939 | |
| 4940 | |
| 4941 | |
| 4942 | /// Turn a body that shows everything into one that shows one at a time. |
| 4943 | /// |
| 4944 | /// Members it already holds become unnamed frames, which is a carousel: a |
| 4945 | /// name arrives with [`Slot::frame`] and nowhere else, so nothing here can |
| 4946 | /// invent one or drop one. |
| 4947 | |
| 4948 | match self |
| 4949 | SelfAll => |
| 4950 | *self = SelfSelective |
| 4951 | picks, |
| 4952 | shown, |
| 4953 | frames: take |
| 4954 | .into_iter |
| 4955 | .map |
| 4956 | label: None, |
| 4957 | member, |
| 4958 | |
| 4959 | .collect, |
| 4960 | ; |
| 4961 | |
| 4962 | SelfSelective |
| 4963 | picks: held, |
| 4964 | shown: at, |
| 4965 | .. |
| 4966 | => |
| 4967 | *held = picks; |
| 4968 | *at = shown; |
| 4969 | |
| 4970 | |
| 4971 | |
| 4972 | |
| 4973 | |
| 4974 | |
| 4975 | |
| 4976 | SelfAll |
| 4977 | |
| 4978 | |
| 4979 | |
| 4980 | /// A named region, and the thing a fragment is aimed at. |
| 4981 | /// |
| 4982 | /// The name is what decision 7 needs and [`layout::Region`] deliberately does |
| 4983 | /// not have: two panes in a split are both `Pane`, so the kind cannot be an |
| 4984 | /// address. A webview maps the id onto `hx-target`; egui and the terminal |
| 4985 | /// ignore it and redraw, which costs them nothing because they were redrawing |
| 4986 | /// anyway. |
| 4987 | |
| 4988 | |
| 4989 | /// The address. Unique within a screen, and stable across responses, or a |
| 4990 | /// fragment lands nowhere. |
| 4991 | pub id: String, |
| 4992 | /// Which region it is. |
| 4993 | pub kind: RegionKind, |
| 4994 | /// Whether the region's own content is here or on its way. |
| 4995 | /// |
| 4996 | /// The loading axis, and only that. Emptiness is *not* said here, which |
| 4997 | /// looks like the obvious place for it and is not: a column with a heading |
| 4998 | /// and no rows is a region that has content — the heading — and a list that |
| 4999 | /// has none. Marking the region empty would hide the heading with it. See |
| 5000 | /// [`Node::StandIn`]. |
| 5001 | pub readiness: Readiness, |
| 5002 | /// What is in it. |
| 5003 | /// |
| 5004 | /// Blocks, regions included, which is the nesting that was always accepted: |
| 5005 | /// a region inside a region is a nested rect on every host. Leaves are |
| 5006 | /// admitted too, and deliberately -- a fact under a heading is a |
| 5007 | /// [`Node::Text`] straight in a pane, and it is the commonest thing in the |
| 5008 | /// tree. |
| 5009 | /// |
| 5010 | /// # Why there is no bound here |
| 5011 | /// |
| 5012 | /// [`Cell::part`] and [`Row::part`] assert that what they are handed is a |
| 5013 | /// leaf, and this does not, which looks like an oversight and is the model |
| 5014 | /// working. The ladder forbids reaching *up*: a run may not hold a block, |
| 5015 | /// because a run has to be drawable on one wrapped line. A block holding a |
| 5016 | /// leaf is going down, and going down is what containment is for. There is |
| 5017 | /// no upward violation for [`with`](Self::with) to catch, so an assertion |
| 5018 | /// here would be a runtime check that can never fire. |
| 5019 | /// |
| 5020 | /// A region whose whole content is one badge is the case that made this |
| 5021 | /// look like a question. It is describable, and it should be: a status pane |
| 5022 | /// is a real screen. Whether it is a *good* screen is a judgement about |
| 5023 | /// that screen rather than a property of the vocabulary, and the bound is |
| 5024 | /// not the place to hold opinions about taste. |
| 5025 | /// |
| 5026 | /// # Why the members are [`Ranked`] and not bare nodes |
| 5027 | /// |
| 5028 | /// Each one carries what it is worth when the region runs out of room, so |
| 5029 | /// a renderer narrows a region the way it already narrows a table: raise a |
| 5030 | /// cutoff over a declared order. `Ranked`'s own docs carry the argument. |
| 5031 | pub body: Body, |
| 5032 | /// How many of [`body`](Self::body) are visible at once. |
| 5033 | /// |
| 5034 | /// [`layout::Showing::All`] by default, so a description written against |
| 5035 | /// the previous version says the same thing. |
| 5036 | /// |
| 5037 | /// This is the kind. The two fields below are the current answer and the |
| 5038 | /// per-child name, and they are here rather than in `makeover-layout` for |
| 5039 | /// the reason a [`Field`]'s value is: a layer that defers every address |
| 5040 | /// does not hold what is picked either. |
| 5041 | /// |
| 5042 | /// # A description never names client-only state |
| 5043 | /// |
| 5044 | /// A description says what a region *is*: these frames are peers, show |
| 5045 | /// one. State that lives only in the client and never reaches a handler is |
| 5046 | /// not part of that. Each renderer decides it, exactly as it already |
| 5047 | /// decides scroll position, focus and the duration of an undo window. |
| 5048 | /// |
| 5049 | /// The consequence is worth stating plainly rather than discovering later: |
| 5050 | /// **a screen has no way to say a preference should persist.** That is a |
| 5051 | /// deliberate limit. A description that wanted to say "remember which tab |
| 5052 | /// the reader was on across visits" would be naming storage, and storage is |
| 5053 | /// the host's. |
| 5054 | /// |
| 5055 | /// Three behaviours settle as renderer policy under this rule, recorded |
| 5056 | /// here because a reader meets the question at this field: |
| 5057 | /// |
| 5058 | /// - **Section tabs switched without a round trip.** This field already |
| 5059 | /// answers it: the description says the frames are peers and one is up, |
| 5060 | /// and the renderer toggles and rewrites the URL. MNW defines |
| 5061 | /// `switchSectionTab` verbatim in three bundles (`static/page-item-2.js`, |
| 5062 | /// `static/page-library-downloads.js`, `static/page-project.js`) and |
| 5063 | /// reaches a fourth site through the delegated handler in |
| 5064 | /// `static/actions-pages.js`. Three definitions, four call sites, and none |
| 5065 | /// of them is something a description should have been carrying. |
| 5066 | /// - **A persisted view preference.** The renderer decides whether it |
| 5067 | /// persists and where; nothing in the description mentions storage. The |
| 5068 | /// persist-across-swap behaviour has **one** implementation in MNW, |
| 5069 | /// `static/page-discover.js:176-178`, not the two an earlier count |
| 5070 | /// claimed. The count is recorded because it is what made a vocabulary |
| 5071 | /// addition look worth buying, and at one site it is not. |
| 5072 | /// - **Loading a region's contents on first reveal.** [`Readiness`] says |
| 5073 | /// what a region shows while it waits; what makes it *start* is the |
| 5074 | /// renderer's. MNW does it twice, license text on a `<details>` toggle |
| 5075 | /// and a video `src` on first `play`, each guarding with a `loaded` flag. |
| 5076 | /// |
| 5077 | /// One fragility worth knowing before a conversion, since a converter will |
| 5078 | /// arrive here first: MNW's `static/page-project-2.js` binds its |
| 5079 | /// `.view-btn` handlers directly at script load rather than by delegation. |
| 5080 | /// The container it binds into is never an htmx target today, so that is |
| 5081 | /// correct as written. If it ever becomes one, the handlers and the view |
| 5082 | /// state both break silently. |
| 5083 | /// |
| 5084 | /// [`Readiness`]: layout::Readiness |
| 5085 | /// What this region is called in its own right. |
| 5086 | /// |
| 5087 | /// Not [`label`](Self::label), which is the name a *parent* reads off this |
| 5088 | /// region when it is showing one child at a time -- the tab's name, |
| 5089 | /// gathered by [`labels`](Self::labels). Setting that on a tab strip |
| 5090 | /// itself is read by nothing, which is how MNW's library strip lost its |
| 5091 | /// `aria-label="Library sections"` when it converted and announced as an |
| 5092 | /// unnamed tab list. |
| 5093 | /// |
| 5094 | /// Counted before it was added, across MNW, goingson and Balanced |
| 5095 | /// Breakfast: 199 `aria-label` sites, and the ones on containers -- |
| 5096 | /// `table`, `nav`, `ul`, `section`, `aside` -- are this. "Tag breadcrumbs", |
| 5097 | /// "Selected tags", "Waitlist entries", "Sales per month". The ones on |
| 5098 | /// controls are a different thing and need nothing new: an icon-only button |
| 5099 | /// already has [`Act::label`], and a renderer drawing it as a glyph is the |
| 5100 | /// renderer's choice about how to spell a name it was given. |
| 5101 | /// |
| 5102 | /// Every renderer has somewhere to put it, which is what separates it from |
| 5103 | /// [`Act::hint`]: a webview writes `aria-label`, a terminal a rule caption |
| 5104 | /// or a heading line, egui a frame's title. None of them has to invent |
| 5105 | /// copy, and none of them may draw it *instead of* a heading the body |
| 5106 | /// already carries -- a region with a `Heading` in it is named twice on |
| 5107 | /// purpose, once for the eye and once for the reader that cannot see it. |
| 5108 | /// |
| 5109 | /// `None` is a region with no name of its own, which is most of them and is |
| 5110 | /// what every region did before this field existed. |
| 5111 | pub name: , |
| 5112 | /// The call that fills this region, when the region's content is not here |
| 5113 | /// yet. |
| 5114 | /// |
| 5115 | /// The region half of [`layout::Awaiting`]: a screen that is mostly local |
| 5116 | /// reads plus one slow part says so here instead of being hand-split into |
| 5117 | /// a second route, which is what MNW's user dashboard does with its payout |
| 5118 | /// summary because that one tab calls a payment provider and the rest of |
| 5119 | /// it reads the database. |
| 5120 | /// |
| 5121 | /// [`readiness`](Self::readiness) is [`Pending`](layout::Readiness::Pending) |
| 5122 | /// while this is set, and [`Screen::replace`] clears it when the answer |
| 5123 | /// lands, so a retained-screen host cannot ask twice for one region. |
| 5124 | /// |
| 5125 | /// The wait's size, if it has one, is on the action rather than here. A |
| 5126 | /// region is fed by a call and the call is what knows. |
| 5127 | /// |
| 5128 | /// Boxed, which is the one place in this file that is: [`Node::Region`] |
| 5129 | /// holds a [`Slot`] by value, so every node in every tree would carry an |
| 5130 | /// [`Action`]'s width for a field almost no region sets. Reach for |
| 5131 | /// [`fed_by`](Self::fed_by) and [`awaiting`](Self::awaiting) rather than the |
| 5132 | /// field, and the box is not something a caller has to think about. |
| 5133 | pub fed_by: , |
| 5134 | /// Whether this region's contents change without the user. |
| 5135 | /// |
| 5136 | /// The other word beside [`layout::Awaiting`], and the two divide by |
| 5137 | /// whether the waiting ends: awaiting resolves once, in finite time, and |
| 5138 | /// this never resolves. A sync panel whose state moves when an OAuth |
| 5139 | /// callback lands in another process is the case, and no measure of |
| 5140 | /// progress can carry it — the transition that prompted the question is |
| 5141 | /// `Authenticating -> NeedsEncryption`, which is not an amount of |
| 5142 | /// anything. |
| 5143 | /// |
| 5144 | /// A bool rather than a rate. The description says the contents move; how |
| 5145 | /// often to look is the renderer's, picked once per host rather than per |
| 5146 | /// screen, which is the whole of what this buys. MNW hand-writes |
| 5147 | /// `hx-trigger="every 10s"` in two templates and audiofiles re-reads every |
| 5148 | /// frame, and nothing makes those two comparable today. |
| 5149 | /// |
| 5150 | /// Orthogonal to [`readiness`](Self::readiness). A live region can be |
| 5151 | /// [`Ready`](layout::Readiness::Ready), [`Pending`](layout::Readiness::Pending) |
| 5152 | /// or [`Failed`](layout::Readiness::Failed); those four stay mutually |
| 5153 | /// exclusive states about whether there is content yet, and liveness is a |
| 5154 | /// fact about the content after it arrives. |
| 5155 | /// |
| 5156 | /// What a host does with it depends on where the content comes from. A |
| 5157 | /// region that also names a [`fed_by`](Self::fed_by) is re-asked on the |
| 5158 | /// renderer's cadence — see [`Screen::refreshes`], which is the walk that |
| 5159 | /// finds them and the reason [`Screen::replace`] leaves a live region's |
| 5160 | /// feed in place instead of clearing it. A live region with no call reads |
| 5161 | /// something the host already holds, and the cadence is a repaint. |
| 5162 | pub live: bool, |
| 5163 | /// What brings this region out, when it is not simply out. |
| 5164 | /// |
| 5165 | /// The region names a control and the value that reveals it, and every |
| 5166 | /// renderer answers it from what it already holds: no request, no |
| 5167 | /// fragment, no re-render of a form the reader is midway through. See |
| 5168 | /// [`Reveal`] for why the condition lives here and not on the control. |
| 5169 | /// |
| 5170 | /// `None` is a region that is always out, so a description written against |
| 5171 | /// the previous version says the same thing. |
| 5172 | /// |
| 5173 | /// Boxed, for [`fed_by`](Self::fed_by)'s reason and measured the same way: |
| 5174 | /// [`Node::Region`] holds a [`Slot`] by value, so an unboxed condition puts |
| 5175 | /// a `String` and a `Vec` on every node in every tree for a field a handful |
| 5176 | /// of regions in a screen set. |
| 5177 | pub revealed_by: , |
| 5178 | /// Whether this region leads with a row that things share, and what that |
| 5179 | /// row does when it is [`layout::Room::Tight`]. |
| 5180 | /// |
| 5181 | /// `None` says it does not and is why the field is additive at a call site |
| 5182 | /// rather than a re-reading of every screen in the tree. It is not a |
| 5183 | /// defaulted fallback and must not be read as one: a region with no run |
| 5184 | /// has no row to fall back, so there is nothing for it to have failed to |
| 5185 | /// say. |
| 5186 | /// |
| 5187 | /// Set it with [`across`](Self::across), which takes the row itself. The |
| 5188 | /// only way to make a [`Run`] is [`Run::new`], and that takes the |
| 5189 | /// fallback, so there is no path to a row here that failed to say what it |
| 5190 | /// does when it is tight. |
| 5191 | /// |
| 5192 | /// Boxed, for [`fed_by`](Self::fed_by)'s reason and measured the same way: |
| 5193 | /// [`Node::Region`] holds a [`Slot`] by value, so an inline `Run` puts a |
| 5194 | /// `Vec`'s width on every node in every tree for a field one region in a |
| 5195 | /// screen sets. |
| 5196 | pub run: , |
| 5197 | /// What this region asks when the questions inside it move. |
| 5198 | /// |
| 5199 | /// The region names the route, the wait and the floor; the values it sends |
| 5200 | /// are the ones its own |
| 5201 | /// [`questions`](Self::questions) are holding; and the answer lands where |
| 5202 | /// [`Action::replaces`] points, which is usually a region inside this one. |
| 5203 | /// MNW's fee calculator is the site: five dials and a results panel that |
| 5204 | /// recomputes when any of them moves. |
| 5205 | /// |
| 5206 | /// # Why the region and not one of the dials |
| 5207 | /// |
| 5208 | /// The cheaper shape was a [`Consult::sends`] on one field naming its |
| 5209 | /// peers, and it was rejected for what it says rather than for what it |
| 5210 | /// costs: it nominates one of five equals as the owner of the recompute, |
| 5211 | /// and whichever is picked reads as arbitrary to the next person to open |
| 5212 | /// the file. It is also asymmetric on a host with focus, where the caret |
| 5213 | /// would have to be in the owning box for anything to happen. |
| 5214 | /// |
| 5215 | /// # What is gathered |
| 5216 | /// |
| 5217 | /// Every question this region contains, at any depth, nested regions |
| 5218 | /// included — [`questions`](Self::questions) is the walk, so the three |
| 5219 | /// renderers cannot disagree about what "inside" means. Beside that, |
| 5220 | /// whatever [`Consult::sends`] names, which is how a dial outside the panel |
| 5221 | /// joins in. |
| 5222 | /// |
| 5223 | /// Several, for [`Field::consults`]' reason: two frames recomputing from |
| 5224 | /// one set of dials at two rates are two questions, and one is not a |
| 5225 | /// special case of the other. Empty for nearly every region, which is every |
| 5226 | /// region written before this field existed. |
| 5227 | pub consults: , |
| 5228 | /// The children are answers to one question, and the reader may add and |
| 5229 | /// take them away. |
| 5230 | /// |
| 5231 | /// A region member, not a rework of [`Repeat`]. See [`Repeating`] for what |
| 5232 | /// separates the two -- briefly, `Repeat` is one *field* answered N times |
| 5233 | /// and this is one |
| 5234 | /// *group* answered N times, which is the shape nothing could say. |
| 5235 | /// |
| 5236 | /// Boxed for [`fed_by`](Self::fed_by)'s reason: `Node::Region` holds a |
| 5237 | /// `Slot` by value, so an unboxed member is paid for by every region in |
| 5238 | /// every tree for a field almost none of them set. |
| 5239 | pub repeating: , |
| 5240 | /// What taking *this* slot away calls, when this region is one. |
| 5241 | /// |
| 5242 | /// On the child rather than on the parent, which is the one place this |
| 5243 | /// diverges from the sketch in the task. A parent-level remove would have |
| 5244 | /// to reach a particular slot, and the two ways to do that are both worse: |
| 5245 | /// a renderer building `.../{at}/remove` is route construction in a |
| 5246 | /// renderer, and one route plus an index under an agreed name is a third |
| 5247 | /// word beside [`Node::SELECTED`] and [`Node::TICKED`] for one screen's |
| 5248 | /// benefit. A child carrying the action that concerns it is |
| 5249 | /// [`fed_by`](Self::fed_by)'s shape, already here and already understood. |
| 5250 | /// |
| 5251 | /// What the parent's [`Repeating::least`] adds is the *enforcement*: a |
| 5252 | /// renderer disables this once the floor is reached, so "at least one |
| 5253 | /// condition" stops being an app disabling its own button. |
| 5254 | /// |
| 5255 | /// Boxed with `repeating` and for its reason. |
| 5256 | pub removes: , |
| 5257 | |
| 5258 | |
| 5259 | /// What a region says when its children are answers to one question. |
| 5260 | /// |
| 5261 | /// [`Repeat`] describes a repeating *field*: an [`Instance`] is one value and |
| 5262 | /// one error, so a slot is one answer to one question. Nothing described a |
| 5263 | /// repeating *group*, and audiofiles' rule editor has two of them on one |
| 5264 | /// screen -- a condition is three questions that only mean anything together, |
| 5265 | /// and an action is two. |
| 5266 | /// |
| 5267 | /// # Why this and not a wider `Repeat` |
| 5268 | /// |
| 5269 | /// The regions already exist: 23 [`RegionKind::Group`] sites in the tree when |
| 5270 | /// this was measured, so a region member attaches to something with consumers, |
| 5271 | /// where widening `Repeat` would have been a redesign of vocabulary that has |
| 5272 | /// none. |
| 5273 | /// |
| 5274 | /// # What it deliberately does not carry |
| 5275 | /// |
| 5276 | /// A per-slot error, which is [`Instance::error`]'s job for a repeating field. |
| 5277 | /// A group's slots can be wrong in ways a string on the group cannot say, and |
| 5278 | /// no screen had needed one: **that was the reopening condition, and it |
| 5279 | /// fired.** MNW's version-upload queue wanted a status and an error against one |
| 5280 | /// slot, so [`Repeat`] grew [`Instance::parts`] and [`Progress`]. |
| 5281 | /// |
| 5282 | /// This member still stands, and the line between the two did not move. A |
| 5283 | /// repeating *field* holds its slots in the renderer's own view until one |
| 5284 | /// submit carries all of them, which is why they need a wire naming and a |
| 5285 | /// per-slot status at all. These regions each carry their own fields and their |
| 5286 | /// own writes, so a slot is already addressed by the routes inside it and there |
| 5287 | /// is nothing to take apart on the way back. The queue was the first shape and |
| 5288 | /// it is a field, not a group. |
| 5289 | /// |
| 5290 | /// # The wire |
| 5291 | /// |
| 5292 | /// Nothing here. A repeating field names its answers `name[0]`, `name[1]`, |
| 5293 | /// because one submit carries all of them; these regions each carry their own |
| 5294 | /// fields with their own writes, so every slot is already addressed by the |
| 5295 | /// routes inside it. There is no set to take apart on the way back and so no |
| 5296 | /// naming convention to agree. |
| 5297 | |
| 5298 | |
| 5299 | /// What one of them is called, singular. "Condition", "Action". |
| 5300 | /// |
| 5301 | /// The renderer numbers them from it -- "Condition 1", "Condition 2" -- |
| 5302 | /// which is why this is the singular noun and not a heading. A description |
| 5303 | /// that wrote the numbers itself would be numbering for a screen it cannot |
| 5304 | /// see, and would go stale the moment a slot was removed from the middle. |
| 5305 | pub one: String, |
| 5306 | /// The fewest slots the reader may leave standing. |
| 5307 | /// |
| 5308 | /// [`Repeat::least`]'s meaning, one level up. `1` is audiofiles' rule |
| 5309 | /// editor, whose conditions cannot go to zero; `0` is the ordinary answer. |
| 5310 | /// |
| 5311 | /// **This is the enforcement the member exists for.** The editor said it by |
| 5312 | /// disabling the last Remove itself, which is a rule living in an app where |
| 5313 | /// every renderer needs it. |
| 5314 | pub least: usize, |
| 5315 | /// The most the reader may add, if there is a ceiling. `None` is none. |
| 5316 | pub most: , |
| 5317 | /// The control that adds a slot. |
| 5318 | /// |
| 5319 | /// A whole [`Act`] rather than a label, because adding a slot here is a |
| 5320 | /// route: the group's state is the app's, unlike a repeating field's, whose |
| 5321 | /// slots live in the renderer's own view until they are submitted. |
| 5322 | pub add: Act, |
| 5323 | |
| 5324 | |
| 5325 | |
| 5326 | /// A repeating group with no floor and no ceiling. |
| 5327 | |
| 5328 | |
| 5329 | Self |
| 5330 | one: one.into, |
| 5331 | least: 0, |
| 5332 | most: None, |
| 5333 | add, |
| 5334 | |
| 5335 | |
| 5336 | |
| 5337 | /// The same, with a floor on how many may be left standing. |
| 5338 | |
| 5339 | pub const |
| 5340 | self.least = least; |
| 5341 | self |
| 5342 | |
| 5343 | |
| 5344 | /// The same, with a ceiling on how many may be added. |
| 5345 | |
| 5346 | pub const |
| 5347 | self.most = Some; |
| 5348 | self |
| 5349 | |
| 5350 | |
| 5351 | /// Whether a group holding this many may lose one. |
| 5352 | /// |
| 5353 | /// Asked by every renderer before it draws a slot's remove control, so the |
| 5354 | /// three cannot disagree about what the floor means. |
| 5355 | |
| 5356 | pub const |
| 5357 | standing > self.least |
| 5358 | |
| 5359 | |
| 5360 | /// Whether a group holding this many may gain one. |
| 5361 | |
| 5362 | pub const |
| 5363 | match self.most |
| 5364 | Some => standing < most, |
| 5365 | None => true, |
| 5366 | |
| 5367 | |
| 5368 | |
| 5369 | |
| 5370 | |
| 5371 | /// An empty region under this address. |
| 5372 | |
| 5373 | Self |
| 5374 | id: id.into, |
| 5375 | kind, |
| 5376 | readiness: Ready, |
| 5377 | body: All, |
| 5378 | name: None, |
| 5379 | fed_by: None, |
| 5380 | live: false, |
| 5381 | revealed_by: None, |
| 5382 | run: None, |
| 5383 | consults: Vecnew, |
| 5384 | repeating: None, |
| 5385 | removes: None, |
| 5386 | |
| 5387 | |
| 5388 | |
| 5389 | /// A place the app fills itself, and owes every host a fill for. |
| 5390 | /// |
| 5391 | /// See [`RegionKind::Handover`]. Use [`ceded`](Self::ceded) instead when |
| 5392 | /// no host is owed one. |
| 5393 | |
| 5394 | Selfnew |
| 5395 | |
| 5396 | |
| 5397 | /// A place the app fills itself, that no host is owed a fill for. |
| 5398 | /// |
| 5399 | /// See [`RegionKind::Ceded`]. A chart, a waveform: a renderer with nothing |
| 5400 | /// to put here draws nothing and is right to. |
| 5401 | |
| 5402 | Selfnew |
| 5403 | |
| 5404 | |
| 5405 | /// Things that belong together. |
| 5406 | /// |
| 5407 | /// Named rather than left to [`new`](Self::new) because this is the member |
| 5408 | /// a run of siblings under a heading should reach for, and the one it |
| 5409 | /// reached for instead -- [`RegionKind::Pane`] -- is what `Slot::new` makes |
| 5410 | /// easy. The heading goes in the body, not here. |
| 5411 | |
| 5412 | Selfnew |
| 5413 | |
| 5414 | |
| 5415 | /// A named assembly, whose body says what it is made of. |
| 5416 | /// |
| 5417 | /// The body is not optional in spirit, though nothing here enforces it: a |
| 5418 | /// widget with an empty body is a [`handover`](Self::handover) that has |
| 5419 | /// mislaid its host fill, and a renderer that does not know the name will |
| 5420 | /// draw nothing at all. Assemble it out of members the description already |
| 5421 | /// has, the way [`layout::Region::Widget`] describes. |
| 5422 | |
| 5423 | Selfnew |
| 5424 | |
| 5425 | |
| 5426 | /// Say what brings this region out. |
| 5427 | /// |
| 5428 | /// The condition is the region's own, so a form with several conditional |
| 5429 | /// sections reads as a list of regions each stating its precondition |
| 5430 | /// rather than as controls reaching across the form. |
| 5431 | /// |
| 5432 | /// Calling it twice replaces the condition, which is the reading every |
| 5433 | /// builder here has: the second call is a correction. |
| 5434 | /// |
| 5435 | /// ``` |
| 5436 | /// use quasi_router::{RegionKind, Reveal, Slot}; |
| 5437 | /// |
| 5438 | /// let settings = Slot::group("pwyw-settings").revealed_by(Reveal::ticked("pwyw")); |
| 5439 | /// let custom = Slot::new("dash-custom-license", RegionKind::Pane) |
| 5440 | /// .revealed_by(Reveal::holding("license", "custom")); |
| 5441 | /// |
| 5442 | /// assert!(settings.revealed(Some("on"))); |
| 5443 | /// assert!(!settings.revealed(None)); |
| 5444 | /// assert!(custom.revealed(Some("custom"))); |
| 5445 | /// assert!(!custom.revealed(Some("all-rights-reserved"))); |
| 5446 | /// ``` |
| 5447 | |
| 5448 | |
| 5449 | self.revealed_by = Some; |
| 5450 | self |
| 5451 | |
| 5452 | |
| 5453 | /// Whether this region is out, given what its control is holding. |
| 5454 | /// |
| 5455 | /// `true` for a region that named no condition, which is nearly all of |
| 5456 | /// them: a region says when it is *not* applicable, and saying nothing is |
| 5457 | /// saying it always is. |
| 5458 | /// |
| 5459 | /// What a renderer does with `false` is the renderer's. A browser hides the |
| 5460 | /// element, and a host with no cursor may dim the region, leave it out, or |
| 5461 | /// say in words that it does not apply right now — the reading the shape |
| 5462 | /// was ruled for. |
| 5463 | |
| 5464 | |
| 5465 | self.revealed_by |
| 5466 | .as_ref |
| 5467 | .is_none_or |
| 5468 | |
| 5469 | |
| 5470 | /// The name of the control this region is watching, if it watches one. |
| 5471 | |
| 5472 | |
| 5473 | self.revealed_by |
| 5474 | .as_ref |
| 5475 | .map |
| 5476 | |
| 5477 | |
| 5478 | /// Add a node, chaining. |
| 5479 | /// |
| 5480 | /// At [`layout::Priority::Essential`], so it never drops. The signature is |
| 5481 | /// unchanged from before [`Ranked`] existed and so is what it means, which |
| 5482 | /// is why the whole tree kept building. |
| 5483 | |
| 5484 | |
| 5485 | match &mut self.body |
| 5486 | All => members.push, |
| 5487 | // A member added to a selective region is a frame with no name, |
| 5488 | // which is a carousel's. A name arrives through [`Self::frame`] and |
| 5489 | // nowhere else. |
| 5490 | Selective => frames.push, |
| 5491 | |
| 5492 | self |
| 5493 | |
| 5494 | |
| 5495 | /// Add every node a shape answered with, chaining. |
| 5496 | /// |
| 5497 | /// [`with`](Self::with)'s plural, and the whole of what `include each` |
| 5498 | /// emits. A shape answering `Vec<Node>` has no region of its own -- what it |
| 5499 | /// returns is a run of members and not one node -- so before this a caller |
| 5500 | /// spread it by hand, with a loop whose body was `include node;`. |
| 5501 | /// |
| 5502 | /// That loop is why this exists. It reads as a loop over data and is a |
| 5503 | /// splice, and a staged shape cannot make sense of it: the `include` names |
| 5504 | /// a binding rather than a shape, so there is no twin to retarget to and no |
| 5505 | /// filler to call. Said as one `include each`, the caller splices a |
| 5506 | /// reference the way every other `include` does. |
| 5507 | |
| 5508 | |
| 5509 | for node in nodes |
| 5510 | self = self.with; |
| 5511 | |
| 5512 | self |
| 5513 | |
| 5514 | |
| 5515 | /// A named frame: a tab, or a disclosure's one panel. |
| 5516 | /// |
| 5517 | /// The only way to write a label, and it places the member at the same |
| 5518 | /// time. That is the whole of the fix in quasicoherent `2cdc6761`: a name |
| 5519 | /// cannot be written on something that will not draw it, because the name |
| 5520 | /// and the thing that draws it are one call. |
| 5521 | /// |
| 5522 | /// Naming a frame on a region that shows everything makes it show one at a |
| 5523 | /// time, since a named frame is a claim that something reveals it. Say |
| 5524 | /// [`showing_one`](Self::showing_one) or |
| 5525 | /// [`showing_at_most_one`](Self::showing_at_most_one) first to choose |
| 5526 | /// which; unstated, a disclosure is the safer default because it has a |
| 5527 | /// resting state that shows nothing. |
| 5528 | |
| 5529 | |
| 5530 | if matches! |
| 5531 | self.body.select; |
| 5532 | |
| 5533 | match &mut self.body |
| 5534 | Selective => frames.push, |
| 5535 | All => unreachable!, |
| 5536 | |
| 5537 | self |
| 5538 | |
| 5539 | |
| 5540 | /// Lead with a row that things share. |
| 5541 | /// |
| 5542 | /// Takes the row itself, so the fallback arrives with it: a |
| 5543 | /// [`layout::Fallback`] converts, which is the empty row this reads as, and |
| 5544 | /// a [`Run`] built up with [`Run::beside`] is the row that already holds |
| 5545 | /// something. Either way the thing handed over has said what it does when |
| 5546 | /// it is tight, because [`Run::new`] is the only way to make one and it |
| 5547 | /// takes the answer. That is rule 2, and it is now held by the type of the |
| 5548 | /// argument rather than by a check inside a method that could only fire |
| 5549 | /// after the description was already written. |
| 5550 | /// |
| 5551 | /// The empty form is not a degenerate case. Five tab strips in MNW declare |
| 5552 | /// a fallback and no members at all: a [`RegionKind::TabGroup`] puts its |
| 5553 | /// own strip in the row, generated from its children's |
| 5554 | /// [`labels`](Self::label), so the row is full without anything being put |
| 5555 | /// in it and the fallback is the only thing left to say. |
| 5556 | /// |
| 5557 | /// Calling it twice replaces the row, members and all. The row is one |
| 5558 | /// value now, so a second call is a second row rather than a correction to |
| 5559 | /// the first, and correcting a fallback means correcting it on the `Run` |
| 5560 | /// before the region is ever told about it. |
| 5561 | |
| 5562 | |
| 5563 | self.run = Some; |
| 5564 | self |
| 5565 | |
| 5566 | |
| 5567 | /// Add a node, saying what it is worth when room runs out. |
| 5568 | /// |
| 5569 | /// The narrowing member. Spelled as a second constructor rather than as a |
| 5570 | /// builder on the placement, the way [`Column::priority`] is, because a |
| 5571 | /// `Ranked` is built at the point it is inserted and there is nothing to |
| 5572 | /// hold between building it and pushing it. |
| 5573 | |
| 5574 | |
| 5575 | self.body.push; |
| 5576 | self |
| 5577 | |
| 5578 | |
| 5579 | /// Add several nodes, chaining. |
| 5580 | /// |
| 5581 | /// Takes anything that becomes a [`Ranked`], so a run of bare nodes still |
| 5582 | /// works and reads as before, and a run of ranked ones needs no second |
| 5583 | /// method. |
| 5584 | |
| 5585 | |
| 5586 | self.body.extend; |
| 5587 | self |
| 5588 | |
| 5589 | |
| 5590 | /// The content is on its way rather than here. |
| 5591 | |
| 5592 | |
| 5593 | self.readiness = Pending; |
| 5594 | self |
| 5595 | |
| 5596 | |
| 5597 | /// The content arrives from this call rather than with the screen. |
| 5598 | /// |
| 5599 | /// Sets [`readiness`](Self::readiness) to |
| 5600 | /// [`Pending`](layout::Readiness::Pending) in the same breath, because a |
| 5601 | /// region that says where its content is coming from is by construction a |
| 5602 | /// region that does not have it yet, and the two disagreeing is a state no |
| 5603 | /// renderer could draw honestly. |
| 5604 | /// |
| 5605 | /// Mark the action [`awaiting`](Action::awaiting) unless there is a reason |
| 5606 | /// not to. Without the mark this is still a deferred load and every renderer |
| 5607 | /// still fetches; what is lost is the size of the wait, so the stand-in has |
| 5608 | /// no proportion to draw. |
| 5609 | |
| 5610 | |
| 5611 | self.readiness = Pending; |
| 5612 | self.fed_by = Some; |
| 5613 | self |
| 5614 | |
| 5615 | |
| 5616 | /// The contents change without the user, so a renderer keeps looking. |
| 5617 | /// |
| 5618 | /// See [`live`](Self::live). Says nothing about how often: the cadence is |
| 5619 | /// the renderer's, and a description that named one would be a description |
| 5620 | /// the webview and the terminal disagreed about. |
| 5621 | /// |
| 5622 | /// Combines with [`fed_by`](Self::fed_by) rather than replacing it. Called |
| 5623 | /// after it, the region is asked for once and then re-asked; called on a |
| 5624 | /// region with no call, the host re-reads whatever it is drawing from. |
| 5625 | |
| 5626 | pub const |
| 5627 | self.live = true; |
| 5628 | self |
| 5629 | |
| 5630 | |
| 5631 | /// Ask this route when the questions inside this region move. |
| 5632 | /// |
| 5633 | /// See [`consults`](Self::consults). Appends rather than replaces, which is |
| 5634 | /// the reading [`Field::consulting`] has for the same reason: two questions |
| 5635 | /// about one set of dials are two questions, and a second call adding a |
| 5636 | /// second one is what a builder reads as. |
| 5637 | /// |
| 5638 | /// ``` |
| 5639 | /// use quasi_router::{Action, Consult, Slot}; |
| 5640 | /// |
| 5641 | /// let calculator = Slot::group("pricing-calculator").consulting( |
| 5642 | /// Consult::new(Action::get("/pricing/compare").replacing("results-panel")) |
| 5643 | /// .after(std::time::Duration::from_millis(300)), |
| 5644 | /// ); |
| 5645 | /// |
| 5646 | /// assert_eq!(calculator.consults.len(), 1); |
| 5647 | /// ``` |
| 5648 | |
| 5649 | |
| 5650 | self.consults.push; |
| 5651 | self |
| 5652 | |
| 5653 | |
| 5654 | /// What this region is waiting on, when it is waiting on something. |
| 5655 | /// |
| 5656 | /// Asked once here rather than reached through the action in each renderer. |
| 5657 | |
| 5658 | |
| 5659 | self.fed_by.as_ref.and_then |
| 5660 | |
| 5661 | |
| 5662 | /// Show one child at a time, starting at this one. |
| 5663 | /// |
| 5664 | /// The carousel and the tab group, which are one thing said twice: whether |
| 5665 | /// a host draws a strip of names or a prev/next row falls out of whether |
| 5666 | /// the children carry a [`label`](Self::label), never out of the widget's |
| 5667 | /// name. |
| 5668 | /// |
| 5669 | /// `shown` is where the region starts, not where it stays. Whether a later |
| 5670 | /// visit comes back to the same child is the renderer's, and there is no |
| 5671 | /// way to say otherwise here. See [`showing`](Self::showing) for the rule |
| 5672 | /// and what it settles. |
| 5673 | |
| 5674 | |
| 5675 | self.body.select; |
| 5676 | self |
| 5677 | |
| 5678 | |
| 5679 | /// Show one child or none, starting closed unless a child is named. |
| 5680 | /// |
| 5681 | /// Disclosure. `None` is the closed state and is a legal resting place, |
| 5682 | /// which is the whole of what separates this from |
| 5683 | /// [`showing_one`](Self::showing_one). |
| 5684 | /// |
| 5685 | /// A closed disclosure that fetches its contents when it first opens is |
| 5686 | /// describable as `None` here plus a [`Readiness`](layout::Readiness) for |
| 5687 | /// the wait. What makes the fetch start is the renderer's, under the rule |
| 5688 | /// on [`showing`](Self::showing). |
| 5689 | |
| 5690 | |
| 5691 | self.body.select; |
| 5692 | self |
| 5693 | |
| 5694 | |
| 5695 | /// Say that this region's children are answers to one question. |
| 5696 | /// |
| 5697 | /// See [`Repeating`], and put [`removes`](Self::removes) on each child: the |
| 5698 | /// two are halves of one description and a group with neither is a group |
| 5699 | /// nobody can shrink. |
| 5700 | |
| 5701 | |
| 5702 | self.repeating = Some; |
| 5703 | self |
| 5704 | |
| 5705 | |
| 5706 | /// Say what taking this slot away calls. |
| 5707 | /// |
| 5708 | /// Only meaningful on a child of a [`repeating`](Self::repeating) region. |
| 5709 | /// Ignored elsewhere rather than refused, for the reason every other |
| 5710 | /// builder here is a no-op off its own member: a description that says |
| 5711 | /// something no renderer reads is a description bug, and a panic in a |
| 5712 | /// builder chain is a worse way to find one than a control that is not |
| 5713 | /// drawn. |
| 5714 | |
| 5715 | |
| 5716 | self.removes = Some; |
| 5717 | self |
| 5718 | |
| 5719 | |
| 5720 | /// Name this region in its own right. See [`name`](Self::name) for what |
| 5721 | /// separates it from [`label`](Self::label). |
| 5722 | |
| 5723 | |
| 5724 | self.name = Some; |
| 5725 | self |
| 5726 | |
| 5727 | |
| 5728 | /// Which child to draw, once [`shown`](Self::shown) is read against the body. |
| 5729 | /// |
| 5730 | /// `None` means draw them all, which is both [`layout::Showing::All`] and a |
| 5731 | /// closed disclosure — the two cases differ in what chrome sits around them |
| 5732 | /// and not in what a renderer does with the body, so they answer the same |
| 5733 | /// here. |
| 5734 | /// |
| 5735 | /// An index past the end is clamped rather than refused. A description |
| 5736 | /// pointing at a frame that is not there is a bug in the app, and a renderer |
| 5737 | /// that answers it by drawing nothing reports it as a region that vanished, |
| 5738 | /// which is the hardest kind of bug to find from what is on the screen. |
| 5739 | /// [`layout::Share::percent`] clamps for the same reason. |
| 5740 | /// |
| 5741 | /// The clamp is [`layout::Window`]'s, not this method's. A frame is a window |
| 5742 | /// of one over children that are all present, which is the same arithmetic a |
| 5743 | /// paged list runs over rows that are not — see [`Rest`]. Sharing it is what |
| 5744 | /// stops the two disagreeing about which frame is last. |
| 5745 | |
| 5746 | |
| 5747 | let Selective |
| 5748 | picks, |
| 5749 | shown, |
| 5750 | frames, |
| 5751 | = &self.body |
| 5752 | else |
| 5753 | return None; |
| 5754 | ; |
| 5755 | if frames.is_empty |
| 5756 | return None; |
| 5757 | |
| 5758 | let frame = ; |
| 5759 | match picks |
| 5760 | One => Some, |
| 5761 | AtMostOne => shown.map, |
| 5762 | |
| 5763 | |
| 5764 | |
| 5765 | /// How much of this region is up at once, in the vocabulary's words. |
| 5766 | |
| 5767 | pub const |
| 5768 | self.body.showing |
| 5769 | |
| 5770 | |
| 5771 | /// Which member the region opens on, before clamping. |
| 5772 | /// |
| 5773 | /// Read [`current`](Self::current) instead when drawing, which is where an |
| 5774 | /// index past the end of the body is dealt with. |
| 5775 | |
| 5776 | pub const |
| 5777 | self.body.shown |
| 5778 | |
| 5779 | |
| 5780 | /// Every member in order, whichever shape the body is. |
| 5781 | + '_ |
| 5782 | self.body.members |
| 5783 | |
| 5784 | |
| 5785 | /// The children's names, when they have them. |
| 5786 | /// |
| 5787 | /// Empty unless *every* child is a named region, which is the test a |
| 5788 | /// renderer applies before drawing a strip: a strip with a hole in it is |
| 5789 | /// worse than the prev/next row it would otherwise have drawn, and a |
| 5790 | /// half-labelled body is an app bug rather than a third idiom. |
| 5791 | |
| 5792 | |
| 5793 | let Selective = &self.body else |
| 5794 | // A region that shows everything reveals nothing, so it has no |
| 5795 | // controls to name. Since `2cdc6761` it cannot hold a name either. |
| 5796 | return Vecnew; |
| 5797 | ; |
| 5798 | |
| 5799 | let named: = frames |
| 5800 | .iter |
| 5801 | .filter_map |
| 5802 | .collect; |
| 5803 | |
| 5804 | if named.len == frames.len |
| 5805 | named |
| 5806 | else |
| 5807 | Vecnew |
| 5808 | |
| 5809 | |
| 5810 | |
| 5811 | /// What this region and the regions inside it offer under a control name. |
| 5812 | /// |
| 5813 | /// The described half of a [`Reveal`]: what the box was handed to the |
| 5814 | /// reader holding, before anything was typed into it. A renderer that |
| 5815 | /// holds edits reads those first and falls back to this, which is the same |
| 5816 | /// order a submit reads a form in. |
| 5817 | |
| 5818 | |
| 5819 | self.run |
| 5820 | .iter |
| 5821 | .flat_map |
| 5822 | .chain |
| 5823 | .find_map |
| 5824 | |
| 5825 | |
| 5826 | /// Every question inside this region, at any depth, in draw order. |
| 5827 | /// |
| 5828 | /// What a [`consults`](Self::consults) gathers, and it lives here rather |
| 5829 | /// than in each renderer for [`holds`](Self::holds)' reason: three walks |
| 5830 | /// over this crate's own tree are three chances to disagree about what |
| 5831 | /// "inside this region" means, and the browser's answer — every control |
| 5832 | /// the element contains — is not one a terminal can copy without being |
| 5833 | /// told the same shape. |
| 5834 | /// |
| 5835 | /// The run as well as the body: a filter bar that moved into the region's |
| 5836 | /// leading row is still a dial of that region, and a walk that skipped the |
| 5837 | /// run would recompute without it. |
| 5838 | /// |
| 5839 | /// A repeating question ([`Field::repeats`]) is one entry, under the name |
| 5840 | /// the description gave it. How many boxes that is standing right now is |
| 5841 | /// the renderer's, since it is the renderer that holds the count. |
| 5842 | |
| 5843 | |
| 5844 | let mut found = Vecnew; |
| 5845 | for placed in self |
| 5846 | .run |
| 5847 | .iter |
| 5848 | .flat_map |
| 5849 | .chain |
| 5850 | |
| 5851 | placed.node.questions; |
| 5852 | |
| 5853 | found |
| 5854 | |
| 5855 | |
| 5856 | /// Whether a control anywhere in this region carries this |
| 5857 | /// [`Act::id`](Act::id). |
| 5858 | /// |
| 5859 | /// The run as well as the body, for [`find`](Self::find)'s reason: a control |
| 5860 | /// in a toolbar is as reachable as one in a pane, and a walk that skipped |
| 5861 | /// the run would answer `false` for half the screens in the tree. |
| 5862 | |
| 5863 | |
| 5864 | self.body |
| 5865 | .iter |
| 5866 | .chain |
| 5867 | .any |
| 5868 | |
| 5869 | |
| 5870 | /// This slot, or the first slot under this address anywhere inside it. |
| 5871 | |
| 5872 | |
| 5873 | if self.id == id |
| 5874 | return Some; |
| 5875 | |
| 5876 | // The run is walked with the body, and it has to be: goingson's toolbar |
| 5877 | // is a region that moved out of the pane and into the tab strip's row, |
| 5878 | // and a region a fragment cannot be aimed at is a region that stops |
| 5879 | // updating. Body first, because that is draw order for everything that |
| 5880 | // is not a tab group and the one that is has no body a fragment names. |
| 5881 | self.body |
| 5882 | .iter |
| 5883 | .chain |
| 5884 | .find_map |
| 5885 | Region => slot.find, |
| 5886 | _ => None, |
| 5887 | |
| 5888 | |
| 5889 | |
| 5890 | /// Whether this region, or one inside it, holds a row of a live selection. |
| 5891 | /// |
| 5892 | /// [`find`](Self::find)'s walk asking a different question. See |
| 5893 | /// [`Screen::chooses`], which is the only caller and carries the reasoning. |
| 5894 | |
| 5895 | |
| 5896 | self.body |
| 5897 | .iter |
| 5898 | .chain |
| 5899 | .any |
| 5900 | Region => slot.chooses, |
| 5901 | Table => rows.iter.any, |
| 5902 | _ => false, |
| 5903 | |
| 5904 | |
| 5905 | |
| 5906 | /// The mutable half of [`find`](Self::find). |
| 5907 | /// |
| 5908 | /// Same walk, and it has to be a second function rather than the same one |
| 5909 | /// generic over mutability: a `&mut` borrow of `self` cannot be handed to |
| 5910 | /// the recursive call and kept, which is what `find_map` does on the shared |
| 5911 | /// side. |
| 5912 | /// Every call this region and the regions inside it are waiting on, in draw |
| 5913 | /// order. |
| 5914 | /// |
| 5915 | /// Here rather than in each renderer because it is a walk over this crate's |
| 5916 | /// own tree, and two hosts writing it separately is two answers to "which |
| 5917 | /// regions have not arrived". A webview needs none of it: the markup carries |
| 5918 | /// a trigger per region and the browser does the walk. Every host that |
| 5919 | /// retains the description rather than the markup does need it. |
| 5920 | /// # A tab's panel is not waiting, it is unasked |
| 5921 | /// |
| 5922 | /// A labelled region that shows one child at a time is a tab strip, and a |
| 5923 | /// child of one carrying [`fed_by`](Self::fed_by) is naming the tab's |
| 5924 | /// address rather than saying its content is on its way. Those are not |
| 5925 | /// returned here: a host that asked for all of them would fetch every |
| 5926 | /// panel the moment the screen went up, which is what the reader pressing |
| 5927 | /// the tabs exists to avoid — measured on MNW's library page as five |
| 5928 | /// database reads per view where there had been one. |
| 5929 | /// |
| 5930 | /// The walk still descends, because a panel that has arrived may hold a |
| 5931 | /// region of its own that genuinely is waiting, and that one is nobody's |
| 5932 | /// tab. |
| 5933 | |
| 5934 | // A live region's call is a cadence rather than an arrival, so it is |
| 5935 | // [`Screen::refreshes`]'s and not this walk's. Returning it here would |
| 5936 | // have a retained host ask again the moment the answer landed, which is |
| 5937 | // a poll at whatever speed the event loop happens to run at. |
| 5938 | if let Some = &self.fed_by |
| 5939 | && !self.live |
| 5940 | |
| 5941 | out.push; |
| 5942 | |
| 5943 | let tabbed = self.showing.selective && !self.labels.is_empty; |
| 5944 | for placed in self |
| 5945 | .body |
| 5946 | .iter |
| 5947 | .chain |
| 5948 | |
| 5949 | if let Region = &placed.node |
| 5950 | if tabbed |
| 5951 | slot.panel_feeds_into; |
| 5952 | else |
| 5953 | slot.feeds_into; |
| 5954 | |
| 5955 | |
| 5956 | |
| 5957 | |
| 5958 | |
| 5959 | /// Every call this region and the regions inside it re-ask on a cadence. |
| 5960 | /// |
| 5961 | /// [`feeds_into`](Self::feeds_into)'s counterpart, split by |
| 5962 | /// [`live`](Self::live) so that the two answers never overlap: a call is |
| 5963 | /// one or the other and no host has to work out which. |
| 5964 | /// |
| 5965 | /// A tab's unopened panel is skipped here for the reason it is skipped |
| 5966 | /// there. A panel nobody has asked for is not being kept up to date either. |
| 5967 | |
| 5968 | if let Some = &self.fed_by |
| 5969 | && self.live |
| 5970 | |
| 5971 | out.push; |
| 5972 | |
| 5973 | let tabbed = self.showing.selective && !self.labels.is_empty; |
| 5974 | for placed in self |
| 5975 | .body |
| 5976 | .iter |
| 5977 | .chain |
| 5978 | |
| 5979 | if let Region = &placed.node |
| 5980 | if tabbed |
| 5981 | slot.panel_refreshes_into; |
| 5982 | else |
| 5983 | slot.refreshes_into; |
| 5984 | |
| 5985 | |
| 5986 | |
| 5987 | |
| 5988 | |
| 5989 | /// This region and the regions inside it that ask a question of their own. |
| 5990 | /// |
| 5991 | /// [`refreshes_into`](Self::refreshes_into)'s shape for |
| 5992 | /// [`consults`](Self::consults), and here for its reason: what counts as a |
| 5993 | /// region of this screen is this crate's answer, and a renderer walking the |
| 5994 | /// tree itself is a second answer waiting to disagree. |
| 5995 | /// |
| 5996 | /// A tab's unopened panel is **not** skipped, unlike the two walks above. |
| 5997 | /// Those find calls a host would make on its own; this finds the questions |
| 5998 | /// a control the reader touched sets off, and a control inside a panel |
| 5999 | /// nobody has opened is a control nobody has touched. Skipping it would |
| 6000 | /// cost a walk and rule out nothing. |
| 6001 | |
| 6002 | if !self.consults.is_empty |
| 6003 | out.push; |
| 6004 | |
| 6005 | for placed in self |
| 6006 | .body |
| 6007 | .iter |
| 6008 | .chain |
| 6009 | |
| 6010 | if let Region = &placed.node |
| 6011 | slot.consulting_into; |
| 6012 | |
| 6013 | |
| 6014 | |
| 6015 | |
| 6016 | /// Whether this region or anything inside it changes without the user. |
| 6017 | /// |
| 6018 | /// Wider than [`refreshes_into`](Self::refreshes_into), which finds only the |
| 6019 | /// live regions that name a call. A host asks this to decide whether to keep |
| 6020 | /// redrawing at all, and a live region reading state the host already holds |
| 6021 | /// is exactly the case that has no call to find. |
| 6022 | |
| 6023 | self.live |
| 6024 | || self |
| 6025 | .body |
| 6026 | .iter |
| 6027 | .chain |
| 6028 | .any |
| 6029 | Region => slot.live_within, |
| 6030 | _ => false, |
| 6031 | |
| 6032 | |
| 6033 | |
| 6034 | /// [`refreshes_into`](Self::refreshes_into) for a tab's panel: the panel's |
| 6035 | /// own call is its address, not a cadence. |
| 6036 | |
| 6037 | for placed in self |
| 6038 | .body |
| 6039 | .iter |
| 6040 | .chain |
| 6041 | |
| 6042 | if let Region = &placed.node |
| 6043 | slot.refreshes_into; |
| 6044 | |
| 6045 | |
| 6046 | |
| 6047 | |
| 6048 | /// [`feeds_into`](Self::feeds_into) for a tab's panel: whatever is inside it |
| 6049 | /// is waiting, and the panel itself is not. |
| 6050 | |
| 6051 | for placed in self |
| 6052 | .body |
| 6053 | .iter |
| 6054 | .chain |
| 6055 | |
| 6056 | if let Region = &placed.node |
| 6057 | slot.feeds_into; |
| 6058 | |
| 6059 | |
| 6060 | |
| 6061 | |
| 6062 | /// The call that fills this region when something above it asks on its |
| 6063 | /// behalf, which is a tab strip pressing one of its own tabs. |
| 6064 | /// |
| 6065 | /// The address a panel carries but does not act on, so the one party that |
| 6066 | /// does act on it does not have to reach into the field and decide for |
| 6067 | /// itself what the field means here. |
| 6068 | /// |
| 6069 | /// # Only while it is empty, and only a retained-screen host can tell |
| 6070 | /// |
| 6071 | /// A panel that has been read is not asked for again, which is what going |
| 6072 | /// back to a tab means. That is the answer for a host holding the |
| 6073 | /// description, and it is not the answer a webview gives: the markup keeps |
| 6074 | /// the address on the button after [`Screen::replace`] has dropped it from |
| 6075 | /// the tree, so a browser re-reads the panel on every press. The divergence |
| 6076 | /// is `replace` clearing [`fed_by`](Self::fed_by), which predates this and |
| 6077 | /// is the rule that stops a retained screen asking twice on one paint. |
| 6078 | /// |
| 6079 | /// Neither is wrong and the difference is visible only as freshness, so it |
| 6080 | /// is recorded here rather than papered over. What both agree on is the one |
| 6081 | /// thing that mattered: pressing a tab is what fetches it, and four unpressed |
| 6082 | /// frames fetch nothing. |
| 6083 | |
| 6084 | |
| 6085 | if self.body.is_empty |
| 6086 | self.fed_by.as_deref |
| 6087 | else |
| 6088 | None |
| 6089 | |
| 6090 | |
| 6091 | |
| 6092 | |
| 6093 | if self.id == id |
| 6094 | return Some; |
| 6095 | |
| 6096 | self.body |
| 6097 | .iter_mut |
| 6098 | .chain |
| 6099 | .find_map |
| 6100 | Region => slot.find_mut, |
| 6101 | _ => None, |
| 6102 | |
| 6103 | |
| 6104 | |
| 6105 | |
| 6106 | /// A value an act puts into a field on the same screen. |
| 6107 | /// |
| 6108 | /// An act names a destination field, and the renderer decides where in it the |
| 6109 | /// value lands. Measured on MNW's media picker, where the whole point of the |
| 6110 | /// button is to put an image reference into the box the reader is already |
| 6111 | /// typing in, and where the three surfaces it appears on lose between 30 |
| 6112 | /// seconds and the entire unsaved draft if the server does the appending |
| 6113 | /// instead. |
| 6114 | /// |
| 6115 | /// # Stated as a destination and never as a caret |
| 6116 | /// |
| 6117 | /// The description says "into `body`". It never says "at the caret", because |
| 6118 | /// where inside a field a value lands is the renderer's, the same class of fact |
| 6119 | /// as how a menu overflows or when a toast clears. A webview inserts at the |
| 6120 | /// selection, a terminal and an immediate-mode host append, and neither is |
| 6121 | /// wrong. This is what keeps `d52884b0` — "a field's described state is its |
| 6122 | /// value, and the caret is the renderer's" — standing rather than reopened: |
| 6123 | /// nothing here carries a caret in either direction. |
| 6124 | /// |
| 6125 | /// # Both halves, because an act has no value of its own |
| 6126 | /// |
| 6127 | /// [`field`](Self::field) is addressed by [`Field::name`], which is already how |
| 6128 | /// [`Field::writes`] and a submit name a field, so no id relationship is |
| 6129 | /// invented. [`value`](Self::value) is here because an [`Act`] carries a label |
| 6130 | /// and an address and nothing else a renderer could put anywhere: the picker's |
| 6131 | /// card reads as a file name and deposits ``, and the two |
| 6132 | /// are not the same string. |
| 6133 | /// |
| 6134 | /// # It does not replace the act's action |
| 6135 | /// |
| 6136 | /// A renderer fills first and then dispatches [`Act::action`] as it always |
| 6137 | /// would. An act that only fills says so with [`Destination::Local`], which is |
| 6138 | /// the vocabulary's existing way to say that no request goes out, and is what |
| 6139 | /// every measured site wants. |
| 6140 | |
| 6141 | |
| 6142 | /// The [`Field::name`] that receives it. |
| 6143 | pub field: String, |
| 6144 | /// What lands there. |
| 6145 | pub value: String, |
| 6146 | |
| 6147 | |
| 6148 | /// A control that calls a route. |
| 6149 | /// |
| 6150 | /// A button, a link and a menu item are the same thing to a description: a |
| 6151 | /// label, an address, and how loudly it is saying it. Which of the three a |
| 6152 | /// renderer draws is a renderer decision. |
| 6153 | |
| 6154 | |
| 6155 | /// What it is called. |
| 6156 | pub label: String, |
| 6157 | /// What it calls. |
| 6158 | pub action: Action, |
| 6159 | /// What it is saying. [`layout::Tone::Danger`] is what marks the button |
| 6160 | /// that destroys something. |
| 6161 | pub tone: Tone, |
| 6162 | /// Focused, disabled, or neither. |
| 6163 | pub state: , |
| 6164 | /// What to ask before doing it, if it should be asked. |
| 6165 | /// |
| 6166 | /// The prompt only. The word on the agreeing button is |
| 6167 | /// [`label`](Self::label), because it already is — goingson's `confirmDelete` |
| 6168 | /// passes `confirmText: 'Delete'` for an act labelled "Delete" — and a |
| 6169 | /// second string would be the same word twice with a chance to disagree. |
| 6170 | /// [`tone`](Self::tone) already says whether the dialog is a dangerous one. |
| 6171 | /// |
| 6172 | /// `Region::Modal` names the box a confirmation appears in and does not name |
| 6173 | /// the pattern. This is the pattern: a webview raises a dialog, a touch host |
| 6174 | /// an action sheet, a terminal a y/n line, and none of them is a route to a |
| 6175 | /// modal screen and back, which is a different interaction. |
| 6176 | pub confirm: , |
| 6177 | /// The key that reaches it, written the way a user would say it. |
| 6178 | /// |
| 6179 | /// An `Act` had a label and a destination and nothing said which key gets |
| 6180 | /// there, so goingson's 279-line `keyboard.js` holds the table beside the |
| 6181 | /// description, and the help overlay that lists the shortcuts is a second |
| 6182 | /// hand-written copy that can drift from it. |
| 6183 | /// |
| 6184 | /// A terminal makes the case sharper than a webview does: there the key *is* |
| 6185 | /// the affordance, so a description that cannot name one cannot describe the |
| 6186 | /// screen's primary interaction at all. |
| 6187 | /// |
| 6188 | /// Text rather than a modelled chord — "n", "ctrl+k", "?" — because the |
| 6189 | /// vocabulary of keys is the host's and a description that modelled it would |
| 6190 | /// be naming one host's keyboard. A renderer that does not know a name |
| 6191 | /// ignores it, which is what a webview does with a key a terminal wants. |
| 6192 | /// |
| 6193 | /// Screen-scoped, because a screen is what this describes. An app-wide |
| 6194 | /// shortcut belongs to the app and is not a fact about any one screen: |
| 6195 | /// that is [`Chrome::bindings`](crate::Chrome::bindings), held beside the |
| 6196 | /// router rather than inside any answer. A renderer matches those first, so |
| 6197 | /// a screen cannot capture the key that opens the palette. |
| 6198 | pub key: , |
| 6199 | /// The [`Screen::selection`] this acts on, if it acts on one. |
| 6200 | /// |
| 6201 | /// Every ticked [`Row::value`] is sent under [`Node::TICKED`], repeated |
| 6202 | /// once per member. Repeated rather than joined, because a name appearing |
| 6203 | /// many times is what [`Params::get_all`] is for and a delimiter would have |
| 6204 | /// to be one no value can contain. |
| 6205 | /// |
| 6206 | /// # A handler still answers for an empty set |
| 6207 | /// |
| 6208 | /// Every renderer draws a control over an empty selection as disabled and |
| 6209 | /// refuses the press, so the ordinary way to reach a handler with no ticks |
| 6210 | /// is gone. It is not the only way: a hand-typed request has none, and a |
| 6211 | /// webview host that does not serve |
| 6212 | /// [`SELECTION_JS`](https://makenot.work/git/max/quasi) leaves the control |
| 6213 | /// live. A bulk write over nothing should still answer the screen rather |
| 6214 | /// than erroring — a renderer's refusal is an affordance, not a guarantee |
| 6215 | /// about what arrives. |
| 6216 | /// |
| 6217 | /// # The name does not select between sets yet, and cannot |
| 6218 | /// |
| 6219 | /// A screen holds one selection ([`Screen::selection`]), so being set at |
| 6220 | /// all is what makes a control a commit control, and the name is what makes |
| 6221 | /// it *readable* — "Archive" over `chosen` is a different sentence from |
| 6222 | /// "Archive" on a row. |
| 6223 | /// |
| 6224 | /// Matching it against the screen's name was the first shape and it does |
| 6225 | /// not work, because a renderer does not always have the screen: an |
| 6226 | /// [`Outcome::Fragment`] replaces a region and carries no screen at all, so |
| 6227 | /// a webview rendering one would have had to guess and a terminal, which |
| 6228 | /// keeps the screen beside it, would not. The two hosts would then disagree |
| 6229 | /// about a typo, which is exactly the drift this vocabulary exists to stop. |
| 6230 | /// So both read it the same way, and the name starts choosing between sets |
| 6231 | /// on the day [`Screen::selection`] becomes a map. |
| 6232 | /// |
| 6233 | /// [`Params::get_all`]: crate::Params::get_all |
| 6234 | /// [`Outcome::Fragment`]: crate::Outcome::Fragment |
| 6235 | pub over: , |
| 6236 | /// What the press asks for before the call goes out, if it asks for |
| 6237 | /// anything. |
| 6238 | /// |
| 6239 | /// [`confirm`](Self::confirm) is the yes/no shape of this moment, a |
| 6240 | /// question raised after the press and before the call, and this is the |
| 6241 | /// shape that comes back with a value. Measured on MNW's content table, |
| 6242 | /// where five verbs sit over the selection and two of them, "Set Price" |
| 6243 | /// and "Add Tag", reveal a small form first: a label, one box, an Apply |
| 6244 | /// and a hint, hidden until the verb is pressed. |
| 6245 | /// |
| 6246 | /// Every field is sent under its own [`Field::name`], the way a submit |
| 6247 | /// sends it, and the ticks ride along under [`Node::TICKED`] when |
| 6248 | /// [`over`](Self::over) names a selection. A handler reads a verb that |
| 6249 | /// asked for a value the same way it reads one that did not. |
| 6250 | /// |
| 6251 | /// Empty is the ordinary control, which is nearly all of them. |
| 6252 | /// |
| 6253 | /// # Why it is not a form |
| 6254 | /// |
| 6255 | /// Said as a [`Node::Form`] the verb is lost: the description holds a |
| 6256 | /// route, a submit label and a box, and nothing says the box belongs to |
| 6257 | /// "Set Price" rather than to the screen. Said as a route that answers a |
| 6258 | /// form fragment it is a round trip to ask a question whose shape the |
| 6259 | /// screen already knows, and a fragment is markup a terminal or an egui |
| 6260 | /// host has nowhere to put. |
| 6261 | /// |
| 6262 | /// A field here carries no [`Field::writes`]. It is answered by the control |
| 6263 | /// that asked for it, so a renderer that honoured a write on it as well |
| 6264 | /// would fire twice for one value. |
| 6265 | /// |
| 6266 | /// # The disclosure is the renderer's |
| 6267 | /// |
| 6268 | /// quasi-webview puts the fields in a `<details>` under the control, which |
| 6269 | /// is the shape MNW's button-then-form already has. quasi-tui and |
| 6270 | /// quasi-immediate draw them beside the control instead: a terminal has no |
| 6271 | /// popover, and "first paint is final paint" is worth more there than |
| 6272 | /// hiding two boxes. Both send the same values, which is the part the |
| 6273 | /// description states. |
| 6274 | pub asks: , |
| 6275 | /// The field on this screen that receives the act's value, if it has one. |
| 6276 | /// |
| 6277 | /// See [`Prefill`] for the ruling and for why both halves are there. Absent |
| 6278 | /// on nearly every control, which is what makes this additive: a renderer |
| 6279 | /// that finds nothing here draws exactly what it drew before the member |
| 6280 | /// existed. |
| 6281 | /// |
| 6282 | /// Not a second address. [`over`](Self::over) names a selection the act |
| 6283 | /// reads and this names a field the act writes, and an act may carry both: |
| 6284 | /// the picker's card names neither, and a verb that gathers ticks and |
| 6285 | /// deposits a summary somewhere would name both without either meaning the |
| 6286 | /// other. |
| 6287 | pub fills: , |
| 6288 | /// Standing help about the control, when the label does not carry it. |
| 6289 | /// |
| 6290 | /// The same member as [`Field::hint`] and it means the same thing: a |
| 6291 | /// sentence that is always true of this control, shown rather than hunted |
| 6292 | /// for. [`confirm`](Self::confirm) is a question asked at the press and |
| 6293 | /// [`asks`](Self::asks) is a value collected at it; both are about the |
| 6294 | /// moment, and this is about the control. |
| 6295 | /// |
| 6296 | /// Counted before it was added: 78 `title` attributes in MNW, 12 in |
| 6297 | /// Balanced Breakfast and 14 `on_hover_text` calls in audiofiles, and where |
| 6298 | /// they sit is the finding -- `button` and `a` outnumber every other |
| 6299 | /// element carrying one. audiofiles' storage section is the clearest single |
| 6300 | /// site, where six acts wanted one and three said something the label could |
| 6301 | /// not: "Local-only: other synced devices keep their own copies.", |
| 6302 | /// "Runs in the background: keep working", "the result appears in the |
| 6303 | /// status line." |
| 6304 | /// |
| 6305 | /// # Not a tooltip |
| 6306 | /// |
| 6307 | /// Half the hosts have no pointer. The shipped apps spelled this as a hover |
| 6308 | /// because egui and a browser both had one, and the hover is the spelling |
| 6309 | /// rather than the thing. A terminal puts it on a help line, egui may keep |
| 6310 | /// its hover, a webview writes `title` *and* stays free to draw it: what |
| 6311 | /// the description says is that the sentence is true, not that it is |
| 6312 | /// hidden. |
| 6313 | /// |
| 6314 | /// A renderer that draws it must not also drop it from the accessible tree, |
| 6315 | /// which is the failure `title` alone has on a browser. |
| 6316 | /// |
| 6317 | /// # It lives one layer down as of makeover-layout 0.40.0 |
| 6318 | /// |
| 6319 | /// `makeover_layout::Act::hint` is where the member is now, and this one |
| 6320 | /// mirrors it across [`as_layout`](Self::as_layout). |
| 6321 | /// |
| 6322 | /// # Why not prose beside the act |
| 6323 | /// |
| 6324 | /// A [`Node::Text`] next to a control reads well and says nothing about |
| 6325 | /// which control it belongs to, so a renderer laying the region out its own |
| 6326 | /// way separates them. That is the same loss [`Rest`] has beside a table, |
| 6327 | /// and it is why three sites were enough to file this and 104 are enough to |
| 6328 | /// build it. |
| 6329 | /// |
| 6330 | /// `None` is a control whose label is the whole of it, which is nearly all |
| 6331 | /// of them. |
| 6332 | pub hint: , |
| 6333 | /// The value the press puts on the clipboard, if that is what it does. |
| 6334 | /// |
| 6335 | /// Measured on the MNW server: seven of the 67 `window.<name>` globals are |
| 6336 | /// this, across 14 sites -- `copyElementText` (4), `copyEmbedBtn` (4), |
| 6337 | /// `copyText` (2 files), `copyFeedUrl`, `copyItemLink`, `copyKeyCode`, |
| 6338 | /// `onCopyItemId`. |
| 6339 | /// |
| 6340 | /// # Why it is a member here and not a [`Destination`] |
| 6341 | /// |
| 6342 | /// A copy asks no route, so its action is [`Action::local`]. That variant's |
| 6343 | /// own rule is the reason this member exists: *what happens locally is |
| 6344 | /// named by the member carrying the action, never by the variant*. A bare |
| 6345 | /// act with a local destination says only "the renderer's own affordance |
| 6346 | /// happens", so without a member saying what, a described copy button is |
| 6347 | /// `data-action="copyThing"` in a new hat -- the exact thing |
| 6348 | /// [`Destination::Local`] was written to refuse. |
| 6349 | /// |
| 6350 | /// # The value, not its source |
| 6351 | /// |
| 6352 | /// All 14 measured sites copy something the server already rendered, and |
| 6353 | /// six of the seven scrape it back off the DOM at press time. So this |
| 6354 | /// carries the string. Naming a source element instead would make two |
| 6355 | /// elements point at each other by id for a value the description is |
| 6356 | /// holding anyway, which is what [`Field::suggests`] declined. |
| 6357 | /// |
| 6358 | /// The cost, stated: a value the *reader* has since edited cannot be |
| 6359 | /// copied this way. No measured site is one. A control that wants the live |
| 6360 | /// contents of a field is a different member and should be filed when a |
| 6361 | /// second site asks for it. |
| 6362 | /// |
| 6363 | /// # The acknowledgement is not here |
| 6364 | /// |
| 6365 | /// Every one of the seven relabels itself to "Copied!" and reverts, six at |
| 6366 | /// 1500ms and `copyFeedUrl` at 2000ms. That is a temporary label, which is |
| 6367 | /// mnw-server `033c722f`'s class and belongs to `makeover-timing` rather |
| 6368 | /// than to this member. Deliberately separable: a host with no notion of a |
| 6369 | /// reverting label still needs to be told the act copies something. |
| 6370 | pub copies: , |
| 6371 | /// The name an [`Outcome::Anchored`] reaches this control by. |
| 6372 | /// |
| 6373 | /// [`Anchor::Control`] names a control and an [`Act`] had no name to be |
| 6374 | /// named by: a label is what it says and can be the same word twice on one |
| 6375 | /// screen, and an action is where it goes, which two controls may share. |
| 6376 | /// |
| 6377 | /// `None` on nearly every control, which is what makes this additive: a |
| 6378 | /// control nothing anchors to needs no name, and a renderer that finds none |
| 6379 | /// draws exactly what it drew before the member existed. |
| 6380 | /// |
| 6381 | /// Screen-scoped and stable, the same contract [`Slot::id`] carries and for |
| 6382 | /// the same reason — an answer aimed at it lands nowhere if it moves. |
| 6383 | /// |
| 6384 | /// [`Outcome::Anchored`]: crate::Outcome::Anchored |
| 6385 | /// [`Anchor::Control`]: crate::Anchor::Control |
| 6386 | /// [`Slot::id`]: Slot::id |
| 6387 | pub id: , |
| 6388 | /// The picture this control shows, when it shows one. |
| 6389 | /// |
| 6390 | /// [`label`](Self::label) stays what the control *says* and this is what |
| 6391 | /// it *shows*; a renderer draws both, because a control drawing only a |
| 6392 | /// picture and hiding its name is a control with no accessible text. |
| 6393 | /// |
| 6394 | /// # What the containment model already covers |
| 6395 | /// |
| 6396 | /// [`Row::part`] and [`Cell::part`] take any leaf, [`Node::Image`] is one, |
| 6397 | /// and a webview puts an activated row's whole run inside the anchor, so a |
| 6398 | /// picture in an activated row is already pressable with no member here. |
| 6399 | /// |
| 6400 | /// What that leaves is the case where the *control itself* is the picture, |
| 6401 | /// which a row cannot be: |
| 6402 | /// |
| 6403 | /// - `MNW/server/src/quasi/media_picker.rs`, `fn card`. A tile is a region |
| 6404 | /// holding a [`Node::Image`] and an [`Act`] as siblings, so only the file |
| 6405 | /// name answers a press and a reader aiming at the thumbnail hits |
| 6406 | /// nothing. It cannot be an activated row instead, because the press |
| 6407 | /// deposits a value and [`fills`](Self::fills) lives here rather than on |
| 6408 | /// [`Row`]. |
| 6409 | /// - `MNW/server/templates/pages/project.html`, the storefront item card: |
| 6410 | /// `<a class="item-thumbnail"><img></a>`, with a second link on the title |
| 6411 | /// going to the same place. One act showing the cover and labelled with |
| 6412 | /// the title is one control where the markup has two. |
| 6413 | /// |
| 6414 | /// # What each renderer does |
| 6415 | /// |
| 6416 | /// A webview draws the picture inside the control. quasi-immediate draws it |
| 6417 | /// above the button, outside `makeover_immediate::widget::act`, because |
| 6418 | /// `makeover_layout::Act` does not carry a picture -- which was |
| 6419 | /// [`hint`](Self::hint)'s reason too until makeover-layout 0.40.0 moved |
| 6420 | /// that one down. quasi-tui ignores it and draws the label, which is |
| 6421 | /// already the honest terminal answer -- a picture's alt text is what a |
| 6422 | /// terminal has, and the label is saying it. |
| 6423 | pub shows: , |
| 6424 | |
| 6425 | |
| 6426 | |
| 6427 | /// A neutral control calling this route. |
| 6428 | |
| 6429 | Self |
| 6430 | label: label.into, |
| 6431 | action, |
| 6432 | tone: Neutral, |
| 6433 | state: None, |
| 6434 | confirm: None, |
| 6435 | key: None, |
| 6436 | over: None, |
| 6437 | asks: Vecnew, |
| 6438 | fills: None, |
| 6439 | hint: None, |
| 6440 | copies: None, |
| 6441 | shows: None, |
| 6442 | id: None, |
| 6443 | |
| 6444 | |
| 6445 | |
| 6446 | /// Standing help about this control. See [`hint`](Self::hint). |
| 6447 | |
| 6448 | |
| 6449 | self.hint = Some; |
| 6450 | self |
| 6451 | |
| 6452 | |
| 6453 | /// Pressing this puts that value on the clipboard. |
| 6454 | /// |
| 6455 | /// Sets the destination to [`Action::local`] as well, because a copy asks |
| 6456 | /// no route and the two facts are one sentence. See |
| 6457 | /// [`copies`](Self::copies). |
| 6458 | |
| 6459 | |
| 6460 | self.action = local; |
| 6461 | self.copies = Some; |
| 6462 | self |
| 6463 | |
| 6464 | |
| 6465 | /// The picture this control shows. See [`shows`](Self::shows). |
| 6466 | /// |
| 6467 | /// The label is untouched and stays the control's name, which is what a |
| 6468 | /// renderer that draws no pictures reads and what a screen reader |
| 6469 | /// announces. |
| 6470 | |
| 6471 | |
| 6472 | self.shows = Some; |
| 6473 | self |
| 6474 | |
| 6475 | |
| 6476 | /// The name an anchored screen reaches this control by. |
| 6477 | /// |
| 6478 | /// See [`id`](Self::id). Nothing else reads it: it is not a class, not a |
| 6479 | /// test hook and not a second address, and a control that is never anchored |
| 6480 | /// to should not carry one. |
| 6481 | |
| 6482 | |
| 6483 | self.id = Some; |
| 6484 | self |
| 6485 | |
| 6486 | |
| 6487 | /// This acts on the screen's selection, by name. |
| 6488 | /// |
| 6489 | /// The commit half of a staged tick. See [`over`](Self::over) for what |
| 6490 | /// reaches the handler, and [`Screen::selection`] for why a tick stages |
| 6491 | /// rather than writes. |
| 6492 | |
| 6493 | |
| 6494 | self.over = Some; |
| 6495 | self |
| 6496 | |
| 6497 | |
| 6498 | /// Ask for this value before doing it, chaining. |
| 6499 | /// |
| 6500 | /// Adds rather than replaces, for [`Field::consults`]' reason: MNW's two |
| 6501 | /// verbs ask for one value each, and a builder that took the last call |
| 6502 | /// would make a verb wanting two unwritable. See [`asks`](Self::asks). |
| 6503 | |
| 6504 | |
| 6505 | self.asks.push; |
| 6506 | self |
| 6507 | |
| 6508 | |
| 6509 | /// Put this value into that field when it is pressed. |
| 6510 | /// |
| 6511 | /// Replaces rather than appends, unlike [`asking`](Self::asking): an act |
| 6512 | /// deposits one value, and a control writing into two boxes at once is a |
| 6513 | /// description doing two things under one press. See [`Prefill`]. |
| 6514 | |
| 6515 | |
| 6516 | self.fills = Some |
| 6517 | field: field.into, |
| 6518 | value: value.into, |
| 6519 | ; |
| 6520 | self |
| 6521 | |
| 6522 | |
| 6523 | /// Ask this before doing it. |
| 6524 | |
| 6525 | |
| 6526 | self.confirm = Some; |
| 6527 | self |
| 6528 | |
| 6529 | |
| 6530 | /// The key that reaches it. |
| 6531 | |
| 6532 | |
| 6533 | self.key = Some; |
| 6534 | self |
| 6535 | |
| 6536 | |
| 6537 | /// Set what it is saying. |
| 6538 | |
| 6539 | |
| 6540 | self.tone = tone; |
| 6541 | self |
| 6542 | |
| 6543 | |
| 6544 | /// Present, visible, and not answering. |
| 6545 | |
| 6546 | |
| 6547 | self.state = Some; |
| 6548 | self |
| 6549 | |
| 6550 | |
| 6551 | /// Whether the control currently answers input. |
| 6552 | |
| 6553 | |
| 6554 | !self |
| 6555 | .state |
| 6556 | .is_some_and |
| 6557 | |
| 6558 | |
| 6559 | /// Borrow as the description layer's own type. |
| 6560 | /// |
| 6561 | /// [`action`](Self::action) and [`confirm`](Self::confirm) do not survive |
| 6562 | /// the crossing, and that is what the two layers disagree about rather than |
| 6563 | /// an oversight. An address is quasi's — every host follows one differently |
| 6564 | /// — and a confirmation is a question asked after the press, so it belongs |
| 6565 | /// to whoever is holding the interaction. What is left is what a renderer |
| 6566 | /// needs to *draw* the control, which is all `layout::Act` claims to be. |
| 6567 | |
| 6568 | |
| 6569 | Act |
| 6570 | label: &self.label, |
| 6571 | key: self.key.as_deref, |
| 6572 | tone: self.tone, |
| 6573 | state: self.state, |
| 6574 | hint: self.hint.as_deref, |
| 6575 | |
| 6576 | |
| 6577 | |
| 6578 | |
| 6579 | // `Part` was merged into `Cell` on 2026-09-05. |
| 6580 | // |
| 6581 | // A part was a cell that carried its own role because a list had no columns to |
| 6582 | // carry it. Now a list declares columns like a table does, so the role is |
| 6583 | // `CellKey::Role` and there is one type. `Part::worth` is `Cell::priority` |
| 6584 | // falling back to its key. |
| 6585 | |
| 6586 | /// One row of a list. |
| 6587 | /// |
| 6588 | /// # The run |
| 6589 | /// |
| 6590 | /// A row's content is an inline run of [`Part`]s, in the order the description |
| 6591 | /// says them, the same way a [`Cell`]'s is. |
| 6592 | /// -- each of which arrived as a counted-sites argument, a member here, a |
| 6593 | /// [`layout::RowPart`] variant and a release: `RowPart::Tokens` at |
| 6594 | /// makeover-layout 0.9.0 for a badge in a row, `RowPart::Proportion` at 0.11.0 |
| 6595 | /// for a bar in one. A link in a row was simply not sayable, and a figure in |
| 6596 | /// one was not either. Under the run both are already sayable and cost nothing. |
| 6597 | /// |
| 6598 | /// The bound is that every part is a leaf, so a row is drawable on one wrapped |
| 6599 | /// line without a renderer knowing what is in it. [`Row::part`] is where that |
| 6600 | /// bites at a call site. |
| 6601 | /// |
| 6602 | /// Order is the description's: a part draws where it was put, so a tag between |
| 6603 | /// two facts stays between them. |
| 6604 | /// |
| 6605 | /// # What stayed a field |
| 6606 | /// |
| 6607 | /// [`activate`](Self::activate), [`current`](Self::current), |
| 6608 | /// [`selected`](Self::selected), [`menu`](Self::menu) and |
| 6609 | /// [`toggle`](Self::toggle) are facts *about* the row rather than content in |
| 6610 | /// it. A run of things on a line is not where "this row is the one the detail |
| 6611 | /// pane is showing" belongs. |
| 6612 | /// |
| 6613 | /// # The cost |
| 6614 | /// |
| 6615 | /// [`primary()`](Self::primary) is no longer guaranteed to be one string, which |
| 6616 | /// is what let a constrained renderer right-align a row cheaply. It answers the |
| 6617 | /// text of the primary parts joined, and a row built the ordinary way still has |
| 6618 | /// exactly one. |
| 6619 | /// A row and when it happens. |
| 6620 | /// |
| 6621 | /// The pairing [`Node::Timeline`] is made of. Deliberately a pair rather than |
| 6622 | /// members on [`Row`]: a row does not become a different kind of thing by |
| 6623 | /// being placed, and every list, table and detail pane in the tree would |
| 6624 | /// otherwise carry two integers it has no use for. |
| 6625 | |
| 6626 | |
| 6627 | /// Where it sits on the axis, and for how long. |
| 6628 | pub placement: Placement, |
| 6629 | /// The thing itself, said the ordinary way. |
| 6630 | pub row: Row, |
| 6631 | |
| 6632 | |
| 6633 | |
| 6634 | /// A row at a start and a duration, both in minutes. |
| 6635 | |
| 6636 | pub const |
| 6637 | Self |
| 6638 | placement: new, |
| 6639 | row, |
| 6640 | |
| 6641 | |
| 6642 | |
| 6643 | /// Whether this and another cover any of the same time. |
| 6644 | /// |
| 6645 | /// Forwarded so a renderer laying out collisions does not reach through to |
| 6646 | /// the placement and, in doing so, decide for itself what overlapping |
| 6647 | /// means. |
| 6648 | |
| 6649 | pub const |
| 6650 | self.placement.overlaps |
| 6651 | |
| 6652 | |
| 6653 | |
| 6654 | |
| 6655 | |
| 6656 | /// What is in the row, in order, one entry per column it says anything in. |
| 6657 | /// |
| 6658 | /// Built by [`Row::new`], [`secondary`](Row::secondary), |
| 6659 | /// [`meta`](Row::meta), [`token`](Row::token), [`act`](Row::act), |
| 6660 | /// [`meter`](Row::meter) for the default column set, and by |
| 6661 | /// [`cells`](Row::cells), [`at`](Row::at) and [`cell`](Row::cell) for a |
| 6662 | /// declared one. |
| 6663 | /// |
| 6664 | /// A cell says which column it answers to through [`Cell::key`], so a row |
| 6665 | /// is no longer two collections with a private staging vector between |
| 6666 | /// them. A cell keyed [`CellKey::Named`] is unresolved until |
| 6667 | /// [`Table::row`] sees it. |
| 6668 | pub cells: , |
| 6669 | /// The route that selects this row, if selecting it does anything. |
| 6670 | pub activate: , |
| 6671 | /// Whether this is the row the detail side is currently showing. |
| 6672 | /// |
| 6673 | /// This one is the app's own pointer into a set: what a list-detail |
| 6674 | /// arrangement highlights because its pane is showing it, and what a |
| 6675 | /// webview says with `aria-current`. The user's tick is |
| 6676 | /// [`selected`](Self::selected), and conflating them meant a screen with |
| 6677 | /// bulk actions could not describe its checkboxes at all. |
| 6678 | pub current: bool, |
| 6679 | /// Whether this row is part of a selection that is already in force. |
| 6680 | /// |
| 6681 | /// Three states for [`selected`]'s reason, the app owns the set, the |
| 6682 | /// renderer contributes the gesture through [`Choosing`], and it draws as a |
| 6683 | /// highlighted row rather than as a checkbox. |
| 6684 | /// |
| 6685 | /// [`selected`]: Self::selected |
| 6686 | pub chosen: , |
| 6687 | /// Whether the user has ticked this row, and whether they can. |
| 6688 | /// |
| 6689 | /// Three states in one field, which is why it is not a `bool`. `None` means |
| 6690 | /// the row is not selectable and no affordance should be drawn; `Some(false)` |
| 6691 | /// means it can be ticked and is not; `Some(true)` means it is. A plain bool |
| 6692 | /// cannot tell "not ticked" from "not tickable", so every renderer would |
| 6693 | /// have had to be told selectability some other way, and each would have |
| 6694 | /// picked a different way. |
| 6695 | /// |
| 6696 | /// This is the user's selection, as distinct from |
| 6697 | /// [`current`](Self::current). goingson's contacts and tasks screens both |
| 6698 | /// drive bulk actions from it. |
| 6699 | pub selected: , |
| 6700 | /// Everything else that can be done to this row. |
| 6701 | /// |
| 6702 | /// The [`Actions`](layout::RowPart::Actions) parts of the run are what the |
| 6703 | /// row shows; this is what it *offers*, reached by right-click on a |
| 6704 | /// pointer host, long-press on a touch one, and a key in a terminal. That |
| 6705 | /// split is the whole reason it belongs in the description rather than in |
| 6706 | /// a renderer: one description has to become a context menu, an action |
| 6707 | /// sheet and a key-driven menu, and no single renderer can be the place |
| 6708 | /// where it is said. |
| 6709 | /// |
| 6710 | /// goingson opens one at 14 sites and Balanced Breakfast at 9, on top of |
| 6711 | /// 680 lines of generic menu machinery between `components.js` and |
| 6712 | /// `context-menus.js`. |
| 6713 | /// |
| 6714 | /// A field rather than a role in the run, because a menu is not on the |
| 6715 | /// line. The run is what the row draws; this is what it holds back until |
| 6716 | /// the host asks, and no renderer draws it in sequence with the primary. |
| 6717 | pub menu: , |
| 6718 | /// What ticking this row calls, if ticking it is the write. |
| 6719 | /// |
| 6720 | /// Part of it. [`selected`](Self::selected) says whether the row is ticked |
| 6721 | /// and whether it can be, and that was the whole story for a bulk |
| 6722 | /// checkbox, whose tick is client state feeding a later action. A |
| 6723 | /// checklist is the other case: the tick *is* the write, and it is the |
| 6724 | /// only affordance the screen offers for it. Described without this, the |
| 6725 | /// port drew the tick inert and put the toggle on a button beside it, |
| 6726 | /// which is a user clicking a button next to a checkbox that ignores |
| 6727 | /// clicks. |
| 6728 | /// |
| 6729 | /// Two fields rather than a `Selection` struct, matching how |
| 6730 | /// [`activate`](Self::activate) sits beside [`current`](Self::current): |
| 6731 | /// state and behaviour are separate facts about the row. They do have to |
| 6732 | /// agree — a `toggle` with no [`selected`](Self::selected) is a route on a |
| 6733 | /// control nothing draws — and [`Row::toggling`] is the constructor that |
| 6734 | /// makes them agree. |
| 6735 | pub toggle: , |
| 6736 | /// What this row's tick contributes to the screen's selection. |
| 6737 | /// |
| 6738 | /// [`selected`](Self::selected) says the row can be ticked; this says what |
| 6739 | /// ticking it *means*, which is the half that was missing. A set of ticks |
| 6740 | /// with nothing in them is not a selection, so a renderer holding |
| 6741 | /// [`Screen::selection`] holds these. |
| 6742 | /// |
| 6743 | /// `value` rather than `id`, matching [`Choice::value`]: throughout this |
| 6744 | /// vocabulary it is the word for what a control contributes when it is |
| 6745 | /// chosen, and a row's tick is the same kind of fact. |
| 6746 | /// |
| 6747 | /// A selectable row without one is the dead affordance this member exists |
| 6748 | /// to end, and [`Row::ticking`] is the constructor that cannot produce it. |
| 6749 | /// It is not enforced here, for [`toggle`](Self::toggle)'s reason: a |
| 6750 | /// description layer that refused to hold a half-built row would refuse it |
| 6751 | /// at the moment the app is still building it. |
| 6752 | /// |
| 6753 | /// [`Choice::value`]: Choice::value |
| 6754 | /// [`Screen::selection`]: Screen::selection |
| 6755 | pub value: , |
| 6756 | /// The address a link reaches this row by. |
| 6757 | /// |
| 6758 | /// Not [`value`](Self::value): a value is unique within its list and an |
| 6759 | /// address is unique in the document, because an address is what a reader |
| 6760 | /// copies and sends on. |
| 6761 | /// |
| 6762 | /// `None` on nearly every row, and a renderer that finds none draws what it |
| 6763 | /// drew before. |
| 6764 | /// |
| 6765 | pub address: , |
| 6766 | /// How far into a hierarchy this row sits. 0 is top level. |
| 6767 | /// |
| 6768 | /// A hierarchy of rows is a **flat list of rows each saying how deep it |
| 6769 | /// is**, not rows holding rows. audiofiles' tags are dotted -- |
| 6770 | /// `drums.kick`, `genre.house` -- and the shipped sidebar builds a real |
| 6771 | /// tree with a recursive draw over it; described the other way, every tag |
| 6772 | /// was one row at its full dotted path and a vault with two hundred tags |
| 6773 | /// drew a wall where the shipped one drew an outline. |
| 6774 | /// |
| 6775 | /// Flat rather than nested for the reason the decision turned on: a |
| 6776 | /// renderer that has never heard of this member still draws the list it |
| 6777 | /// drew before, in order, with nothing missing. `Row::children` would have |
| 6778 | /// made every renderer's list walk recursive on pain of dropping rows |
| 6779 | /// silently, and a description that a renderer can only half-implement by |
| 6780 | /// losing content is not one this vocabulary should be able to say. |
| 6781 | /// |
| 6782 | /// A row deeper than the row above it is that row's child. Nothing checks |
| 6783 | /// that, and nothing should: a list whose first row is at depth 3 is a |
| 6784 | /// branch shown on its own, which is a screen somebody will write. |
| 6785 | /// |
| 6786 | /// Not [`Node::Heading`]'s level, which says how far down the *document* a |
| 6787 | /// title sits. That is depth in prose; this is containment in a set. |
| 6788 | pub depth: Nesting, |
| 6789 | /// Whether this row has a disclosure, and whether it is currently open. |
| 6790 | /// |
| 6791 | /// `None` means no disclosure at all and no affordance drawn -- the state |
| 6792 | /// of every row written before this member existed, and the right answer |
| 6793 | /// for a leaf. `Some(false)` is a closed branch and `Some(true)` an open |
| 6794 | /// one. Three states in one field for [`selected`](Self::selected)'s |
| 6795 | /// reason: a bool cannot tell a leaf from a branch that happens to be shut. |
| 6796 | /// |
| 6797 | /// # What a renderer does with it, and what it does not |
| 6798 | /// |
| 6799 | /// It draws a disclosure, **as a separate hit target from the label**. The |
| 6800 | /// shipped egui sidebar already separates them deliberately, and it is the |
| 6801 | /// behaviour being described rather than an improvement on it: pressing a |
| 6802 | /// tag filters by it, pressing its chevron does not. |
| 6803 | /// |
| 6804 | /// A closed row's descendants -- the rows after it at a greater |
| 6805 | /// [`depth`](Self::depth), up to the next row at its own depth or less -- |
| 6806 | /// are not drawn. That is computable from the flat list, which is what |
| 6807 | /// makes the flat list enough. |
| 6808 | /// |
| 6809 | /// # The gesture is the renderer's |
| 6810 | /// |
| 6811 | /// This says where the outline starts, not where it stays. |
| 6812 | /// [`Region::showing_at_most_one`] states the same rule for a disclosure |
| 6813 | /// around a region and every word of it applies here: what a second press |
| 6814 | /// does, and whether a later visit comes back to the same shape, is the |
| 6815 | /// renderer's. A description that had to be re-asked for to fold a branch |
| 6816 | /// would put a round trip on a gesture that changes nothing anyone else can |
| 6817 | /// observe. |
| 6818 | /// |
| 6819 | /// So there is no route beside this the way [`toggle`](Self::toggle) sits |
| 6820 | /// beside [`selected`](Self::selected). A tick is a write and has to reach |
| 6821 | /// the app; folding a branch is the reader tidying their own view. If a |
| 6822 | /// screen turns up whose open branches are app state worth persisting, that |
| 6823 | /// is the consumer that earns the third member, and it has not turned up. |
| 6824 | /// |
| 6825 | /// [`Region::showing_at_most_one`]: Region::showing_at_most_one |
| 6826 | pub open: , |
| 6827 | /// Which side of a change this row is on, when the table is a diff. |
| 6828 | /// |
| 6829 | /// Decision `19d7602d` (2026-09-02, option d). A diff is a table of lines, |
| 6830 | /// and the only thing the vocabulary was missing was a way for a line to |
| 6831 | /// say whether it was added, removed or unchanged. So it is a member here |
| 6832 | /// rather than a `Node::Diff` carrying git's data model into a vocabulary |
| 6833 | /// shared by a task manager and a sample browser. |
| 6834 | /// |
| 6835 | /// `None` is not [`Change::Context`]. `None` means this table is not a diff |
| 6836 | /// and no renderer should tint it; `Some(Context)` means it is a diff and |
| 6837 | /// this line did not change. Two facts, and a table of ordinary rows must |
| 6838 | /// not read as a diff whose every line is context. |
| 6839 | /// |
| 6840 | /// What a renderer does with it is [`Change`]'s `Intent` impl and its own |
| 6841 | /// palette. A terminal with two colours to spend gets the same three |
| 6842 | /// answers a browser does. |
| 6843 | pub change: , |
| 6844 | |
| 6845 | |
| 6846 | |
| 6847 | /// A row with only its primary text. |
| 6848 | /// |
| 6849 | /// An empty string is an empty run rather than a run holding an empty |
| 6850 | /// string, so `Row::new("")` and [`Row::default`] are the same value. Same |
| 6851 | /// rule as [`Cell::new`], and for the same reason. |
| 6852 | |
| 6853 | let primary = primary.into; |
| 6854 | Self |
| 6855 | cells: if primary.is_empty |
| 6856 | Vecnew |
| 6857 | else |
| 6858 | vec! |
| 6859 | key: Role, |
| 6860 | content: vec!, |
| 6861 | flow: None, |
| 6862 | priority: None, |
| 6863 | span: 1, |
| 6864 | ] |
| 6865 | , |
| 6866 | ..Selfdefault |
| 6867 | |
| 6868 | |
| 6869 | |
| 6870 | /// Put this row at an indent level, 0 being top level. |
| 6871 | /// |
| 6872 | /// [`depth`](Self::depth) is the member and this is how a call site says |
| 6873 | /// it. audiofiles' tag sidebar counts the dots in `drums.kick` and hands |
| 6874 | /// a Nesting built from it here. |
| 6875 | |
| 6876 | pub const |
| 6877 | self.depth = depth; |
| 6878 | self |
| 6879 | |
| 6880 | |
| 6881 | /// Give this row a disclosure, and say whether it is open. |
| 6882 | /// |
| 6883 | /// A branch. Without it the row is a leaf and draws no chevron, which is |
| 6884 | /// what every row written before [`open`](Self::open) existed is. Which |
| 6885 | /// rows it folds away is [`open`](Self::open)'s to say and the renderer's |
| 6886 | /// to do. |
| 6887 | |
| 6888 | pub const |
| 6889 | self.open = Some; |
| 6890 | self |
| 6891 | |
| 6892 | |
| 6893 | /// Something else that can be done to this row, not shown inline. |
| 6894 | |
| 6895 | |
| 6896 | self.menu.push; |
| 6897 | self |
| 6898 | |
| 6899 | |
| 6900 | /// Offer these acts on the row itself, rather than inside a part. |
| 6901 | /// |
| 6902 | /// The plural of [`offers`](Self::offers). A table row got this pair on |
| 6903 | /// 2026-09-02 and a list row did not, though both already carried the same |
| 6904 | /// facts under the same names, so a screen mixing a list and a table had to |
| 6905 | /// write one of them in the chain and the other by assignment. That gap is |
| 6906 | /// what the 2026-09-05 collapse closed by making them one type. |
| 6907 | /// |
| 6908 | /// The whole menu at once, because the measured sites hand over a `Vec` |
| 6909 | /// they already have: audiofiles' file list builds one conditionally on |
| 6910 | /// how many rows are chosen, and pushing it act by act would take the |
| 6911 | /// condition apart. |
| 6912 | /// |
| 6913 | |
| 6914 | |
| 6915 | self.menu.extend; |
| 6916 | self |
| 6917 | |
| 6918 | |
| 6919 | /// This is the row being shown elsewhere. |
| 6920 | /// |
| 6921 | /// Was reachable only by assigning the field, which is why a row built by |
| 6922 | /// a chain had to fall out of the chain to say it. A table row carried the |
| 6923 | /// same fact under the same name and got its builder first; this is the |
| 6924 | /// other half of that fix. |
| 6925 | /// |
| 6926 | /// The app's own pointer into a set, as distinct from |
| 6927 | /// [`selected`](Self::selected), which is the user's tick. Conflating them |
| 6928 | /// is what stopped a screen with bulk actions describing its checkboxes at |
| 6929 | /// all, and that argument is on the field. |
| 6930 | |
| 6931 | pub const |
| 6932 | self.current = current; |
| 6933 | self |
| 6934 | |
| 6935 | |
| 6936 | /// How much of this row's set is done. |
| 6937 | |
| 6938 | |
| 6939 | self.set; |
| 6940 | self |
| 6941 | |
| 6942 | |
| 6943 | /// A tick that is the write, in the state it is currently in. |
| 6944 | /// |
| 6945 | /// Sets [`selected`](Self::selected) and [`toggle`](Self::toggle) together, |
| 6946 | /// because a route on a tick nothing draws is the one way the two fields can |
| 6947 | /// disagree. A checklist item is what this is for; a bulk checkbox sets |
| 6948 | /// `selected` alone and keeps its meaning as client state. |
| 6949 | |
| 6950 | |
| 6951 | self.selected = Some; |
| 6952 | self.toggle = Some; |
| 6953 | self |
| 6954 | |
| 6955 | |
| 6956 | /// Supporting text under the primary. |
| 6957 | |
| 6958 | |
| 6959 | let node = match text.into |
| 6960 | Text => text, |
| 6961 | Rich => rich, |
| 6962 | ; |
| 6963 | self.set; |
| 6964 | self |
| 6965 | |
| 6966 | |
| 6967 | /// A short trailing fact. |
| 6968 | |
| 6969 | |
| 6970 | self.set; |
| 6971 | self |
| 6972 | |
| 6973 | |
| 6974 | /// Add a token, chaining. |
| 6975 | |
| 6976 | |
| 6977 | self.cells.push |
| 6978 | key: Role, |
| 6979 | content: vec!, |
| 6980 | flow: None, |
| 6981 | priority: None, |
| 6982 | span: 1, |
| 6983 | ; |
| 6984 | self |
| 6985 | |
| 6986 | |
| 6987 | /// Make the row tickable, and say whether it is ticked. |
| 6988 | /// |
| 6989 | /// A row is not selectable until something says so, which is what keeps a |
| 6990 | /// checkbox off every list in the app. |
| 6991 | /// |
| 6992 | /// Says nothing about what the tick contributes, so on a screen with a |
| 6993 | /// [`selection`](Screen::selection) it draws a box that joins no set. Reach |
| 6994 | /// for [`ticking`](Self::ticking) instead; this stays for the screens whose |
| 6995 | /// tick is the write, beside [`toggling`](Self::toggling). |
| 6996 | |
| 6997 | pub const |
| 6998 | self.selected = Some; |
| 6999 | self |
| 7000 | |
| 7001 | |
| 7002 | /// Make the row part of a live selection under this value, and say whether |
| 7003 | /// it is chosen. |
| 7004 | /// |
| 7005 | /// The same pairing [`ticking`](Self::ticking) makes one member along: a |
| 7006 | /// row that can be chosen and names nothing is a dead affordance, because |
| 7007 | /// the value is what the app reads back to know which row the press was |
| 7008 | /// about. |
| 7009 | |
| 7010 | |
| 7011 | self.value = Some; |
| 7012 | self.chosen = Some; |
| 7013 | self |
| 7014 | |
| 7015 | |
| 7016 | /// Make the row tickable under this value, and say whether it is ticked. |
| 7017 | /// |
| 7018 | /// Sets [`selected`](Self::selected) and [`value`](Self::value) together, |
| 7019 | /// which is the pair a screen's [`selection`](Screen::selection) needs. |
| 7020 | /// The two halves exist separately for [`toggling`](Self::toggling)'s |
| 7021 | /// reason — state and identity are different facts about the row — and |
| 7022 | /// this is the constructor that stops them being written apart. |
| 7023 | |
| 7024 | |
| 7025 | self.selected = Some; |
| 7026 | self.value = Some; |
| 7027 | self |
| 7028 | |
| 7029 | |
| 7030 | /// Name the row without making it tickable. |
| 7031 | /// |
| 7032 | /// Identity and tickability are different facts about a row, and |
| 7033 | /// [`ticking`](Self::ticking) writes both because a screen's selection needs |
| 7034 | /// both; a row that is only ever pointed at needs the first alone. |
| 7035 | |
| 7036 | |
| 7037 | self.value = Some; |
| 7038 | self |
| 7039 | |
| 7040 | |
| 7041 | /// Give the row a document address a link can reach it by. |
| 7042 | /// |
| 7043 | /// See [`address`](Self::address) for why it is not |
| 7044 | /// [`identified`](Self::identified), and write the name without the `#`. |
| 7045 | /// |
| 7046 | |
| 7047 | |
| 7048 | self.address = Some; |
| 7049 | self |
| 7050 | |
| 7051 | |
| 7052 | /// The route selecting this row. |
| 7053 | |
| 7054 | |
| 7055 | self.activate = Some; |
| 7056 | self |
| 7057 | |
| 7058 | |
| 7059 | /// A control acting on this row. |
| 7060 | |
| 7061 | |
| 7062 | self.cells.push |
| 7063 | key: Role, |
| 7064 | content: vec!, |
| 7065 | flow: None, |
| 7066 | priority: None, |
| 7067 | span: 1, |
| 7068 | ; |
| 7069 | self |
| 7070 | |
| 7071 | |
| 7072 | /// Anything in this row, under the role it takes. |
| 7073 | /// |
| 7074 | /// The general form the constructors above are shorthands for, and the |
| 7075 | /// point of the model: a link in a row and a figure in a row became |
| 7076 | /// sayable at once, where each was previously a |
| 7077 | /// [`layout::RowPart`] variant, a member here, a renderer arm and a |
| 7078 | /// release. |
| 7079 | /// |
| 7080 | /// Appends rather than replacing, so a row can hold two of a role. The |
| 7081 | /// named constructors keep the single-valued roles single-valued, which is |
| 7082 | /// what their call sites already meant. |
| 7083 | /// |
| 7084 | /// # Panics |
| 7085 | /// |
| 7086 | /// If the node is not a leaf. A row is an inline run, so what goes in it |
| 7087 | /// has to be drawable on one wrapped line without the renderer knowing what |
| 7088 | /// it is -- the constrained-consumer bound, biting at a call site rather |
| 7089 | /// than in a doc comment. Same assertion as [`Cell::part`]. |
| 7090 | |
| 7091 | |
| 7092 | assert! |
| 7093 | node.containment == Text, |
| 7094 | "a row is an inline run and holds leaves; {node:?} holds {:?}", |
| 7095 | node.containment |
| 7096 | ; |
| 7097 | self.cells.push |
| 7098 | key: Role, |
| 7099 | content: vec!, |
| 7100 | flow: None, |
| 7101 | priority: None, |
| 7102 | span: 1, |
| 7103 | ; |
| 7104 | self |
| 7105 | |
| 7106 | |
| 7107 | /// Let the part just added take two lines instead of one. |
| 7108 | /// |
| 7109 | /// Applies to the last part in the run, which is the one the call before it |
| 7110 | /// pushed: `Row::new(title).relaxed()` relaxes the title, and |
| 7111 | /// `.secondary(body).relaxed()` relaxes the body. Chaining is what makes |
| 7112 | /// "the last one" unambiguous at a call site, and it is why this is a |
| 7113 | /// method here rather than an argument on every constructor. |
| 7114 | /// |
| 7115 | /// A no-op on an empty run rather than a panic. `Row::new("")` is |
| 7116 | /// deliberately an empty run, so a caller that relaxes a title it turned |
| 7117 | /// out not to have is asking for nothing and gets nothing. |
| 7118 | /// |
| 7119 | /// What two lines means is [`layout::Flow::Relaxed`]'s to say, and what it |
| 7120 | /// costs each renderer is in that type's docs. |
| 7121 | |
| 7122 | |
| 7123 | if let Some = self.cells.last_mut |
| 7124 | part.flow = Some; |
| 7125 | |
| 7126 | self |
| 7127 | |
| 7128 | |
| 7129 | /// Say what the part just added is worth when the run does not fit. |
| 7130 | /// |
| 7131 | /// Applies to the last part in the run, the same way |
| 7132 | /// [`relaxed`](Self::relaxed) does. Without it the part is worth whatever |
| 7133 | /// its role is worth. |
| 7134 | /// |
| 7135 | /// A no-op on an empty run rather than a panic, for `relaxed`'s reason. |
| 7136 | |
| 7137 | |
| 7138 | if let Some = self.cells.last_mut |
| 7139 | part.priority = Some; |
| 7140 | |
| 7141 | self |
| 7142 | |
| 7143 | |
| 7144 | /// Set the one part taking a role, replacing it if it is already there. |
| 7145 | /// |
| 7146 | /// For the roles that are single-valued at every call site that has ever |
| 7147 | /// existed: the primary, the supporting line, the trailing fact, the bar. |
| 7148 | /// Building a row that calls `.meta` twice meant the second one won when |
| 7149 | /// `meta` was an `Option`, and it still does. |
| 7150 | |
| 7151 | match self |
| 7152 | .cells |
| 7153 | .iter_mut |
| 7154 | .find |
| 7155 | |
| 7156 | Some => cell.content = vec!, |
| 7157 | None => self.cells.push |
| 7158 | key: Role, |
| 7159 | content: vec!, |
| 7160 | flow: None, |
| 7161 | priority: None, |
| 7162 | span: 1, |
| 7163 | , |
| 7164 | |
| 7165 | |
| 7166 | |
| 7167 | /// A row of a declared table, its cells in column order. |
| 7168 | /// |
| 7169 | /// Was the table row's own `new` before the 2026-09-05 collapse. It is a separate |
| 7170 | /// constructor from [`new`](Self::new) rather than an overload of it |
| 7171 | /// because the two say different things: `Row::new` names the primary |
| 7172 | /// column of the default set, and this answers a column list positionally. |
| 7173 | |
| 7174 | |
| 7175 | Self |
| 7176 | cells: values |
| 7177 | .into_iter |
| 7178 | .enumerate |
| 7179 | .map |
| 7180 | let mut cell = cell.into; |
| 7181 | cell.key = Column; |
| 7182 | cell |
| 7183 | |
| 7184 | .collect, |
| 7185 | ..Selfdefault |
| 7186 | |
| 7187 | |
| 7188 | |
| 7189 | /// A cell naming the column it sits in, chaining. |
| 7190 | /// |
| 7191 | /// Safer than counting to a column in every way but one, which is why |
| 7192 | /// [`Table::row`] carries a debug assertion: a name no column has is |
| 7193 | /// dropped, so a typo renders an empty column rather than failing to |
| 7194 | /// compile. The row and the column list are usually written in different |
| 7195 | /// functions, so nothing above `Table::row` has both in hand to check. |
| 7196 | |
| 7197 | |
| 7198 | let mut cell = cell.into; |
| 7199 | cell.key = Named; |
| 7200 | self.cells.push; |
| 7201 | self |
| 7202 | |
| 7203 | |
| 7204 | /// A cell in the next column along, chaining. |
| 7205 | |
| 7206 | |
| 7207 | let at = self.cells.len; |
| 7208 | let mut cell = cell.into; |
| 7209 | cell.key = Column; |
| 7210 | self.cells.push; |
| 7211 | self |
| 7212 | |
| 7213 | |
| 7214 | /// Which side of a change this row is on, when the table is a diff. |
| 7215 | |
| 7216 | pub const |
| 7217 | self.change = Some; |
| 7218 | self |
| 7219 | |
| 7220 | |
| 7221 | /// The parts taking one role, in order. |
| 7222 | |
| 7223 | self.cells |
| 7224 | .iter |
| 7225 | .filter |
| 7226 | .flat_map |
| 7227 | |
| 7228 | |
| 7229 | /// The row's primary text. |
| 7230 | /// |
| 7231 | /// A row built the ordinary way has one primary part and answers its |
| 7232 | /// string; one that was given two answers both, joined, in order. |
| 7233 | |
| 7234 | |
| 7235 | self.role |
| 7236 | .filter_map |
| 7237 | Text | Link => Some, |
| 7238 | _ => None, |
| 7239 | |
| 7240 | . |
| 7241 | .join |
| 7242 | |
| 7243 | |
| 7244 | /// The controls the row shows. |
| 7245 | /// |
| 7246 | /// The same service [`primary`](Self::primary) does, for the member the run |
| 7247 | /// replaced. `actions` was a `Vec<Act>` before the run, and every consumer |
| 7248 | /// that read it now writes the same three lines: filter the run by role, |
| 7249 | /// match the one node kind that can be there, and collect. goingson wrote |
| 7250 | /// them twice in one file the day the member went away. |
| 7251 | /// |
| 7252 | /// Not what the row *offers*: that is [`menu`](Self::menu), which is held |
| 7253 | /// back until the host asks for it and is not on the line. |
| 7254 | /// Whether a control in this row's run or menu carries this |
| 7255 | /// [`Act::id`](Act::id). |
| 7256 | |
| 7257 | |
| 7258 | self.menu.iter.any |
| 7259 | || self |
| 7260 | .cells |
| 7261 | .iter |
| 7262 | .any |
| 7263 | |
| 7264 | |
| 7265 | |
| 7266 | self.role |
| 7267 | .filter_map |
| 7268 | Act => Some, |
| 7269 | _ => None, |
| 7270 | |
| 7271 | |
| 7272 | |
| 7273 | /// Every kind of time-derived readout in this row's run, added to `found`. |
| 7274 | |
| 7275 | for cell in &self.cells |
| 7276 | for node in &cell.content |
| 7277 | node.clocks_into; |
| 7278 | |
| 7279 | |
| 7280 | |
| 7281 | |
| 7282 | /// The tags the row shows. |
| 7283 | /// |
| 7284 | /// [`acts`](Self::acts)' counterpart, for the same reason. |
| 7285 | |
| 7286 | self.role |
| 7287 | .filter_map |
| 7288 | Token => Some, |
| 7289 | _ => None, |
| 7290 | |
| 7291 | |
| 7292 | |
| 7293 | |
| 7294 | /// One cell of a table row. |
| 7295 | /// |
| 7296 | /// A cell was a `String` until then, so a table whose rows carry a control |
| 7297 | /// could not be described at all and had to become a column-less table, |
| 7298 | /// losing its headers -- which is what the MNW server's |
| 7299 | /// SSH-keys tab did, and why it read worse than the Askama original it replaced. |
| 7300 | /// |
| 7301 | /// # Why the acts sit on the cell and not on the row |
| 7302 | /// |
| 7303 | /// Counted across MNW's templates, 30 table rows carry a control. 25 put it |
| 7304 | /// alone in the last cell, which a row-level `actions` list would have covered. |
| 7305 | /// The other five put it *beside a value*: `project_content`'s position cell is |
| 7306 | /// the number plus two reorder arrows, `project_synckit`'s slug cell is the slug |
| 7307 | /// plus "Set slug", `promo_codes_list`'s use count is a number that is itself |
| 7308 | /// the button opening the redemptions. A row-level list renders as an appended |
| 7309 | /// cell and cannot say any of those, and neither can an actions *column*, since |
| 7310 | /// a column is a column. The control belongs where it actually is. |
| 7311 | /// |
| 7312 | /// An empty [`value`](Self::value) with acts is the common case, and |
| 7313 | /// [`Cell::acts`] is the constructor for it. That is the trailing actions cell |
| 7314 | /// the markup already writes an empty `<th>` for. |
| 7315 | /// |
| 7316 | /// A `Vec<Act>` and not a node: the ruling that a row holds no nodes holds |
| 7317 | /// here for the same reason. Acts carry their own tone, state and |
| 7318 | /// confirmation, and that is the whole of what these cells hold. |
| 7319 | /// Which column a cell answers to. |
| 7320 | /// |
| 7321 | /// The 2026-09-05 collapse: a list's parts were addressed by role and a table's |
| 7322 | /// values by column, and those were the same operation under two spellings, a |
| 7323 | /// lookup into a declared key set. The only difference was who declares the |
| 7324 | /// keys, and now that a list declares its own the difference is gone. |
| 7325 | |
| 7326 | |
| 7327 | /// A column of the default set, which is what a container that declares no |
| 7328 | /// columns gets. |
| 7329 | /// |
| 7330 | /// [`layout::RowPart`]'s six variants are that set. A container spelled as |
| 7331 | /// a list is a table over them, which is why `list { row "x" { secondary |
| 7332 | /// "y" } }` still says what it always said. |
| 7333 | Role, |
| 7334 | /// A declared column, by position, already resolved. |
| 7335 | Column, |
| 7336 | /// A declared column, by name, pending resolution by [`Table::row`]. |
| 7337 | /// |
| 7338 | /// This replaces the private staging vector a table row used to carry. A named |
| 7339 | /// cell now sits in the row with the others and says it is unresolved, |
| 7340 | /// rather than living in a second collection that had to be drained. |
| 7341 | Named, |
| 7342 | |
| 7343 | |
| 7344 | |
| 7345 | |
| 7346 | SelfRole |
| 7347 | |
| 7348 | |
| 7349 | |
| 7350 | /// One cell: a run of leaf nodes, addressed by a key. |
| 7351 | /// |
| 7352 | /// **The 2026-09-05 collapse merged `Part` into this type.** A part was a cell |
| 7353 | /// that carried its own role because a list had no columns to carry it; a cell |
| 7354 | /// was a part whose column carried the role instead. One type now, with |
| 7355 | /// [`key`](Self::key) saying which column it answers to. |
| 7356 | /// |
| 7357 | /// A cell was a `String` before that, so a table whose rows carry a control |
| 7358 | /// could not be described at all and had to become a column-less table, losing |
| 7359 | /// its headers, which is what the MNW server's SSH-keys tab did and why it |
| 7360 | /// read worse than the Askama original it replaced. |
| 7361 | /// |
| 7362 | /// # Why the acts sit on the cell and not on the row |
| 7363 | /// |
| 7364 | /// Counted across MNW's templates, 30 table rows carry a control. 25 put it |
| 7365 | /// alone in the last cell, which a row-level `actions` list would have covered. |
| 7366 | /// The other five put it *beside a value*: `project_content`'s position cell is |
| 7367 | /// the number plus two reorder arrows, `project_synckit`'s slug cell is the slug |
| 7368 | /// plus "Set slug", `promo_codes_list`'s use count is a number that is itself |
| 7369 | /// the button opening the redemptions. A row-level list renders as an appended |
| 7370 | /// cell and cannot say any of those, and neither can an actions *column*, since |
| 7371 | /// a column is a column. The control belongs where it actually is. |
| 7372 | |
| 7373 | |
| 7374 | /// What is in it, in order. |
| 7375 | /// |
| 7376 | /// An inline run: every entry is a leaf, so the whole cell is drawable on |
| 7377 | /// one wrapped line without a renderer knowing what is in it. That is the |
| 7378 | /// bound, and [`Cell::part`] is where it is enforced. It is also what |
| 7379 | /// [`CellKey::Role`] leans on: several tokens in one row are one cell |
| 7380 | /// holding several [`Node::Token`]s, not several cells fighting for a key. |
| 7381 | /// |
| 7382 | /// The run is what makes a meter in a cell or a figure in a cell sayable |
| 7383 | /// without a member each. |
| 7384 | pub content: , |
| 7385 | /// Which column this cell answers to. |
| 7386 | pub key: CellKey, |
| 7387 | /// How much vertical room it may take, or `None` for its column's. |
| 7388 | /// |
| 7389 | /// An override rather than a duplicate: the column states the default and a |
| 7390 | /// cell may narrow it. BB clamps a feed row's title while its excerpt wraps |
| 7391 | /// freely underneath, which is two columns rather than one override, but a |
| 7392 | /// long value in one row of an otherwise tight column is the case that |
| 7393 | /// keeps this here. |
| 7394 | pub flow: , |
| 7395 | /// What it is worth when the row does not fit, or `None` for its column's. |
| 7396 | /// |
| 7397 | /// `None` means the description did not say, and the key answers instead: |
| 7398 | /// [`layout::RowPart::priority`] for a role, [`Column::priority`] for a |
| 7399 | /// declared column. There is no global default worth having, which is why |
| 7400 | /// this is an `Option` rather than a defaulted value. |
| 7401 | pub priority: , |
| 7402 | /// How many columns this cell covers. 1 is one column. |
| 7403 | /// |
| 7404 | /// **Defined and unread until the 2D work (`b1d4c5d7`).** It is here so |
| 7405 | /// spanning does not cost a second breaking change; nothing honours it yet, |
| 7406 | /// and a renderer meeting a value above 1 today should draw it as 1. |
| 7407 | pub span: u16, |
| 7408 | |
| 7409 | |
| 7410 | |
| 7411 | /// An empty cell in the primary column, covering one column. |
| 7412 | /// |
| 7413 | /// `span` is 1 rather than 0 here, which is why this is written out: a |
| 7414 | /// derived `Default` would produce a cell covering no columns, and nothing |
| 7415 | /// downstream would say so. |
| 7416 | |
| 7417 | Self |
| 7418 | content: Vecnew, |
| 7419 | key: default, |
| 7420 | flow: None, |
| 7421 | priority: None, |
| 7422 | span: 1, |
| 7423 | |
| 7424 | |
| 7425 | |
| 7426 | |
| 7427 | |
| 7428 | /// A cell holding text. |
| 7429 | /// |
| 7430 | /// An empty string is an empty run rather than a run holding an empty |
| 7431 | /// string, so an actions-only cell built through [`acts`](Self::acts) and |
| 7432 | /// one built as `Cell::new("").act(..)` are the same value. |
| 7433 | |
| 7434 | let value = value.into; |
| 7435 | Self |
| 7436 | key: default, |
| 7437 | flow: None, |
| 7438 | priority: None, |
| 7439 | span: 1, |
| 7440 | content: if value.is_empty |
| 7441 | Vecnew |
| 7442 | else |
| 7443 | vec! |
| 7444 | , |
| 7445 | |
| 7446 | |
| 7447 | |
| 7448 | /// A cell holding one tag and no text. |
| 7449 | /// |
| 7450 | /// What a status column is: the cell is the badge. `Cell::new("")` with a |
| 7451 | /// token would say the same thing and reads as an oversight. |
| 7452 | |
| 7453 | Self |
| 7454 | key: default, |
| 7455 | flow: None, |
| 7456 | priority: None, |
| 7457 | span: 1, |
| 7458 | content: vec!, |
| 7459 | |
| 7460 | |
| 7461 | |
| 7462 | /// A tag in this cell, chaining. |
| 7463 | |
| 7464 | |
| 7465 | self.content.push; |
| 7466 | self |
| 7467 | |
| 7468 | |
| 7469 | /// A cell holding controls and no text. |
| 7470 | |
| 7471 | Self |
| 7472 | key: default, |
| 7473 | flow: None, |
| 7474 | priority: None, |
| 7475 | span: 1, |
| 7476 | content: actions.into_iter.map.collect, |
| 7477 | |
| 7478 | |
| 7479 | |
| 7480 | /// A control in this cell, chaining. |
| 7481 | |
| 7482 | |
| 7483 | self.content.push; |
| 7484 | self |
| 7485 | |
| 7486 | |
| 7487 | /// How much of a set this cell's row is through, chaining. |
| 7488 | /// |
| 7489 | /// [`Row::meter`]'s counterpart on the other container, and it is a setting |
| 7490 | /// for that one's reason: a proportion is a fact about the thing the cell is |
| 7491 | /// in rather than a leaf beside its text. `Node::Meter` has no member of its |
| 7492 | /// own and cannot get one -- `meter` is a setting here and on `Row`, so the |
| 7493 | /// name is taken -- which is what made a table's progress column reach for a |
| 7494 | /// supplier before this existed. goingson's task list is the site. |
| 7495 | |
| 7496 | |
| 7497 | self.content.push; |
| 7498 | self |
| 7499 | |
| 7500 | |
| 7501 | /// Where this cell's value goes. |
| 7502 | /// |
| 7503 | /// The value becomes the link. A cell with no value and an `activate` is a |
| 7504 | /// link with nothing to press, so give it text. |
| 7505 | /// |
| 7506 | /// Under the run this rewrites the leading text into a [`Node::Link`] |
| 7507 | /// rather than setting a member beside it, which is the same fact said once |
| 7508 | /// instead of as a pair of fields that could disagree. A cell with no text |
| 7509 | /// to link gains nothing, because a link with no label is a control nothing |
| 7510 | /// draws. |
| 7511 | |
| 7512 | |
| 7513 | if let Some = self |
| 7514 | .content |
| 7515 | .iter_mut |
| 7516 | .find |
| 7517 | && let Text = first |
| 7518 | |
| 7519 | *first = Link |
| 7520 | text: take, |
| 7521 | action, |
| 7522 | ; |
| 7523 | |
| 7524 | self |
| 7525 | |
| 7526 | |
| 7527 | /// Anything in this cell, chaining. |
| 7528 | /// |
| 7529 | /// The general form the five constructors above are shorthands for, and the |
| 7530 | /// whole point of the model: a meter in a cell, a figure in a cell and a |
| 7531 | /// second linked value in a cell all became sayable at once, where each was |
| 7532 | /// previously a member, three renderer arms and a release. |
| 7533 | /// |
| 7534 | /// # Panics |
| 7535 | /// |
| 7536 | /// If the node is not a leaf. A cell is an inline run, so what goes in it |
| 7537 | /// has to be drawable on one wrapped line without the renderer knowing what |
| 7538 | /// it is -- that is the constrained-consumer bound, and this is where it |
| 7539 | /// bites at a call site rather than in a doc comment. |
| 7540 | |
| 7541 | |
| 7542 | assert! |
| 7543 | node.containment == Text, |
| 7544 | "a cell is an inline run and holds leaves; {node:?} holds \ |
| 7545 | {:?}", |
| 7546 | node.containment |
| 7547 | ; |
| 7548 | self.content.push; |
| 7549 | self |
| 7550 | |
| 7551 | |
| 7552 | /// What this cell is worth when the row does not fit. |
| 7553 | /// |
| 7554 | /// [`priority`](Self::priority) if the description said, and otherwise the |
| 7555 | /// key's: [`layout::RowPart::priority`] for a role. |
| 7556 | /// |
| 7557 | /// **A cell keyed to a declared column cannot answer alone**, and reports |
| 7558 | /// [`layout::Priority::Essential`] rather than guessing. Its column holds |
| 7559 | /// the real answer, and a renderer drawing a declared table should read |
| 7560 | /// [`Column::priority`] instead of calling this. Essential is the safe end |
| 7561 | /// of the scale on purpose: a caller that forgets to consult the column |
| 7562 | /// draws a cell it could have dropped, rather than dropping one it should |
| 7563 | /// have drawn. That asymmetry is the collapse being honest -- a table row |
| 7564 | /// was always meaningless without its columns, and now it says so. |
| 7565 | |
| 7566 | |
| 7567 | self.priority.unwrap_or_else |
| 7568 | Role => role.priority, |
| 7569 | Column | Named => Essential, |
| 7570 | |
| 7571 | |
| 7572 | |
| 7573 | /// How much vertical room this cell may take. |
| 7574 | /// |
| 7575 | /// [`flow`](Self::flow) if the description said, and otherwise |
| 7576 | /// [`layout::Flow::Tight`], which is one line and is what every part did |
| 7577 | /// before the field existed. |
| 7578 | |
| 7579 | |
| 7580 | self.flow.unwrap_or_default |
| 7581 | |
| 7582 | |
| 7583 | /// The cell's text, with the parts that are not text left out. |
| 7584 | /// |
| 7585 | /// A cell that is one string answers that string; one that mixes answers |
| 7586 | /// the text between its tags and controls, in order. |
| 7587 | |
| 7588 | |
| 7589 | self.content |
| 7590 | .iter |
| 7591 | .filter_map |
| 7592 | Text | Link => Some, |
| 7593 | _ => None, |
| 7594 | |
| 7595 | . |
| 7596 | .join |
| 7597 | |
| 7598 | |
| 7599 | /// Whether anything in this cell answers a click. |
| 7600 | /// |
| 7601 | /// A badge is not one: it says something and answers nothing, which is why |
| 7602 | /// this asks the tag rather than counting tags. |
| 7603 | |
| 7604 | |
| 7605 | self.content.iter.any |
| 7606 | Act | Link => true, |
| 7607 | Token => tag.kind.interactive && tag.action.is_some, |
| 7608 | _ => false, |
| 7609 | |
| 7610 | |
| 7611 | |
| 7612 | |
| 7613 | |
| 7614 | |
| 7615 | Selfnew |
| 7616 | |
| 7617 | |
| 7618 | |
| 7619 | |
| 7620 | |
| 7621 | Selfnew |
| 7622 | |
| 7623 | |
| 7624 | |
| 7625 | /// What a press on a row of a live selection meant. |
| 7626 | /// |
| 7627 | /// The renderer's half of [`Row::chosen`]: the description says which rows |
| 7628 | /// are chosen and the app owns the set, but *how a press was meant* is the one |
| 7629 | /// part only the renderer can know, because it is the host's idiom and a |
| 7630 | /// different idiom on each. |
| 7631 | /// |
| 7632 | /// Three members, which is what every file manager on every desktop has offered |
| 7633 | /// for thirty years and what audiofiles lost when its list was described. A host |
| 7634 | /// maps its own gesture onto them: |
| 7635 | /// |
| 7636 | /// | | pointer | touch | terminal | |
| 7637 | /// |---|---|---|---| |
| 7638 | /// | [`Only`](Self::Only) | click | tap | Enter | |
| 7639 | /// | [`Also`](Self::Also) | ctrl-click, cmd-click on macOS | long-press | Space | |
| 7640 | /// | [`Through`](Self::Through) | shift-click | drag over a run | none yet | |
| 7641 | /// |
| 7642 | /// The terminal's gap is stated rather than invented around: `quasi_tui::Key` |
| 7643 | /// carries no modifiers, so shift-Enter is not expressible without widening the |
| 7644 | /// key type every host driving that renderer maps onto. |
| 7645 | /// |
| 7646 | /// # What a reader holding both keys means |
| 7647 | /// |
| 7648 | /// Ruled here rather than left to each renderer, which is the whole point of |
| 7649 | /// the type: **shift wins**. Finder and Explorer both read ctrl-shift-click as |
| 7650 | /// "extend the run and keep what was already chosen", which is a fourth member |
| 7651 | /// and has no consumer asking for one. Of the three that exist, taking the run |
| 7652 | /// is nearer to what the reader asked for than toggling the single row they |
| 7653 | /// happened to land on -- and the one thing holding two keys cannot mean is the |
| 7654 | /// plain press. |
| 7655 | /// |
| 7656 | /// # Why this is in the vocabulary at all |
| 7657 | /// |
| 7658 | /// It looks like input state, which every other ruling here has refused to |
| 7659 | /// carry: a description says what is on the screen and never where, never how |
| 7660 | /// wide, never which key. The difference is that this is not the *gesture*, it |
| 7661 | /// is what the gesture **meant**, and the meaning is the same on every host |
| 7662 | /// while the gesture is not. A description that carried "ctrl was held" would |
| 7663 | /// be naming a keyboard; this names an intention a touch host can honour with |
| 7664 | /// no keyboard at all. |
| 7665 | /// |
| 7666 | /// The test it passes and a modifier would not: a terminal can implement it. |
| 7667 | /// |
| 7668 | /// # It travels in the payload, not in the address |
| 7669 | /// |
| 7670 | /// Under [`Node::CHOOSING`], beside whatever else the activation carries. |
| 7671 | |
| 7672 | |
| 7673 | /// This row and nothing else. The plain press, and the default. |
| 7674 | |
| 7675 | Only, |
| 7676 | /// This row as well as whatever was already chosen, or out of it if it was |
| 7677 | /// already in. |
| 7678 | /// |
| 7679 | /// A toggle rather than an add, which is what every file manager does with |
| 7680 | /// ctrl-click and is the only reading that lets a reader correct a |
| 7681 | /// mis-click without starting over. |
| 7682 | Also, |
| 7683 | /// Every row from the one the app is pointing at to this one. |
| 7684 | /// |
| 7685 | /// The range's other end is the app's own -- `Row::current`, or whatever |
| 7686 | /// the app calls its focus -- and is deliberately not carried here. A |
| 7687 | /// renderer that named it would be answering with the row it *drew* as |
| 7688 | /// current, which is the description's answer from one frame ago; the app |
| 7689 | /// holds the live one. |
| 7690 | Through, |
| 7691 | |
| 7692 | |
| 7693 | |
| 7694 | /// The spelling that travels in a payload. |
| 7695 | |
| 7696 | pub const |
| 7697 | match self |
| 7698 | SelfOnly => "only", |
| 7699 | SelfAlso => "also", |
| 7700 | SelfThrough => "through", |
| 7701 | |
| 7702 | |
| 7703 | |
| 7704 | /// What a handler reads back, defaulting to [`Only`](Self::Only). |
| 7705 | /// |
| 7706 | /// **An unknown spelling is [`Only`](Self::Only) rather than an error**, and |
| 7707 | /// that is the same bargain every other read of a submitted value strikes |
| 7708 | /// here: a press that arrives saying something this version does not know is |
| 7709 | /// still a press on a row, and refusing it would break the ordinary act to |
| 7710 | /// protect the extraordinary one. The plain reading is the safe one -- it |
| 7711 | /// chooses the row that was pressed and nothing else. |
| 7712 | |
| 7713 | |
| 7714 | match value |
| 7715 | Some => SelfAlso, |
| 7716 | Some => SelfThrough, |
| 7717 | _ => SelfOnly, |
| 7718 | |
| 7719 | |
| 7720 | |
| 7721 | |
| 7722 | /// A table: the columns, and the rows that answer to them. |
| 7723 | /// |
| 7724 | /// The columns arrive with the table, so a row is never built against a column |
| 7725 | /// list that does not exist yet. That is the rule [`Run::new`] holds for a |
| 7726 | /// shared row's fallback, here for the same reason: a table's invariant is that |
| 7727 | /// cells line up with columns, and the point a row is put in is the only place |
| 7728 | /// with enough context to keep that true. |
| 7729 | /// |
| 7730 | /// # Why a cell is addressed by name |
| 7731 | /// |
| 7732 | /// [`Column`] documented its `name` as "the heading, and the name the cell is |
| 7733 | /// addressed by ... what replaces addressing columns by position" while the row |
| 7734 | /// stayed positional, so the file held both answers at once. Position is the one |
| 7735 | /// that loses. A cell that appears on some rows and not others shifts every cell |
| 7736 | /// after it, so a conditional cell had to be written as a matched pair and |
| 7737 | /// nothing but care kept the pair matched. Under [`Row::at`] an absent cell is an |
| 7738 | /// empty cell in its own column, and a row whose arity depends on a runtime flag |
| 7739 | /// is an ordinary row. |
| 7740 | /// |
| 7741 | /// A row built by [`Row::cells`] is still positional and still works. The two |
| 7742 | /// are not mixed in one row: if a row names any column, the names are the row. |
| 7743 | |
| 7744 | |
| 7745 | columns: , |
| 7746 | rows: , |
| 7747 | more: , |
| 7748 | |
| 7749 | |
| 7750 | |
| 7751 | /// A table with these columns and no rows yet. |
| 7752 | |
| 7753 | Self |
| 7754 | columns: columns.into_iter.collect, |
| 7755 | rows: Vecnew, |
| 7756 | more: None, |
| 7757 | |
| 7758 | |
| 7759 | |
| 7760 | /// Add one column, for a caller building the table a piece at a time. |
| 7761 | /// |
| 7762 | /// Beside [`new`](Self::new) rather than instead of it: a table whose |
| 7763 | /// columns are a literal list still says so in one expression, and a table |
| 7764 | /// assembled by something that accretes (the declared form, which has no |
| 7765 | /// expression to hold a list in) says the same thing one column at a time. |
| 7766 | /// |
| 7767 | /// **Columns before rows.** [`row`](Self::row) resolves a named cell |
| 7768 | /// against the columns the table has when the row arrives, so a column |
| 7769 | /// added afterwards is invisible to every row already pushed. |
| 7770 | |
| 7771 | |
| 7772 | debug_assert! |
| 7773 | self.rows.is_empty, |
| 7774 | "a column added after a row cannot be seen by that row's named cells" |
| 7775 | ; |
| 7776 | self.columns.push; |
| 7777 | self |
| 7778 | |
| 7779 | |
| 7780 | /// Add a row, resolving any cell that named its column. |
| 7781 | /// |
| 7782 | /// A named cell whose column this table does not have is dropped: the |
| 7783 | /// column list is the table's statement of what a row may say, and a row |
| 7784 | /// saying more than that is answered by the columns rather than by |
| 7785 | /// widening them. A column no cell named is empty in this row. |
| 7786 | |
| 7787 | |
| 7788 | if row |
| 7789 | .cells |
| 7790 | .iter |
| 7791 | .any |
| 7792 | |
| 7793 | let placed: = take |
| 7794 | .into_iter |
| 7795 | .filter_map |
| 7796 | Named => Some, |
| 7797 | _ => None, |
| 7798 | |
| 7799 | .collect; |
| 7800 | debug_assert! |
| 7801 | placed |
| 7802 | .iter |
| 7803 | .all, |
| 7804 | "a cell named a column this table does not have; the name is dropped and the \ |
| 7805 | cell is silently lost. Named: {:?}. Columns: {:?}", |
| 7806 | placed.iter.map., |
| 7807 | self.columns |
| 7808 | .iter |
| 7809 | .map |
| 7810 | ., |
| 7811 | ; |
| 7812 | debug_assert! |
| 7813 | |
| 7814 | let mut names = self |
| 7815 | .columns |
| 7816 | .iter |
| 7817 | .map |
| 7818 | .; |
| 7819 | names.sort_unstable; |
| 7820 | let before = names.len; |
| 7821 | names.dedup; |
| 7822 | names.len == before |
| 7823 | , |
| 7824 | "two columns share a name, so a named cell cannot say which it meant and both \ |
| 7825 | take the first one's value. Columns: {:?}", |
| 7826 | self.columns |
| 7827 | .iter |
| 7828 | .map |
| 7829 | ., |
| 7830 | ; |
| 7831 | row.cells = self |
| 7832 | .columns |
| 7833 | .iter |
| 7834 | .enumerate |
| 7835 | .map |
| 7836 | let mut cell = placed |
| 7837 | .iter |
| 7838 | .find |
| 7839 | .map_or_else; |
| 7840 | cell.key = Column; |
| 7841 | cell |
| 7842 | |
| 7843 | .collect; |
| 7844 | |
| 7845 | self.rows.push; |
| 7846 | self |
| 7847 | |
| 7848 | |
| 7849 | /// Add several rows, chaining. |
| 7850 | |
| 7851 | |
| 7852 | for row in rows |
| 7853 | self = self.row; |
| 7854 | |
| 7855 | self |
| 7856 | |
| 7857 | |
| 7858 | /// Say what is not shown, and how to ask for it. |
| 7859 | |
| 7860 | |
| 7861 | self.more = Some; |
| 7862 | self |
| 7863 | |
| 7864 | |
| 7865 | /// The columns, in order. |
| 7866 | |
| 7867 | |
| 7868 | &self.columns |
| 7869 | |
| 7870 | |
| 7871 | |
| 7872 | |
| 7873 | /// A region inside a region, without the enclosing one naming the variant. |
| 7874 | /// `From<Act>` below is the same courtesy for the same reason. |
| 7875 | |
| 7876 | SelfRegion |
| 7877 | |
| 7878 | |
| 7879 | |
| 7880 | |
| 7881 | /// The wrapping every caller of a control-returning function writes by |
| 7882 | /// hand. `From<Table>` below is the same courtesy for the same reason, and |
| 7883 | /// a shape that returns a control has to be placeable in a body without |
| 7884 | /// the caller naming the variant. |
| 7885 | |
| 7886 | SelfAct |
| 7887 | |
| 7888 | |
| 7889 | |
| 7890 | |
| 7891 | /// A question inside a body, without the enclosing container naming the |
| 7892 | /// variant. The third of these, after `From<Slot>` and `From<Act>` above, |
| 7893 | /// and for their reason: a shape that returns a `Field` has to be placeable |
| 7894 | /// where a node goes. MNW's repository bar is the site -- its ref chooser |
| 7895 | /// is a shape of its own and the bar holds it beside the tab strip. |
| 7896 | |
| 7897 | SelfField |
| 7898 | |
| 7899 | |
| 7900 | |
| 7901 | |
| 7902 | |
| 7903 | SelfTable |
| 7904 | columns: table.columns, |
| 7905 | rows: table.rows, |
| 7906 | more: table.more, |
| 7907 | |
| 7908 | |
| 7909 | |
| 7910 | |
| 7911 | /// A row that says where it sits in a hierarchy. |
| 7912 | /// |
| 7913 | /// [`Row`] carries [`depth`](Row::depth) and [`open`](Row::open), and |
| 7914 | /// [`folded`] is the one reading of them the three |
| 7915 | /// renderers must agree on. A trait rather than the function written twice: two |
| 7916 | /// copies of "which rows does a shut branch hide" is two answers waiting to |
| 7917 | /// disagree, and a reader who folds a branch in one host and finds a different |
| 7918 | /// list in another is being shown the disagreement. |
| 7919 | |
| 7920 | /// How far in the row sits. [`Row::depth`]. |
| 7921 | ; |
| 7922 | /// Its disclosure, if it has one. [`Row::open`]. |
| 7923 | ; |
| 7924 | /// What a renderer holding the reader's own folds keys this branch by. |
| 7925 | /// |
| 7926 | /// [`value`](Row::value) when the row names itself and its leading text |
| 7927 | /// otherwise, which is the same fallback `quasi_immediate::row_at` makes |
| 7928 | /// for the same reason: a row that names itself is identified by the app |
| 7929 | /// and one that does not is identified by what it says. |
| 7930 | /// |
| 7931 | /// Here rather than in each renderer, because a reader who folds a branch |
| 7932 | /// in a terminal and opens the same screen in a window is entitled to find |
| 7933 | /// the same branch. Two spellings of "which row is this" is two answers. |
| 7934 | ; |
| 7935 | |
| 7936 | |
| 7937 | |
| 7938 | |
| 7939 | self.depth |
| 7940 | |
| 7941 | |
| 7942 | |
| 7943 | self.open |
| 7944 | |
| 7945 | |
| 7946 | |
| 7947 | self.value.clone.unwrap_or_else |
| 7948 | |
| 7949 | |
| 7950 | |
| 7951 | /// Which rows a closed branch folds away, one answer per row in order. |
| 7952 | /// |
| 7953 | /// A row is folded when a row above it is a closed branch shallower than it, |
| 7954 | /// with nothing at that branch's own depth or shallower in between. That is the |
| 7955 | /// whole of what makes a flat list an outline, and it is computed from the list |
| 7956 | /// rather than described, which is why `Row::children` was not needed to say a |
| 7957 | /// hierarchy. |
| 7958 | /// |
| 7959 | /// A row folded by one branch cannot un-fold under another inside it: a shut |
| 7960 | /// branch takes its whole subtree, including the open branches in it, and those |
| 7961 | /// come back in the state they were left when it opens. |
| 7962 | /// |
| 7963 | /// A renderer that does not call this draws every row flat, which is today's |
| 7964 | /// list and is the graceful degradation the flat model was chosen for. |
| 7965 | |
| 7966 | |
| 7967 | folded_by |
| 7968 | |
| 7969 | |
| 7970 | /// [`folded`], for a renderer holding the reader's own answer about a branch. |
| 7971 | /// |
| 7972 | /// A terminal and an immediate-mode window keep the folds the reader made, the |
| 7973 | /// way they keep a scroll offset and a tick, and the description's `open` is |
| 7974 | /// where that starts rather than where it stays ([`Row::open`]). So they read |
| 7975 | /// each row's state off the reader first and hand the pairs here, and the |
| 7976 | /// walk itself stays in one place: two copies of "what does a shut branch |
| 7977 | /// cover" is two answers waiting to disagree across hosts. |
| 7978 | |
| 7979 | |
| 7980 | let levels = levels.into_iter; |
| 7981 | let mut folded = Vecwith_capacity; |
| 7982 | // The depth of the shallowest closed branch still folding, if any. One |
| 7983 | // variable and not a stack: a branch inside a folded subtree can never be |
| 7984 | // the reason a row is hidden, because it is hidden itself. |
| 7985 | let mut shut: = None; |
| 7986 | for in levels |
| 7987 | if shut.is_some_and |
| 7988 | shut = None; |
| 7989 | |
| 7990 | let hidden = shut.is_some; |
| 7991 | folded.push; |
| 7992 | if !hidden && open == Some |
| 7993 | shut = Some; |
| 7994 | |
| 7995 | |
| 7996 | folded |
| 7997 | |
| 7998 | |
| 7999 | /// Which way a readout is reckoned against the current time. |
| 8000 | /// |
| 8001 | /// The three time-derived members of [`Node`] say the same thing about |
| 8002 | /// themselves in three spellings, and this is how a renderer asks which one it |
| 8003 | /// is holding without matching all three everywhere it cares. Two things read |
| 8004 | /// it: the formatting, which differs per kind, and the cadence, which follows |
| 8005 | /// the granularity the format chose. |
| 8006 | /// |
| 8007 | /// Ordered so a renderer taking the minimum of a screen's kinds gets the finest |
| 8008 | /// one first, which is what [`Screen::clocks`] is usually asked for. |
| 8009 | |
| 8010 | |
| 8011 | /// Counting up from an instant. [`Node::Since`]. |
| 8012 | Since, |
| 8013 | /// Counting down to one. [`Node::Until`]. |
| 8014 | Until, |
| 8015 | /// How long ago one was. [`Node::Age`]. |
| 8016 | Age, |
| 8017 | |
| 8018 | |
| 8019 | /// A thing on a screen. |
| 8020 | /// |
| 8021 | /// Every member composes something `makeover-layout` already names, and that is |
| 8022 | /// the admission test for a new one. A node with no counterpart there means the |
| 8023 | /// vocabulary is missing a word, and the fix is to add the word rather than to |
| 8024 | /// add a widget here. |
| 8025 | /// |
| 8026 | /// There is a second test, and a member passes both or neither: **a renderer |
| 8027 | /// must be able to size it before its content arrives.** A member that can only |
| 8028 | /// be laid out once it is filled makes the screen move under the reader, which |
| 8029 | /// is what "First paint is final paint" in `makeover-layout`'s header forbids. |
| 8030 | /// If a member cannot be sized in advance as written, it is missing the fact |
| 8031 | /// that would make it sizeable, and adding that fact is the fix — the same |
| 8032 | /// shape as the first test, one layer along. |
| 8033 | /// |
| 8034 | /// The failure this catches is an optional measurement standing in for a |
| 8035 | /// measurement that has not been taken. An `Option` on a count here means the |
| 8036 | /// host cannot count, for the life of the screen; it never means the count is |
| 8037 | /// still coming. A number that shows up after the first paint widens whatever |
| 8038 | /// prints it. |
| 8039 | /// |
| 8040 | /// `#[non_exhaustive]`, the pairing [`Region`](layout::Region) made when it grew |
| 8041 | /// [`Region::Widget`](layout::Region::Widget), and for the same reason: the |
| 8042 | /// member after this one should not be a lockstep event across three renderers. |
| 8043 | /// This is the enum that reason applies to most, since a renderer spells every |
| 8044 | /// member of it and nothing else. |
| 8045 | /// |
| 8046 | /// What the attribute buys is that a renderer *may* lag, not that it should. A |
| 8047 | /// wildcard arm here is a promise about one member, so write it the way the rest |
| 8048 | /// of the suite does: the arm says what the honest degradation is and why it is |
| 8049 | /// the safe read, rather than falling through to nothing without comment. The |
| 8050 | /// matches inside this crate stay exhaustive -- the attribute does not apply |
| 8051 | /// within it, and a wildcard here would only hide a member added without a |
| 8052 | /// spelling. |
| 8053 | |
| 8054 | |
| 8055 | |
| 8056 | /// A title, at one of three depths in the heading tree. |
| 8057 | Heading |
| 8058 | /// How far down the tree it sits. |
| 8059 | level: Heading, |
| 8060 | /// The text. |
| 8061 | text: String, |
| 8062 | , |
| 8063 | /// Prose, with a tone. |
| 8064 | Text |
| 8065 | /// The text. |
| 8066 | text: String, |
| 8067 | /// What it is saying. [`layout::Tone::Neutral`] is ordinary content. |
| 8068 | tone: Tone, |
| 8069 | , |
| 8070 | /// Prose the author wrote in markdown. |
| 8071 | /// |
| 8072 | /// What is carried is the **source**, never markup, which is the property |
| 8073 | /// that lets this exist at all. Every renderer has an honest answer |
| 8074 | /// because each renders the source its own way: a webview through a |
| 8075 | /// markdown-to-HTML pass, a terminal through markdown-to-ANSI, egui |
| 8076 | /// through its own. A `Node::Html` would have handed every one of them a |
| 8077 | /// string it could not honour, and would have broken |
| 8078 | /// [`Node::Text`]'s escaping guarantee for every consumer rather than the |
| 8079 | /// one that asked. That refusal stands; this is not it. |
| 8080 | /// |
| 8081 | /// Sanitising is the renderer's, at the point markup is produced, for the |
| 8082 | /// reason escaping already is: this holds text a user typed, and a |
| 8083 | /// description that sanitised would be deciding what a host can draw. |
| 8084 | /// |
| 8085 | /// Not available inside a [`Row`]: a row part holds no node, by the |
| 8086 | /// ruling. What a row can hold is [`Prose`], which carries the same |
| 8087 | /// markdown source under the same reasoning without being a node, so the |
| 8088 | /// projects card no longer keeps its raw markdown in `secondary`. |
| 8089 | Rich |
| 8090 | /// The markdown, as written. |
| 8091 | source: String, |
| 8092 | /// How much of the format this source may use. |
| 8093 | /// |
| 8094 | /// Separate from [`trust`](Self::Rich::trust) on purpose, and the two |
| 8095 | /// correlate without being the same question: a creator's forum post is |
| 8096 | /// rich and untrusted, a button's own hint is plain and trusted. |
| 8097 | richness: Richness, |
| 8098 | /// How far the source is trusted. |
| 8099 | trust: Trust, |
| 8100 | , |
| 8101 | /// Source code, already classified by whoever holds a lexer. |
| 8102 | /// |
| 8103 | /// Decision `19d7602d`, 2026-09-02, option (d). The node carries |
| 8104 | /// [`Lexeme`]s and never a bare string, which is the whole ruling: an app |
| 8105 | /// that browses source already has a lexer, a renderer does not and should |
| 8106 | /// not grow one, and three renderers each growing their own would disagree |
| 8107 | /// about the same file. |
| 8108 | /// |
| 8109 | /// # Why this is not [`Rich`](Self::Rich) |
| 8110 | /// |
| 8111 | /// `Rich` carries markdown and each renderer renders it its own way, which |
| 8112 | /// is right for prose and wrong here: markdown's fenced code block says a |
| 8113 | /// run is code and says nothing about what is in it, so a renderer would |
| 8114 | /// still be choosing whether to lex. This says what the runs are and leaves |
| 8115 | /// only the colouring. |
| 8116 | /// |
| 8117 | /// # Inline against block, and why one member rather than two |
| 8118 | /// |
| 8119 | /// A clone URL in a sentence and a file in a source browser are the same |
| 8120 | /// claim about the same text; what differs is whether it sits in the line or |
| 8121 | /// owns one. That is a containment fact, so it is a flag here and |
| 8122 | /// `Node::containment` reads it: `inline` is a leaf and may sit in a [`Row`] |
| 8123 | /// or a [`Cell`], and a block is not and may not. Two members would have |
| 8124 | /// made one distinction twice, once in the type and once in the bound. |
| 8125 | /// |
| 8126 | /// # The language is carried even though the runs are classified |
| 8127 | /// |
| 8128 | /// Not redundant. The classification is fixed at what [`layout::Syntax`] |
| 8129 | /// can say, and a renderer with an opinion of its own -- a terminal that |
| 8130 | /// already ships a highlighter, a host that wants a language badge over the |
| 8131 | /// block -- has nothing else to read. A hint, and no renderer is obliged to |
| 8132 | /// use it. |
| 8133 | Code |
| 8134 | /// The source, run by run, in order. Concatenating every `text` gives |
| 8135 | /// the file back exactly, whitespace included. |
| 8136 | runs: , |
| 8137 | /// What language it is, when the app knows. A hint; see above. |
| 8138 | language: , |
| 8139 | /// Whether it sits in a line rather than owning one. |
| 8140 | inline: bool, |
| 8141 | , |
| 8142 | /// A control that calls a route. |
| 8143 | Act, |
| 8144 | /// Text that goes somewhere. |
| 8145 | /// |
| 8146 | /// The containment model predicted this before anything asked for it: the |
| 8147 | /// composability matrix had a `Link` row whose "as a `Node`" cell was a |
| 8148 | /// dash, and the only way to say it was [`Cell::activate`], a member on one |
| 8149 | /// container. Once a cell is a run of leaves, the run needs a leaf that |
| 8150 | /// means "this text is a link" or the thing stops being sayable at all. |
| 8151 | /// |
| 8152 | /// Distinct from [`Act`](Self::Act), and the difference is what the reader |
| 8153 | /// sees rather than what the route does. An act is a control drawn as one, |
| 8154 | /// which is right for `Edit` and wrong for a title: making every linked |
| 8155 | /// value a button would put a row of bevels down the first column of half a |
| 8156 | /// dashboard. Both call a route; only one of them looks like a button. |
| 8157 | Link |
| 8158 | /// What it says. |
| 8159 | text: String, |
| 8160 | /// Where it goes. |
| 8161 | action: Action, |
| 8162 | , |
| 8163 | /// One figure, on a line. |
| 8164 | /// |
| 8165 | /// The second thing the containment model found, and it found it by |
| 8166 | /// refusing: a cell is a run of leaves, [`Stats`](Self::Stats) is a |
| 8167 | /// collection, so putting a figure in a cell failed the bound rather than |
| 8168 | /// quietly working. That is the check doing its job -- the matrix had |
| 8169 | /// "figure in a cell" as a dash nobody had attempted, and the dash turns |
| 8170 | /// out to have been hiding a missing member rather than a missing renderer |
| 8171 | /// arm. |
| 8172 | /// |
| 8173 | /// Not a duplicate of a one-element [`Stats`](Self::Stats), and the |
| 8174 | /// difference is the claim being made. A strip says "this is a row of |
| 8175 | /// tiles", which is why the set is the node there: a renderer handed one |
| 8176 | /// tile at a time cannot tell it is looking at a set. This says "this |
| 8177 | /// number sits on this line", where the run is already the grouping and |
| 8178 | /// there is nothing for a set to add. A dashboard strip of one is still a |
| 8179 | /// strip; a revenue column is not. |
| 8180 | Figure, |
| 8181 | /// Time elapsed since an instant, counting up. |
| 8182 | /// |
| 8183 | /// The three time-derived members are this, [`Until`](Self::Until) and |
| 8184 | /// [`Age`](Self::Age), and the ruling they implement is one sentence: **the |
| 8185 | /// renderer owns the clock.** What is carried here is an instant and which |
| 8186 | /// way the readout runs against now; the cadence and the spelling are the |
| 8187 | /// renderer's, and the description names neither. |
| 8188 | /// |
| 8189 | /// A stopwatch, an uptime, a running timer. goingson's time-tracking widget |
| 8190 | /// is the measured case: 564 lines of JS whose whole job is to subtract a |
| 8191 | /// start time from now every second, in two places, for a readout the |
| 8192 | /// description could not say at all. |
| 8193 | /// |
| 8194 | /// # Why a kind and not a format string |
| 8195 | /// |
| 8196 | /// A format string is presentation living in a description, and it would |
| 8197 | /// bind three renderers with different width budgets to one spelling. A |
| 8198 | /// bare instant is worse in the other direction: a stopwatch and a |
| 8199 | /// last-modified stamp become indistinguishable, so a renderer cannot pick |
| 8200 | /// a cadence and would tick a static date once a second. |
| 8201 | /// |
| 8202 | /// The kind answers many-readouts-one-tick for free. Every `Since` on a |
| 8203 | /// screen refreshes together because they share a kind, which is what |
| 8204 | /// `updateRowElapsed` walking `.task-timer-elapsed[data-started]` does by |
| 8205 | /// hand today, and [`Screen::clocks`] is how a renderer asks which kinds it |
| 8206 | /// is holding. |
| 8207 | /// |
| 8208 | /// # Why a duration is not carried |
| 8209 | /// |
| 8210 | /// [`Consult::after`] carries one, and the line between them is stated |
| 8211 | /// there: a duration is the description's when it is a property of the |
| 8212 | /// subject that renderers would otherwise disagree about, and the |
| 8213 | /// renderer's when it is presentation policy. A debounce is the route's own |
| 8214 | /// expense and no renderer could know it. A tick cadence follows the |
| 8215 | /// displayed granularity, so two renderers showing the same readout do not |
| 8216 | /// meaningfully disagree. |
| 8217 | /// |
| 8218 | /// A leaf, so it may sit in a row's run or a table cell. That is the half |
| 8219 | /// the goingson row readout needs: the elapsed time is one part of a task |
| 8220 | /// row beside its title, not a block of its own. |
| 8221 | Since |
| 8222 | /// The instant it counts from. |
| 8223 | at: SystemTime, |
| 8224 | , |
| 8225 | /// Time remaining until an instant, counting down. |
| 8226 | /// |
| 8227 | /// [`Since`](Self::Since) run the other way, under the same ruling and with |
| 8228 | /// the same reasoning. A deadline, an expiry, a lock that lifts. |
| 8229 | /// |
| 8230 | /// What a renderer does once the instant is past is the renderer's, for the |
| 8231 | /// reason the spelling is. Nothing here says whether that reads as a |
| 8232 | /// negative countdown or as zero, because a description that said would be |
| 8233 | /// picking one of those for a terminal it has never seen. |
| 8234 | Until |
| 8235 | /// The instant it counts down to. |
| 8236 | at: SystemTime, |
| 8237 | , |
| 8238 | /// How long ago an instant was. |
| 8239 | /// |
| 8240 | /// [`Since`](Self::Since)'s coarse sibling, and the reason it is a third |
| 8241 | /// member rather than the same one: "3h ago" and a running `h:mm:ss` are |
| 8242 | /// the same subtraction shown at granularities two orders apart, and the |
| 8243 | /// granularity is what decides the cadence. A stamp drawn as a stopwatch |
| 8244 | /// ticks a number that has not changed once a second for a day. |
| 8245 | /// |
| 8246 | /// A last-modified, a posted-at, a synced-at. |
| 8247 | Age |
| 8248 | /// The instant being aged. |
| 8249 | at: SystemTime, |
| 8250 | , |
| 8251 | /// A picture, at a source this crate holds and the description does not. |
| 8252 | /// |
| 8253 | /// The [`Act`](Self::Act) split, and `layout::Image`'s own docs carry the |
| 8254 | /// argument: an address is not the description's to hold, so the shape and |
| 8255 | /// the alt text live there and the URL lives here. |
| 8256 | /// |
| 8257 | /// A leaf, so it may sit in a run the way [`Link`](Self::Link) does. What |
| 8258 | /// it may *not* do is stand in for a region: a picture is one thing on the |
| 8259 | /// page, and a gallery of them is a widget assembled out of several. |
| 8260 | Image, |
| 8261 | /// A small labelled thing sitting inside something else. |
| 8262 | Token, |
| 8263 | /// Something the app is telling the user, unprompted. |
| 8264 | Notice |
| 8265 | /// Transient and stacked, or persistent and in flow. |
| 8266 | kind: Notice, |
| 8267 | /// What it is saying. |
| 8268 | tone: Tone, |
| 8269 | /// The message. |
| 8270 | text: String, |
| 8271 | /// One thing to do about it, if there is one. |
| 8272 | /// |
| 8273 | /// [`Message::undo`] says a response can offer a way back, and a host |
| 8274 | /// that draws a `Message` by converting it into one of these had |
| 8275 | /// nowhere to put it: quasi-tui's `announce` said so in a comment and |
| 8276 | /// dropped it. |
| 8277 | /// |
| 8278 | /// Grown here rather than answered by a holder beside the screen, which |
| 8279 | /// was the alternative. Every retained-screen host would have written |
| 8280 | /// the same holder, and both shapes are the same thing said from two |
| 8281 | /// ends -- which is what [`Message`]'s own docs already claim. |
| 8282 | /// |
| 8283 | /// [`StandIn`](Self::StandIn)'s member under the same name and for the |
| 8284 | /// same reason: a sentence about a situation, and the one thing to do |
| 8285 | /// about it, are one thing on the screen. |
| 8286 | /// |
| 8287 | /// [`Message`]: crate::Message |
| 8288 | /// [`Message::undo`]: crate::Message::undo |
| 8289 | act: , |
| 8290 | , |
| 8291 | /// What stands where content would be, when there is none. |
| 8292 | /// |
| 8293 | /// goingson draws one at 27 sites across 12 files and Balanced Breakfast |
| 8294 | /// at 9, and the class families had already drifted into `empty-state-- |
| 8295 | /// error` against `error-state` for the same fact. Every one of those |
| 8296 | /// sites substitutes markup where a list would go, which is what makes |
| 8297 | /// this a node. |
| 8298 | /// |
| 8299 | /// # Why not on the region |
| 8300 | /// |
| 8301 | /// It was on [`Slot`] first, and a real screen killed it: the project |
| 8302 | /// dashboard's columns are a heading and a list, and a column with no rows |
| 8303 | /// is a region that has content and a list that has none. Marking the |
| 8304 | /// region empty took the heading down with the rows. The emptiness belongs |
| 8305 | /// to the thing that is empty. |
| 8306 | /// |
| 8307 | /// [`Slot::readiness`] keeps the loading axis and only that, which is what |
| 8308 | /// `aria-busy` is about. |
| 8309 | /// |
| 8310 | /// # The state is the vocabulary's and the sentence is not |
| 8311 | /// |
| 8312 | /// `makeover-layout` names the four states because "nothing here yet" and |
| 8313 | /// "this broke" mean the same thing in every app that will have them. "No |
| 8314 | /// projects yet" is content, and so is the button under it, so both are |
| 8315 | /// here. A [`layout::Readiness::Ready`] renders nothing at all: the state |
| 8316 | /// that shows content has no stand-in to draw. |
| 8317 | StandIn |
| 8318 | /// Which of the states this is standing in for. |
| 8319 | state: Readiness, |
| 8320 | /// The sentence. "No projects yet", "Failed to load events". |
| 8321 | message: String, |
| 8322 | /// The way out, if there is one. "Add your first project", "Try again". |
| 8323 | /// |
| 8324 | /// 2 of goingson's 27 have one and 25 say a sentence and stop, which is |
| 8325 | /// why it is optional rather than a second required string. |
| 8326 | act: , |
| 8327 | , |
| 8328 | /// One control, standing on its own. |
| 8329 | /// |
| 8330 | /// A [`Form`](Self::Form) is a set of questions asked together and |
| 8331 | /// answered at once. A settings screen is not that: goingson's is sections |
| 8332 | /// with headings between them, each holding one control that writes as |
| 8333 | /// soon as it changes, and wrapping those in a form would describe markup |
| 8334 | /// that is not there and a submit that does not exist. |
| 8335 | /// |
| 8336 | /// Almost always carries a [`Field::writes`], because a control with no |
| 8337 | /// form around it and no route on it collects a value nothing reads. |
| 8338 | /// |
| 8339 | /// Boxed because it is the only member holding a whole struct by value, and |
| 8340 | /// [`Field`] is the largest one here — every other member holds a `Vec`, a |
| 8341 | /// `String` or a small enum. Unboxed it decides the size of every [`Node`] |
| 8342 | /// in every list, and of the [`Response`](crate::Response) that carries one. |
| 8343 | Field, |
| 8344 | /// Fields, and the route that submits them. |
| 8345 | Form |
| 8346 | /// Where the answers go. Almost always a [`Method::Post`]. |
| 8347 | action: Action, |
| 8348 | /// What the submit control is called. |
| 8349 | submit: String, |
| 8350 | /// The questions, in order. |
| 8351 | fields: , |
| 8352 | , |
| 8353 | /// Rows with named columns. |
| 8354 | /// |
| 8355 | /// # Which containers carry a [`Rest`], and which do not |
| 8356 | /// |
| 8357 | /// A container whose contents came from a query that can be partial carries |
| 8358 | /// one. That is this one -- in both its arrangements, since a list is this |
| 8359 | /// node with no columns -- and nothing else in this enum: |
| 8360 | /// [`Timeline`](Self::Timeline) is bounded by its `Track`, so more of |
| 8361 | /// it is a different window and that is navigation rather than paging; |
| 8362 | /// [`Stats`](Self::Stats) is a fixed set of figures; [`Region`](Self::Region) |
| 8363 | /// holds nodes rather than rows. Written down so the next container to |
| 8364 | /// arrive answers the question rather than inheriting an answer. |
| 8365 | /// |
| 8366 | /// This one went without for four minors. goingson's task list, the first |
| 8367 | /// described table anywhere, had to hang its paging off a separate |
| 8368 | /// [`Act`](Self::Act) under the table, and recorded the cost: the renderer |
| 8369 | /// could not tell the control belonged to the table above it. |
| 8370 | Table |
| 8371 | /// The columns, in order. A cell keyed by position answers these in order. |
| 8372 | /// |
| 8373 | /// **Empty is a list.** That is the whole of what a list is since the |
| 8374 | /// 2026-09-06 collapse, and it is a fact the rows already carried rather |
| 8375 | /// than a flag added beside them: a row whose cells are keyed by |
| 8376 | /// [`CellKey::Role`] answers the default column set, and a table that |
| 8377 | /// declared no columns of its own is a table using that set. So there is |
| 8378 | /// no `look` member and no second variant to keep in step. |
| 8379 | /// |
| 8380 | /// A renderer reads it to choose an arrangement, not to choose a |
| 8381 | /// meaning. `columns.is_empty()` draws the flowed, one-per-line form a |
| 8382 | /// list has always drawn -- `<ul>` in a webview -- and a declared column |
| 8383 | /// list draws a grid. Both are the same node holding the same rows. |
| 8384 | columns: , |
| 8385 | /// The rows, in order. |
| 8386 | rows: , |
| 8387 | /// What is not shown, if anything is. |
| 8388 | /// |
| 8389 | /// A described list of the first 50 of 400 tasks was indistinguishable |
| 8390 | /// from a described list of 50 tasks, so each app grew its own answer: |
| 8391 | /// goingson a 159-line pagination manager with two consumers that had |
| 8392 | /// each written it separately first, Balanced Breakfast four |
| 8393 | /// `loadMore` sites. Two idioms for one fact, and the fact is what |
| 8394 | /// belongs here -- how much more there is and how to ask for it. |
| 8395 | /// Whether that becomes numbered pages, a load-more button or an |
| 8396 | /// infinite scroll is the renderer's. |
| 8397 | more: , |
| 8398 | , |
| 8399 | /// Rows placed by when they happen, rather than in order. |
| 8400 | /// |
| 8401 | /// The second of the two ways this vocabulary says "several of the same |
| 8402 | /// kind of thing", and the last one to arrive. |
| 8403 | /// [`Table`](Self::Table) puts them in order and, when it declares columns, |
| 8404 | /// lines their parts up under headings; this one puts them on a clock. |
| 8405 | /// |
| 8406 | /// It was the third of three until the 2026-09-06 collapse, when a list |
| 8407 | /// stopped being a node of its own. A timeline stayed: what it adds is |
| 8408 | /// [`layout::Placement`] on every entry, which is a fact a `Row` does not |
| 8409 | /// carry and a column cannot supply. |
| 8410 | /// |
| 8411 | /// A row here is an ordinary [`Row`] and gets no new members: the item |
| 8412 | /// bodies on goingson's day view are a title, a time, a tag and a tone, |
| 8413 | /// which the vocabulary already said. What it could not say is *where the |
| 8414 | /// row sits*, and that is [`layout::Placement`] — a start and a duration, |
| 8415 | /// two integers, which is the whole of what the timeline refusal was |
| 8416 | /// pricing as a component library. See `makeover-layout` 0.24.0. |
| 8417 | /// |
| 8418 | /// # What a renderer owes it |
| 8419 | /// |
| 8420 | /// Draw the span, put each row at its placement, and lay overlapping rows |
| 8421 | /// so both can be read. That last part is presentation and deliberately |
| 8422 | /// unspecified: a webview puts them in columns, a terminal may stack them |
| 8423 | /// with a marker, and neither is wrong. |
| 8424 | /// [`layout::Placement::overlaps`] is how a renderer finds the pairs |
| 8425 | /// without the description declaring them. |
| 8426 | /// |
| 8427 | /// # What it is not |
| 8428 | /// |
| 8429 | /// Not a calendar and not a kanban board. Both were refused alongside the |
| 8430 | /// timeline and neither has been measured; whoever needs one counts the |
| 8431 | /// members it is missing rather than reaching for this. |
| 8432 | Timeline |
| 8433 | /// The axis: its window, its granularity, how often it labels itself. |
| 8434 | track: Track, |
| 8435 | /// What sits on it, each with where it sits. |
| 8436 | /// |
| 8437 | /// Not sorted here, and a renderer must not assume it is. Sorting by |
| 8438 | /// start is presentation for anything that draws top to bottom, and |
| 8439 | /// meaningless for anything that does not. |
| 8440 | entries: , |
| 8441 | /// A moment worth bringing into view, if any. |
| 8442 | /// |
| 8443 | /// "Show me 09:00" rather than a scroll offset in pixels. goingson's JS |
| 8444 | /// hardcodes `targetHour = 9` inside the renderer, which is the shape |
| 8445 | /// this replaces: the app knows the interesting hour, the renderer |
| 8446 | /// knows how to get there. |
| 8447 | /// |
| 8448 | /// `None` means the renderer chooses, which is usually the span's |
| 8449 | /// start. |
| 8450 | focus: , |
| 8451 | , |
| 8452 | /// How much of a set is done. |
| 8453 | Meter, |
| 8454 | /// A value with a caption, several of them as one strip. |
| 8455 | /// |
| 8456 | /// Against `makeover-layout`'s [`layout::Figure`], which arrived at 0.11.0 |
| 8457 | /// for this. The dashboard shape: a large value over a small caption, |
| 8458 | /// several in a row. goingson had five of them across five screens with |
| 8459 | /// five class vocabularies for the one shape, and the port had been making |
| 8460 | /// each out of a [`Row`] with the caption as `primary` and the figure as |
| 8461 | /// `meta`, which reads backwards — a row's primary slot means the thing |
| 8462 | /// itself, and here the thing is the number. |
| 8463 | /// |
| 8464 | /// # Why the set is the node and not each figure |
| 8465 | /// |
| 8466 | /// Four tiles in a strip and four tiles down a column are different things, |
| 8467 | /// and a renderer handed one at a time cannot tell it is looking at a set. |
| 8468 | /// The objection to that is real and is answered by what is already here: a |
| 8469 | /// node whose value is its grouping sounds like a layout instruction, and |
| 8470 | /// [`Table`](Self::Table) has been exactly that since the beginning without |
| 8471 | /// anyone calling it one. |
| 8472 | /// |
| 8473 | /// # Why the action is here and not on the figure |
| 8474 | /// |
| 8475 | /// One of goingson's five is a control — sync's "Not Applied: 3" opens the |
| 8476 | /// list. `makeover-layout` cannot name an action at all, so the figure it |
| 8477 | /// describes carries none, and this pairs the description with the address |
| 8478 | /// the same way [`Row`] pairs its parts with [`Row::activate`]. |
| 8479 | Stats |
| 8480 | /// The figures, in order, and what each one calls if it calls anything. |
| 8481 | figures: , |
| 8482 | , |
| 8483 | /// A region inside a region. |
| 8484 | Region, |
| 8485 | /// Markup this vocabulary did not write, and the scope that isolates it. |
| 8486 | /// |
| 8487 | /// The measured consumer is MNW's custom pages: a creator writes HTML and |
| 8488 | /// CSS, the server sanitises both and re-scopes every selector under a |
| 8489 | /// canvas element, and what comes out is a document whose middle is |
| 8490 | /// opaque. Nothing in a vocabulary of rows, cards and fields can say that, |
| 8491 | /// and no amount of growing it will -- the whole point of the feature is |
| 8492 | /// that the platform does not know what the creator drew. |
| 8493 | /// |
| 8494 | /// # This is the one member a description does not describe |
| 8495 | /// |
| 8496 | /// Everywhere else, a description says what a thing *is* and a renderer |
| 8497 | /// decides what it looks like. Here the app hands over markup and the |
| 8498 | /// renderer writes it out. That is the concession, it is deliberate, and |
| 8499 | /// the way to keep it from spreading is to remember what earned it: markup |
| 8500 | /// that is *data*, authored by somebody who is not the app and stored |
| 8501 | /// rather than written. A screen reaching for this to avoid describing its |
| 8502 | /// own layout is a screen that has not been described. |
| 8503 | /// |
| 8504 | /// # Sanitised by whoever produced it |
| 8505 | /// |
| 8506 | /// This crate does not parse the markup, does not sanitise it, and has no |
| 8507 | /// opinion about what is in it. It cannot: what is safe depends on the |
| 8508 | /// document's own headers, and MNW's answer is an allowlist pass plus a |
| 8509 | /// `default-src 'none'` CSP on a cookieless host. An app putting reader |
| 8510 | /// input in here is putting reader input in a browser. |
| 8511 | /// |
| 8512 | /// # Every host but a markup one draws nothing |
| 8513 | /// |
| 8514 | /// A terminal has no use for a string of HTML and will not grow one, so it |
| 8515 | /// draws nothing here rather than drawing the tags. That is the same read |
| 8516 | /// [`Binding::key`] gets from a host that has never heard of the key, and |
| 8517 | /// it is honest for this member in a way it would not be for most: these |
| 8518 | /// are public web pages and no terminal is going to serve one. |
| 8519 | /// |
| 8520 | /// [`Binding::key`]: crate::chrome::Binding::key |
| 8521 | Canvas, |
| 8522 | |
| 8523 | |
| 8524 | /// Markup an app stored rather than wrote, and how it is kept to itself. |
| 8525 | /// |
| 8526 | /// The body half of what a custom page needs; the stylesheet half is |
| 8527 | /// [`Document::style`], because a sheet is true of the document rather than of |
| 8528 | /// a place in it. |
| 8529 | /// |
| 8530 | /// # The scope is markup-shaped on purpose |
| 8531 | /// |
| 8532 | /// [`class`](Self::class) and [`id`](Self::id) are the two hooks a stylesheet |
| 8533 | /// can be confined to, and they are named here in the markup's own words for |
| 8534 | /// [`Document`]'s reason: this is where a host keeps what its own taxonomy |
| 8535 | /// says, rather than where the vocabulary grows a word for it. MNW's sanitiser |
| 8536 | /// rewrites every creator selector to sit under `.user-canvas#uc-{owner}`, so |
| 8537 | /// the element the renderer writes has to carry that class and that id or the |
| 8538 | /// sheet it was scoped for matches nothing. Which strings those are is the |
| 8539 | /// app's to know; that they are a class and an id is the sanitiser's contract, |
| 8540 | /// not this crate's invention. |
| 8541 | /// |
| 8542 | /// Both optional, and both empty is a canvas that isolates nothing -- markup |
| 8543 | /// dropped into the page with no sheet aimed at it, which is a real shape for |
| 8544 | /// an app whose stylesheet is its own. |
| 8545 | /// |
| 8546 | /// # An empty [`markup`](Self::markup) is the other half of the same idea |
| 8547 | /// |
| 8548 | /// The scope is here because a class or an id can be a *contract with a |
| 8549 | /// stylesheet the app did not write*, and creator markup is only one way to |
| 8550 | /// arrive at one. The other is a block the app draws itself and has published |
| 8551 | /// a name for: MNW's guide tells creators that the buy block is `.mnw-buy`, |
| 8552 | /// the file list `.mnw-files` and the item block `.mnw-item`, with worked CSS |
| 8553 | /// against them. Those names are as load-bearing as the canvas id and for the |
| 8554 | /// same reason -- somebody outside the app has already written selectors |
| 8555 | /// against them -- and a renderer that prefixed or renamed them would silently |
| 8556 | /// stop every creator page that styles one. |
| 8557 | /// |
| 8558 | /// So a canvas holding no markup and only [`within`](Self::within) is an |
| 8559 | /// ordinary shape: described content, under a name the app has promised. What |
| 8560 | /// the two uses share is the whole of what this member is, which is why they |
| 8561 | /// are one member and not two. What they do not share is risk: markup is |
| 8562 | /// opaque and dangerous, and a name is neither. |
| 8563 | |
| 8564 | |
| 8565 | /// The markup, as whoever produced it left it. |
| 8566 | pub markup: String, |
| 8567 | /// The classes on the element that scopes it, space-separated. |
| 8568 | pub class: , |
| 8569 | /// The id on the element that scopes it. |
| 8570 | pub id: , |
| 8571 | /// Nodes drawn inside the scope, after the markup. |
| 8572 | /// |
| 8573 | /// Inside rather than beside, because that is what the measured consumer |
| 8574 | /// does and the difference is visible: MNW's project pages put the buy |
| 8575 | /// block and the file list in the scoping element after the creator's |
| 8576 | /// markup, which is what lets a creator style the platform's own blocks to |
| 8577 | /// match the page they wrote. Nodes placed outside would be a different |
| 8578 | /// page, quietly. |
| 8579 | /// |
| 8580 | /// Ordinary nodes. Nothing is injected into the middle of the markup and |
| 8581 | /// nothing can be: a canvas is opaque, so the only place the app's own |
| 8582 | /// nodes can go is after it. |
| 8583 | pub within: , |
| 8584 | |
| 8585 | |
| 8586 | |
| 8587 | /// Markup with no scope on it yet. |
| 8588 | |
| 8589 | |
| 8590 | Self |
| 8591 | markup: markup.into, |
| 8592 | ..Selfdefault |
| 8593 | |
| 8594 | |
| 8595 | |
| 8596 | /// Put this class on the element that scopes the markup. |
| 8597 | |
| 8598 | |
| 8599 | self.class = Some; |
| 8600 | self |
| 8601 | |
| 8602 | |
| 8603 | /// Put this id on the element that scopes the markup. |
| 8604 | |
| 8605 | |
| 8606 | self.id = Some; |
| 8607 | self |
| 8608 | |
| 8609 | |
| 8610 | /// Draw this node inside the scope, after the markup. |
| 8611 | |
| 8612 | |
| 8613 | self.within.push; |
| 8614 | self |
| 8615 | |
| 8616 | |
| 8617 | |
| 8618 | |
| 8619 | /// What this node offers under a control name, if anything below it does. |
| 8620 | /// |
| 8621 | /// The described half of a [`Reveal`]: a region names a control by |
| 8622 | /// [`Field::name`], and this is how a renderer finds what that control was |
| 8623 | /// handed to the reader holding. |
| 8624 | /// |
| 8625 | /// The match is exhaustive rather than a wildcard over the containers, |
| 8626 | /// which is what keeps a member that grows a body from quietly hiding |
| 8627 | /// fields from every conditional region on the screen. `Node` is |
| 8628 | /// `#[non_exhaustive]` outside this crate and not inside it, so the |
| 8629 | /// compiler is the reviewer here. |
| 8630 | /// |
| 8631 | /// Every describable choice is a [`Field`] and therefore has a name, which |
| 8632 | /// is what makes a watched choice reachable at all. There is one way to |
| 8633 | /// describe a choice. |
| 8634 | |
| 8635 | |
| 8636 | /// What one field offers, if it is the field being asked about. |
| 8637 | |
| 8638 | |
| 8639 | .then_some |
| 8640 | .flatten |
| 8641 | |
| 8642 | |
| 8643 | match self |
| 8644 | SelfField => offered, |
| 8645 | SelfForm => fields.iter.find_map, |
| 8646 | SelfRegion => slot.holds, |
| 8647 | // The markup is opaque and holds nothing this crate can find; what |
| 8648 | // is walked is the app's own nodes inside the scope. |
| 8649 | SelfCanvas => canvas.within.iter.find_map, |
| 8650 | SelfTable => rows.iter.find_map |
| 8651 | row.cells |
| 8652 | .iter |
| 8653 | .find_map |
| 8654 | , |
| 8655 | SelfTimeline => entries.iter.find_map |
| 8656 | placed |
| 8657 | .row |
| 8658 | .cells |
| 8659 | .iter |
| 8660 | .find_map |
| 8661 | , |
| 8662 | // Everything with no field under it. Written out rather than left |
| 8663 | // to a wildcard, for the reason above. |
| 8664 | SelfHeading |
| 8665 | | SelfText |
| 8666 | | SelfRich |
| 8667 | | SelfAct |
| 8668 | | SelfLink |
| 8669 | | SelfFigure |
| 8670 | | SelfSince |
| 8671 | | SelfUntil |
| 8672 | | SelfAge |
| 8673 | | SelfImage |
| 8674 | | SelfToken |
| 8675 | | SelfNotice |
| 8676 | | SelfStandIn |
| 8677 | | SelfMeter |
| 8678 | // Classified text and nothing else: no field, no act, no clock. |
| 8679 | | SelfCode |
| 8680 | | SelfStats => None, |
| 8681 | |
| 8682 | |
| 8683 | |
| 8684 | /// Every question under this node, in draw order, appended to `found`. |
| 8685 | /// |
| 8686 | /// [`holds`](Self::holds)' walk asking for the boxes themselves rather |
| 8687 | /// than for one of their values, which is what a |
| 8688 | /// [`Slot::consults`] gathers. Exhaustive over the containers for |
| 8689 | /// `holds`' reason, and it appends rather than returning so a region's walk |
| 8690 | /// over a body of nodes allocates once. |
| 8691 | |
| 8692 | match self |
| 8693 | SelfField => found.push, |
| 8694 | SelfCanvas => |
| 8695 | for node in &canvas.within |
| 8696 | node.questions; |
| 8697 | |
| 8698 | |
| 8699 | SelfForm => found.extend, |
| 8700 | SelfRegion => found.extend, |
| 8701 | SelfTable => |
| 8702 | for row in rows |
| 8703 | for cell in &row.cells |
| 8704 | for node in &cell.content |
| 8705 | node.questions; |
| 8706 | |
| 8707 | |
| 8708 | |
| 8709 | |
| 8710 | SelfTimeline => |
| 8711 | for placed in entries |
| 8712 | for cell in &placed.row.cells |
| 8713 | for node in &cell.content |
| 8714 | node.questions; |
| 8715 | |
| 8716 | |
| 8717 | |
| 8718 | |
| 8719 | // Everything with no question under it. Written out rather than |
| 8720 | // left to a wildcard, so a member that grows a body has to answer |
| 8721 | // here instead of quietly contributing nothing to every region |
| 8722 | // consult on the screen. |
| 8723 | SelfHeading |
| 8724 | | SelfText |
| 8725 | | SelfRich |
| 8726 | | SelfAct |
| 8727 | | SelfLink |
| 8728 | | SelfFigure |
| 8729 | | SelfSince |
| 8730 | | SelfUntil |
| 8731 | | SelfAge |
| 8732 | | SelfImage |
| 8733 | | SelfToken |
| 8734 | | SelfNotice |
| 8735 | | SelfStandIn |
| 8736 | | SelfMeter |
| 8737 | | SelfCode |
| 8738 | | SelfStats => |
| 8739 | |
| 8740 | |
| 8741 | |
| 8742 | /// Whether an [`Act`] under this node carries this [`Act::id`]. |
| 8743 | /// |
| 8744 | /// [`holds`](Self::holds)'s shape for a different question, and it walks the |
| 8745 | /// same containers for the same reason: a control is anywhere a run, a row, |
| 8746 | /// a cell or a nested region can put one. |
| 8747 | /// |
| 8748 | /// A row's [`menu`](Row::menu) is walked too. A menu act is a control the |
| 8749 | /// description carries and a renderer draws, so a screen that answers `true` |
| 8750 | /// for one is telling the truth; whether that host has drawn it yet is the |
| 8751 | /// host's business. |
| 8752 | |
| 8753 | |
| 8754 | match self |
| 8755 | SelfAct => act.id.as_deref == Some, |
| 8756 | SelfRegion => slot.names, |
| 8757 | // A control inside the markup is the creator's and carries no id |
| 8758 | // this crate handed out, so only the app's own nodes are walked. |
| 8759 | SelfCanvas => canvas.within.iter.any, |
| 8760 | SelfTable => rows.iter.any, |
| 8761 | SelfTimeline => entries.iter.any, |
| 8762 | // Everything with no control under it. Written out rather than left |
| 8763 | // to a wildcard, so a node kind that gains one stops compiling here. |
| 8764 | SelfHeading |
| 8765 | | SelfText |
| 8766 | | SelfRich |
| 8767 | | SelfField |
| 8768 | | SelfForm |
| 8769 | | SelfLink |
| 8770 | | SelfFigure |
| 8771 | | SelfSince |
| 8772 | | SelfUntil |
| 8773 | | SelfAge |
| 8774 | | SelfImage |
| 8775 | | SelfToken |
| 8776 | | SelfNotice |
| 8777 | | SelfStandIn |
| 8778 | | SelfMeter |
| 8779 | | SelfCode |
| 8780 | | SelfStats => false, |
| 8781 | |
| 8782 | |
| 8783 | |
| 8784 | /// The value a control sends when what it sends is presence rather than a |
| 8785 | /// number: a ticked checkbox, and anything else spelling "this one". |
| 8786 | /// |
| 8787 | /// Named once here rather than agreed by convention between each renderer |
| 8788 | /// and each handler, which is how a value arrives under `tab` in one screen |
| 8789 | /// and `selected` in the next. It is HTML's own convention for a checkbox |
| 8790 | /// read back out, which is why the word is `value`. |
| 8791 | /// |
| 8792 | /// A [`Field`] carries its own name, so a choice needs no shared word to |
| 8793 | /// arrive under. |
| 8794 | pub const SELECTED: &'static str = "value"; |
| 8795 | |
| 8796 | /// The parameter name an [`Act::over`] sends each ticked value under. |
| 8797 | /// |
| 8798 | /// [`SELECTED`](Self::SELECTED)'s sibling, named here for the same reason: |
| 8799 | /// a convention agreed separately by each renderer and each handler is a |
| 8800 | /// convention that holds until one of them is written by someone else. |
| 8801 | /// |
| 8802 | /// Distinct from `SELECTED` rather than shared with it, because the two |
| 8803 | /// carry different counts. A control saying "this one" sends one value and |
| 8804 | /// a handler reads it with [`Params::get`]; a selection sends however many |
| 8805 | /// are ticked, including none, and a handler reads it with |
| 8806 | /// [`Params::get_all`]. One name for both would make "the one thing picked" |
| 8807 | /// and "the first of the things ticked" the same read. |
| 8808 | /// |
| 8809 | /// [`Params::get`]: crate::Params::get |
| 8810 | /// [`Params::get_all`]: crate::Params::get_all |
| 8811 | pub const TICKED: &'static str = "ticked"; |
| 8812 | |
| 8813 | /// The parameter name a row activation sends its [`Choosing`] under. |
| 8814 | /// |
| 8815 | /// [`SELECTED`](Self::SELECTED)'s and [`TICKED`](Self::TICKED)'s third |
| 8816 | /// sibling, named here for the reason both of those are: a convention |
| 8817 | /// agreed separately by each renderer and each handler holds until one of |
| 8818 | /// them is written by someone else. |
| 8819 | /// |
| 8820 | /// Sent with **every** activation of a row that can be chosen, including the |
| 8821 | /// ordinary one, so a handler reads one parameter rather than branching on |
| 8822 | /// whether a parameter arrived. A row that carries no |
| 8823 | /// [`Row::chosen`] fact is not part of a selection and |
| 8824 | /// sends nothing, so a handler that never asks is unaffected. |
| 8825 | pub const CHOOSING: &'static str = "choosing"; |
| 8826 | |
| 8827 | /// A page title. |
| 8828 | |
| 8829 | SelfHeading |
| 8830 | level: Page, |
| 8831 | text: text.into, |
| 8832 | |
| 8833 | |
| 8834 | |
| 8835 | /// A section title. |
| 8836 | |
| 8837 | SelfHeading |
| 8838 | level: Section, |
| 8839 | text: text.into, |
| 8840 | |
| 8841 | |
| 8842 | |
| 8843 | /// A subsection title. |
| 8844 | /// |
| 8845 | /// The third of the three heading levels, which had no constructor while |
| 8846 | /// the other two did. Every site that wanted one wrote the variant out |
| 8847 | /// with its `level` and its `text`, which is the struct rather than the |
| 8848 | /// vocabulary. |
| 8849 | |
| 8850 | SelfHeading |
| 8851 | level: Subsection, |
| 8852 | text: text.into, |
| 8853 | |
| 8854 | |
| 8855 | |
| 8856 | /// Ordinary prose. |
| 8857 | |
| 8858 | SelfText |
| 8859 | text: text.into, |
| 8860 | tone: Neutral, |
| 8861 | |
| 8862 | |
| 8863 | |
| 8864 | /// The same prose, saying something about itself. |
| 8865 | /// |
| 8866 | /// [`text`](Self::text) is this at [`layout::Tone::Neutral`], which is |
| 8867 | /// ordinary content, and it was the only constructor `Text` had: a line |
| 8868 | /// that is a warning wrote the variant out by hand. **21 sites in the tree |
| 8869 | /// do**, nine of them in MNW's commit view alone, which makes this the most |
| 8870 | /// repeated instance of the gap `Node::literal` and `Node::token` closed |
| 8871 | /// before it. |
| 8872 | /// |
| 8873 | /// Not a notice. A notice is a thing that happened and carries a way out; |
| 8874 | /// this is a sentence in the reading that is warning-coloured, which is |
| 8875 | /// what a diff's deletion count and an unverifiable signature both are. |
| 8876 | |
| 8877 | SelfText |
| 8878 | text: text.into, |
| 8879 | tone, |
| 8880 | |
| 8881 | |
| 8882 | |
| 8883 | /// Machine text in a line of reading. |
| 8884 | /// |
| 8885 | /// One unclassified run, inline, no language: a ref path, a fingerprint, a |
| 8886 | /// clone URL, a line of a file. [`Code`](Self::Code) is the only node with |
| 8887 | /// no constructor of its own, and six sites in the tree write this exact |
| 8888 | /// literal out -- one of them under a private helper called `literal`, |
| 8889 | /// which is where the name comes from. |
| 8890 | /// |
| 8891 | /// Nothing lexed it and nothing should, so what this buys is the monospace |
| 8892 | /// and not a colour. A block, or runs a lexer classified, still writes the |
| 8893 | /// variant; the screen that asks for either earns the constructor for it. |
| 8894 | |
| 8895 | SelfCode |
| 8896 | runs: vec!, |
| 8897 | language: None, |
| 8898 | inline: true, |
| 8899 | |
| 8900 | |
| 8901 | |
| 8902 | /// Machine text a lexer has been over, inline. |
| 8903 | /// |
| 8904 | /// The constructor [`literal`](Self::literal) reserved for the screen that |
| 8905 | /// asked, and MNW's source browser is it: a file is drawn one row per line, |
| 8906 | /// because `#L42` is the address of a line and a block that owned its own |
| 8907 | /// lines would have nothing to hang one on. So every line is classified |
| 8908 | /// runs plus the extension, and `literal`'s one-plain-run shape cannot say |
| 8909 | /// it. |
| 8910 | /// |
| 8911 | /// Still inline. A block is the other half and is still unasked for. |
| 8912 | |
| 8913 | |
| 8914 | SelfCode |
| 8915 | runs, |
| 8916 | language, |
| 8917 | inline: true, |
| 8918 | |
| 8919 | |
| 8920 | |
| 8921 | /// Prose written in markdown, by somebody the app does not vouch for. |
| 8922 | /// |
| 8923 | /// [`Richness::Sentence`] and [`Trust::Untrusted`], which is what this member |
| 8924 | /// has always meant and what every call site written before the two axes |
| 8925 | /// existed still gets. Say otherwise with [`trust`](Self::trust) and |
| 8926 | /// [`richness`](Self::richness). |
| 8927 | |
| 8928 | SelfRich |
| 8929 | source: source.into, |
| 8930 | richness: Sentence, |
| 8931 | trust: Untrusted, |
| 8932 | |
| 8933 | |
| 8934 | |
| 8935 | /// Who wrote this markdown. See [`Trust`]. |
| 8936 | /// |
| 8937 | /// Does nothing to a node that is not [`Rich`](Self::Rich), rather than |
| 8938 | /// refusing: the same rule `Screen::replace` follows for a region it cannot |
| 8939 | /// find, and for the same reason -- a miss is worth seeing and is not worth |
| 8940 | /// refusing to draw a screen over. |
| 8941 | |
| 8942 | |
| 8943 | if let SelfRich = &mut self |
| 8944 | *current = trust; |
| 8945 | |
| 8946 | self |
| 8947 | |
| 8948 | |
| 8949 | /// How much of the format this markdown may use. See [`Richness`]. |
| 8950 | /// |
| 8951 | /// [`trust`](Self::trust)'s note about a node that is not `Rich` applies |
| 8952 | /// here too. |
| 8953 | |
| 8954 | |
| 8955 | if let SelfRich |
| 8956 | richness: current, .. |
| 8957 | = &mut self |
| 8958 | |
| 8959 | *current = richness; |
| 8960 | |
| 8961 | self |
| 8962 | |
| 8963 | |
| 8964 | /// A control calling a route. |
| 8965 | |
| 8966 | SelfAct |
| 8967 | |
| 8968 | |
| 8969 | /// A persistent message, dismissed by fixing what caused it. |
| 8970 | |
| 8971 | SelfNotice |
| 8972 | kind: Banner, |
| 8973 | tone, |
| 8974 | text: text.into, |
| 8975 | act: None, |
| 8976 | |
| 8977 | |
| 8978 | |
| 8979 | /// A transient message that dismisses itself. |
| 8980 | |
| 8981 | SelfNotice |
| 8982 | kind: Toast, |
| 8983 | tone, |
| 8984 | text: text.into, |
| 8985 | act: None, |
| 8986 | |
| 8987 | |
| 8988 | |
| 8989 | /// The same notice, with one thing to do about it. |
| 8990 | /// |
| 8991 | /// [`offering`](Self::offering)'s shape, and a no-op on anything else for |
| 8992 | /// the same reason [`and_more`](Self::and_more) is one. Named apart from |
| 8993 | /// `offering` because a stand-in's act is a way *out* of an empty screen and |
| 8994 | /// a notice's is a way *back* from what just happened. |
| 8995 | |
| 8996 | |
| 8997 | if let SelfNotice = &mut self |
| 8998 | *act = Some; |
| 8999 | |
| 9000 | self |
| 9001 | |
| 9002 | |
| 9003 | /// A list of rows. |
| 9004 | /// |
| 9005 | /// A [`Table`](Self::Table) that declares no columns, which is what a list |
| 9006 | /// is since the 2026-09-06 collapse. The constructor stays because "a list |
| 9007 | /// of rows" is what the caller means and `Table { columns: vec![], .. }` is |
| 9008 | /// how the vocabulary spells it, not something every caller should have to |
| 9009 | /// spell. |
| 9010 | |
| 9011 | SelfTable |
| 9012 | columns: Vecnew, |
| 9013 | rows: rows.into_iter.collect, |
| 9014 | more: None, |
| 9015 | |
| 9016 | |
| 9017 | |
| 9018 | /// The same list, saying there is more of it. |
| 9019 | /// |
| 9020 | /// A no-op on anything that is not a [`Self::Table`], which is the one place |
| 9021 | /// this file allows that: the alternative is a constructor taking rows and a |
| 9022 | /// `Rest` together, and every call site that has no more rows then passes a |
| 9023 | /// `None` to say so. |
| 9024 | |
| 9025 | |
| 9026 | if let SelfTable = &mut self |
| 9027 | *more = Some; |
| 9028 | |
| 9029 | self |
| 9030 | |
| 9031 | |
| 9032 | /// A proportion of a set, untoned and unlabelled. |
| 9033 | |
| 9034 | pub const |
| 9035 | SelfMeter |
| 9036 | |
| 9037 | |
| 9038 | /// Nothing here yet. |
| 9039 | |
| 9040 | SelfStandIn |
| 9041 | state: Empty, |
| 9042 | message: message.into, |
| 9043 | act: None, |
| 9044 | |
| 9045 | |
| 9046 | |
| 9047 | /// This did not load. |
| 9048 | |
| 9049 | SelfStandIn |
| 9050 | state: Failed, |
| 9051 | message: message.into, |
| 9052 | act: None, |
| 9053 | |
| 9054 | |
| 9055 | |
| 9056 | /// This is on its way. |
| 9057 | /// |
| 9058 | /// The third of the three drawn states, and the one nothing built until |
| 9059 | /// [`Outcome::Started`](crate::Outcome::Started) needed it. A host that |
| 9060 | /// retains the description says a region is waiting on |
| 9061 | /// [`Slot::readiness`] and needs no node; a host that swaps markup has |
| 9062 | /// nowhere to put an attribute and needs one, and this is what it puts |
| 9063 | /// there. |
| 9064 | /// |
| 9065 | /// [`Slot::readiness`] stays the axis either way. This is the sentence |
| 9066 | /// beside it, not a second way of saying the same thing: a region can be |
| 9067 | /// [`Pending`](layout::Readiness::Pending) with no words at all, which is |
| 9068 | /// every deferred load on every screen. |
| 9069 | |
| 9070 | SelfStandIn |
| 9071 | state: Pending, |
| 9072 | message: message.into, |
| 9073 | act: None, |
| 9074 | |
| 9075 | |
| 9076 | |
| 9077 | /// The same stand-in, with a way out of it. |
| 9078 | /// |
| 9079 | /// A no-op on anything else, for the reason [`Self::and_more`] is one. |
| 9080 | |
| 9081 | |
| 9082 | if let SelfStandIn = &mut self |
| 9083 | *act = Some; |
| 9084 | |
| 9085 | self |
| 9086 | |
| 9087 | |
| 9088 | /// One control on its own, outside any form. |
| 9089 | |
| 9090 | SelfField |
| 9091 | |
| 9092 | |
| 9093 | /// A strip of figures, none of which answers a click. |
| 9094 | |
| 9095 | SelfStats |
| 9096 | figures: figures.into_iter.map.collect, |
| 9097 | |
| 9098 | |
| 9099 | |
| 9100 | /// One more figure, beside [`stats`](Self::stats). |
| 9101 | /// |
| 9102 | /// `stats` takes the whole list, and every other container in this |
| 9103 | /// vocabulary accretes: `Table::column`, `Row::cell` and `Field::options` |
| 9104 | /// were all added for that reason and this is the fourth. A caller building |
| 9105 | /// figures one at a time, or conditionally, has nowhere to hold the list. |
| 9106 | /// |
| 9107 | /// Does nothing to a node that is not [`Stats`](Self::Stats), on |
| 9108 | /// [`trust`](Self::trust)'s rule. |
| 9109 | |
| 9110 | |
| 9111 | if let SelfStats = &mut self |
| 9112 | figures.push; |
| 9113 | |
| 9114 | self |
| 9115 | |
| 9116 | |
| 9117 | /// An axis with nothing on it yet. |
| 9118 | /// |
| 9119 | /// [`Timeline`](Self::Timeline) was the last variant with no constructor of |
| 9120 | /// its own, and its entries accrete for [`figure`](Self::figure)'s reason: |
| 9121 | /// a caller building them one at a time, or conditionally, has nowhere to |
| 9122 | /// hold the list. goingson's day view is the site. |
| 9123 | |
| 9124 | pub const |
| 9125 | SelfTimeline |
| 9126 | track, |
| 9127 | entries: Vecnew, |
| 9128 | focus: None, |
| 9129 | |
| 9130 | |
| 9131 | |
| 9132 | /// One more thing on the axis, beside [`timeline`](Self::timeline). |
| 9133 | /// |
| 9134 | /// Does nothing to a node that is not a [`Timeline`](Self::Timeline), on |
| 9135 | /// [`figure`](Self::figure)'s rule. |
| 9136 | |
| 9137 | |
| 9138 | if let SelfTimeline = &mut self |
| 9139 | entries.push; |
| 9140 | |
| 9141 | self |
| 9142 | |
| 9143 | |
| 9144 | /// The moment the axis should bring into view, in minutes from its start. |
| 9145 | /// |
| 9146 | /// Does nothing to a node that is not a [`Timeline`](Self::Timeline), on |
| 9147 | /// [`figure`](Self::figure)'s rule. |
| 9148 | |
| 9149 | |
| 9150 | if let SelfTimeline = &mut self |
| 9151 | *focus = Some; |
| 9152 | |
| 9153 | self |
| 9154 | |
| 9155 | |
| 9156 | /// A tag standing on its own, rather than inside a row or a cell. |
| 9157 | /// |
| 9158 | /// [`Token`](Self::Token) was the last node variant with no constructor of |
| 9159 | /// its own once [`literal`](Self::literal) landed. A chip that is a control |
| 9160 | /// carries its action and its latched state on the tag. |
| 9161 | |
| 9162 | |
| 9163 | SelfToken |
| 9164 | |
| 9165 | |
| 9166 | /// Time counting up from an instant. |
| 9167 | |
| 9168 | pub const |
| 9169 | SelfSince |
| 9170 | |
| 9171 | |
| 9172 | /// Time counting down to an instant. |
| 9173 | |
| 9174 | pub const |
| 9175 | SelfUntil |
| 9176 | |
| 9177 | |
| 9178 | /// How long ago an instant was. |
| 9179 | |
| 9180 | pub const |
| 9181 | SelfAge |
| 9182 | |
| 9183 | |
| 9184 | /// Which way this node runs against the current time, and from when. |
| 9185 | /// |
| 9186 | /// `None` for everything that is not a time-derived readout, which is most |
| 9187 | /// of the vocabulary. A renderer reads this to format one; a host reads |
| 9188 | /// [`Screen::clocks`] to decide how often to draw again. |
| 9189 | |
| 9190 | pub const |
| 9191 | match self |
| 9192 | SelfSince => Some, |
| 9193 | SelfUntil => Some, |
| 9194 | SelfAge => Some, |
| 9195 | _ => None, |
| 9196 | |
| 9197 | |
| 9198 | |
| 9199 | /// Every kind of time-derived readout at or under this node, added to |
| 9200 | /// `found`. |
| 9201 | /// |
| 9202 | /// Walks into the containers rather than stopping at the top, because the |
| 9203 | /// measured case is a readout in a row: goingson puts an elapsed time on |
| 9204 | /// task rows, and a walk that only looked at the region's own blocks would |
| 9205 | /// answer that a screen full of running timers needs no clock. |
| 9206 | |
| 9207 | if let Some = self.clock |
| 9208 | found.insert; |
| 9209 | return; |
| 9210 | |
| 9211 | match self |
| 9212 | SelfTimeline => |
| 9213 | for placed in entries |
| 9214 | placed.row.clocks_into; |
| 9215 | |
| 9216 | |
| 9217 | SelfTable => |
| 9218 | for row in rows |
| 9219 | row.clocks_into; |
| 9220 | |
| 9221 | |
| 9222 | SelfStandIn => |
| 9223 | SelfRegion => |
| 9224 | for ranked in slot.body.iter |
| 9225 | ranked.node.clocks_into; |
| 9226 | |
| 9227 | |
| 9228 | // Only the app's own nodes: a readout the creator wrote is text in |
| 9229 | // a string, and no script this renderer loads is going to tick it. |
| 9230 | SelfCanvas => |
| 9231 | for node in &canvas.within |
| 9232 | node.clocks_into; |
| 9233 | |
| 9234 | |
| 9235 | // The leaves, and the containers that hold no node. A form holds |
| 9236 | // questions, a strip holds figures, a control holds choices, and |
| 9237 | // none of those is a place a readout can be. |
| 9238 | SelfHeading |
| 9239 | | SelfText |
| 9240 | | SelfRich |
| 9241 | | SelfAct |
| 9242 | | SelfLink |
| 9243 | | SelfFigure |
| 9244 | | SelfImage |
| 9245 | | SelfToken |
| 9246 | | SelfNotice |
| 9247 | | SelfField |
| 9248 | | SelfForm |
| 9249 | | SelfMeter |
| 9250 | | SelfCode |
| 9251 | | SelfStats |
| 9252 | | SelfSince |
| 9253 | | SelfUntil |
| 9254 | | SelfAge => |
| 9255 | |
| 9256 | |
| 9257 | |
| 9258 | |
| 9259 | /// A whole screen. |
| 9260 | /// |
| 9261 | /// [`Arrangement`](layout::Arrangement) is `makeover-layout`'s, and there are |
| 9262 | /// two of them because our apps have two: goingson is list-detail, Balanced |
| 9263 | /// Breakfast is sidebar plus content. Naming a third before an app has one is |
| 9264 | /// how a description becomes a framework. |
| 9265 | |
| 9266 | |
| 9267 | /// What the screen is called. A window title, a tab title, a page heading. |
| 9268 | pub title: String, |
| 9269 | /// How the regions are laid out. |
| 9270 | pub arrangement: Arrangement, |
| 9271 | /// The regions, in order. |
| 9272 | pub slots: , |
| 9273 | /// Messages raised by whatever produced this screen. |
| 9274 | /// |
| 9275 | /// Separate from the slots because a notice belongs to the screen rather |
| 9276 | /// than to a place in it: which region a toast stacks in is the renderer's |
| 9277 | /// question, and a handler answering it would be describing a webview. |
| 9278 | pub notices: , |
| 9279 | /// How this screen is found, shared and indexed. |
| 9280 | /// |
| 9281 | /// Not an `Option`. The default is meaningful — a screen nobody said |
| 9282 | /// anything about is an indexable website — and an `Option` would make |
| 9283 | /// "nobody said" and "indexable" two spellings of one thing. |
| 9284 | pub discovery: Discovery, |
| 9285 | /// Which of the app's places this screen is, if it is one of them. |
| 9286 | /// |
| 9287 | /// A [`Chrome`](crate::Chrome) is built once and held beside the router, |
| 9288 | /// so a `current` flag on a [`Place`](crate::chrome::Place) would be |
| 9289 | /// frozen at build time and could never point at where the user is. The |
| 9290 | /// nav says what the places are; this says which one is showing, and the |
| 9291 | /// renderer marks the place whose [`key`](crate::chrome::Place::key) |
| 9292 | /// matches. |
| 9293 | /// |
| 9294 | /// The same move [`Row::current`] makes one level down, and for the same |
| 9295 | /// reason: it is the app's own pointer at what is showing, said by the |
| 9296 | /// thing that knows. |
| 9297 | /// |
| 9298 | /// `None` for a screen that is not a place in the nav. A confirmation |
| 9299 | /// drawn over one, a detail reached from a row, an app with no nav at all: |
| 9300 | /// nothing is marked, rather than the last place staying lit. |
| 9301 | /// |
| 9302 | /// A key no [`Place`](crate::chrome::Place) carries marks nothing. Not an |
| 9303 | /// error, because the nav is the app's and so is this, and a renderer is |
| 9304 | /// the wrong place to discover that an app disagrees with itself. |
| 9305 | pub place: , |
| 9306 | /// The name of the set this screen's ticks go into, if it holds one. |
| 9307 | /// |
| 9308 | /// [`Row::selected`] said a row could be ticked and nothing said what the |
| 9309 | /// tick was *for*, so the tick had nowhere to go: a webview hid the hole |
| 9310 | /// because the browser owns a checkbox's checked state, and every app then |
| 9311 | /// wrote its own JS to gather the boxes back up. A terminal could not hide |
| 9312 | /// it. It drew the `[ ]`, bound the key, and the key did nothing, which is |
| 9313 | /// worse than not drawing the box. |
| 9314 | /// |
| 9315 | /// So the screen names the set, each [`Row::value`] is what that row's tick |
| 9316 | /// contributes, and [`Act::over`] is how a control says it acts on the |
| 9317 | /// whole of it. The renderer holds the set the way `quasi-tui` already |
| 9318 | /// holds an edit buffer and a scroll offset, and the commit control reads |
| 9319 | /// it by name. |
| 9320 | /// |
| 9321 | /// # Ticking never writes |
| 9322 | /// |
| 9323 | /// Wiki `explicit-commit-affordance`, the general rule: a change that |
| 9324 | /// happens with no obvious indication is confusing, so a tick stages and |
| 9325 | /// the commit control is what locks it in. [`Row::toggle`] describes the |
| 9326 | /// other thing — screens where the tick *is* the write — and is left alone |
| 9327 | /// here rather than removed, because stopping those screens is work in the |
| 9328 | /// apps that have them. |
| 9329 | /// |
| 9330 | /// # One set per screen |
| 9331 | /// |
| 9332 | /// A screen with two independent sets has not been measured. Naming one is |
| 9333 | /// the smallest thing that closes the hole, and the field grows to a map |
| 9334 | /// when an app turns up wanting two, on the same rule every other member |
| 9335 | /// here arrived under. |
| 9336 | /// |
| 9337 | /// [`Row::selected`]: Row::selected |
| 9338 | /// [`Row::value`]: Row::value |
| 9339 | /// [`Act::over`]: Act::over |
| 9340 | pub selection: , |
| 9341 | /// Which of this screen's questions the caret starts in, by |
| 9342 | /// [`Field::name`]. |
| 9343 | /// |
| 9344 | /// A statement about the screen rather than a flag on a field, and it is |
| 9345 | /// the same move [`place`](Self::place) and [`selection`](Self::selection) |
| 9346 | /// already make: the screen names the one thing that is true of it once, |
| 9347 | /// and a fact that cannot be said twice beats a field-local flag that can. |
| 9348 | /// Two fields each claiming the caret is a description arguing with itself, |
| 9349 | /// and there would be no honest way for a renderer to settle it. |
| 9350 | /// |
| 9351 | /// `None` for nearly every screen, which is every screen the reader arrives |
| 9352 | /// at to read. It is the sessionless form -- a login, a password reset -- |
| 9353 | /// where the only thing to do is type, and the caret starting anywhere else |
| 9354 | /// is a keystroke the reader has to spend before they can begin. |
| 9355 | /// |
| 9356 | /// A name no [`Field`] on the screen carries marks nothing. Not an error, |
| 9357 | /// for [`place`](Self::place)'s reason: the screen is the app's and so is |
| 9358 | /// the name, and a renderer is the wrong place to discover that an app |
| 9359 | /// disagrees with itself. |
| 9360 | /// |
| 9361 | /// # This is the initial caret, not [`Choosing::Through`]'s refusal |
| 9362 | /// |
| 9363 | /// The two get conflated because both are about focus, and they are about |
| 9364 | /// different focus. [`Choosing::Through`] declines to carry the *live* |
| 9365 | /// focus -- where the app is pointing right now, as a range is dragged -- |
| 9366 | /// on the grounds that a renderer naming it would be answering with the row |
| 9367 | /// it drew a frame ago while the app holds the moving one. This is the |
| 9368 | /// opposite fact: where the caret is before the reader has done anything, |
| 9369 | /// which nothing else knows and only the description can say. |
| 9370 | /// |
| 9371 | /// Nothing here moves the caret afterwards. A screen cannot pull focus back |
| 9372 | /// on a redraw, and a renderer that read this on every frame would take the |
| 9373 | /// caret away from wherever the reader had walked it to. |
| 9374 | /// |
| 9375 | /// # A renderer honours it once, on arrival |
| 9376 | /// |
| 9377 | /// `quasi-webview` emits `autofocus`, and only in a whole document: most of |
| 9378 | /// what a browser is answered with is a fragment, and moving the caret on a |
| 9379 | /// swap takes it out of whatever the reader was typing into. `quasi-tui` |
| 9380 | /// puts its caret on the matching stop when the screen arrives, and |
| 9381 | /// `quasi-immediate` asks egui for focus on the first frame of one. |
| 9382 | /// |
| 9383 | /// [`Field::name`]: Field::name |
| 9384 | /// [`Choosing::Through`]: Choosing::Through |
| 9385 | pub opens_at: , |
| 9386 | /// How wide this screen's content runs. |
| 9387 | /// |
| 9388 | /// Measured in the MNW server, where 69 of 72 templates carry one of three |
| 9389 | /// mutually exclusive CSS classes for it and nothing described it, so the |
| 9390 | /// choice lived in the template rather than in the screen. |
| 9391 | /// |
| 9392 | /// Beside [`arrangement`](Self::arrangement) and answering the level above |
| 9393 | /// it: that one divides the screen's width between regions, this says how |
| 9394 | /// much of the window the screen takes in the first place. Both are the |
| 9395 | /// description's, which is what answering `e0fd485e` and `0eccff0d` |
| 9396 | /// together settled. |
| 9397 | /// |
| 9398 | /// Not an `Option`, for [`discovery`](Self::discovery)'s reason. The |
| 9399 | /// default is meaningful -- a screen nobody said anything about uses the |
| 9400 | /// window it was given -- and an `Option` would make "nobody said" and |
| 9401 | /// "the whole width" two spellings of one thing. |
| 9402 | pub measure: Measure, |
| 9403 | /// What this screen is about the document it is drawn into. |
| 9404 | /// |
| 9405 | /// Not an `Option`, for [`discovery`](Self::discovery)'s reason: an empty |
| 9406 | /// [`Document`] is what nearly every screen says, and it is meaningful. |
| 9407 | pub document: Document, |
| 9408 | |
| 9409 | |
| 9410 | /// What a screen is about the document it is drawn into. |
| 9411 | /// |
| 9412 | /// Every |
| 9413 | /// [`Response`](crate::Response) variant changes what is inside `<body>`, and |
| 9414 | /// nothing changed anything outside it: a webview host builds its shell once |
| 9415 | /// and hands it to the renderer as an `Arc` before app state exists, so the |
| 9416 | /// head and the `<body>` tag were fixed for the process. A screen that needed |
| 9417 | /// its own could only be served by a route family of its own, which is how |
| 9418 | /// MNW's embeds ended up with a `document()` function outside the adapter. |
| 9419 | /// |
| 9420 | /// Chosen against `Response::Reload`, which was the other option. That is a |
| 9421 | /// host instruction rather than a description of a screen, and it is the line |
| 9422 | /// the vocabulary has held throughout; it also served only one of the two |
| 9423 | /// consumers. |
| 9424 | /// |
| 9425 | /// # What belongs here, and what does not |
| 9426 | /// |
| 9427 | /// The facts that are true of the whole document and that no region can carry. |
| 9428 | /// Both members are markup-shaped and that is deliberate: this is where a host |
| 9429 | /// keeps what its own taxonomy says, not where the vocabulary grows a word for |
| 9430 | /// it. A layout width is [`Screen::measure`] and belongs there; MNW's |
| 9431 | /// `admin-page` grouping and its 24 one-off screen-identity tokens are the |
| 9432 | /// app's own and belong here, which `16ba941e` settled by closing with no |
| 9433 | /// quasi crate gaining a field for them. |
| 9434 | /// |
| 9435 | /// # Every host but the webview ignores it |
| 9436 | /// |
| 9437 | /// A terminal has no document and neither does an egui frame, so both drop it |
| 9438 | /// whole -- the rule [`Binding::key`] already states for a key one host has |
| 9439 | /// never heard of. That is why this is here rather than in `makeover-layout`: |
| 9440 | /// it is not presentation, it is what the one host with a document is told |
| 9441 | /// about it. |
| 9442 | |
| 9443 | |
| 9444 | /// Classes for `<body>`, space-separated, beside whatever the host's own |
| 9445 | /// shell puts there. |
| 9446 | /// |
| 9447 | /// Beside and not instead: a host's shell carries the part that is true of |
| 9448 | /// every page and this carries the part that is true of this one, and a |
| 9449 | /// screen that replaced the global half would be a screen that had to know |
| 9450 | /// it. |
| 9451 | pub body_class: , |
| 9452 | /// Attributes for the root element, by name. |
| 9453 | /// |
| 9454 | /// goingson's pinned theme is the measured consumer: once every theme ships |
| 9455 | /// in one sheet keyed by a root attribute, changing theme is setting an |
| 9456 | /// attribute rather than swapping a `<link>`, and a screen answering a |
| 9457 | /// preference change is the thing that knows the new value. Until this |
| 9458 | /// existed the honest answer was to reload the app. |
| 9459 | /// |
| 9460 | /// A name a renderer will not write is dropped rather than refused, for |
| 9461 | /// [`Screen::replace`]'s reason: a miss is worth being able to see and is |
| 9462 | /// not worth refusing to draw a screen over. What counts as writable is |
| 9463 | /// each renderer's, since it is the one that knows what its document can |
| 9464 | /// hold -- see [`writable_root_attr`]. |
| 9465 | pub root: , |
| 9466 | /// A stylesheet this one document gets, on top of the host's own. |
| 9467 | /// |
| 9468 | /// For a document whose styling is data rather than build output: MNW's |
| 9469 | /// custom pages serve creator-authored CSS, sanitised and re-scoped per |
| 9470 | /// request, and a markup renderer's shell is built once before app state |
| 9471 | /// exists, so there was nowhere for it to go. The measured consumer is the |
| 9472 | /// whole of the reason this is here. |
| 9473 | /// |
| 9474 | /// # Opaque, and sanitised by whoever produced it |
| 9475 | /// |
| 9476 | /// This crate does not read the CSS, does not scope it, and does not |
| 9477 | /// sanitise it. It cannot: what counts as safe depends on the document's |
| 9478 | /// own headers, and MNW's answer is a `lightningcss` pass that rewrites |
| 9479 | /// every selector under a canvas id plus a `default-src 'none'` CSP. An |
| 9480 | /// app handing raw reader input here is handing raw reader input to a |
| 9481 | /// browser, and nothing below this line will save it. |
| 9482 | /// |
| 9483 | /// What a renderer does owe is that the string cannot end the element it is |
| 9484 | /// written into, which is a markup concern rather than a CSS one and is |
| 9485 | /// therefore the renderer's -- see `quasi-webview`'s `push_style`. |
| 9486 | /// |
| 9487 | /// # Last, so it wins |
| 9488 | /// |
| 9489 | /// A per-document sheet exists to override what every document shares, so a |
| 9490 | /// markup renderer writes it after the shell's own and outside the cascade |
| 9491 | /// layers the shell declares. Unlayered rules beat layered ones, which is |
| 9492 | /// what makes "on top of" true without this having to name a layer. |
| 9493 | /// |
| 9494 | /// # Every host but the webview ignores it |
| 9495 | /// |
| 9496 | /// A terminal has no stylesheet to add one to and draws the screen the way |
| 9497 | /// it draws every other, the same read [`Binding::key`] gets from a host |
| 9498 | /// that has never heard of the key. |
| 9499 | /// |
| 9500 | /// [`Binding::key`]: crate::chrome::Binding::key |
| 9501 | pub style: , |
| 9502 | |
| 9503 | |
| 9504 | |
| 9505 | /// Give this document a stylesheet of its own, on top of the host's. |
| 9506 | /// |
| 9507 | /// Replaces rather than adds, unlike [`rooted`](Self::rooted): a document |
| 9508 | /// has one sheet of its own, and two callers each setting one would be two |
| 9509 | /// answers to what this document looks like rather than two facts about it. |
| 9510 | |
| 9511 | |
| 9512 | self.style = Some; |
| 9513 | self |
| 9514 | |
| 9515 | |
| 9516 | /// Put these classes on `<body>`, beside the host shell's own. |
| 9517 | |
| 9518 | |
| 9519 | self.body_class = Some; |
| 9520 | self |
| 9521 | |
| 9522 | |
| 9523 | /// Set this attribute on the root element. |
| 9524 | /// |
| 9525 | /// Adds rather than replaces, for [`Field::consults`]' reason: a document |
| 9526 | /// saying two things about its root is saying two things, and a builder |
| 9527 | /// that took the last call would make the pair unwritable. A name given |
| 9528 | /// twice is a description arguing with itself, and the renderer takes the |
| 9529 | /// first. |
| 9530 | |
| 9531 | |
| 9532 | self.root.push; |
| 9533 | self |
| 9534 | |
| 9535 | |
| 9536 | |
| 9537 | /// Whether a renderer with a markup document will write this root attribute. |
| 9538 | /// |
| 9539 | /// A description names the attribute and a host writes it, so the name reaches |
| 9540 | /// markup as a name rather than as a value and cannot be escaped the way a |
| 9541 | /// value is. `data-theme` is the shape the measured consumer wants; `x |
| 9542 | /// onload=alert(1)` is the shape this refuses. |
| 9543 | /// |
| 9544 | /// ASCII letters, digits and `-`, starting with a letter. Deliberately narrower |
| 9545 | /// than what HTML permits: every attribute anybody has wanted here fits, and |
| 9546 | /// the cost of being wrong is a script tag rather than a missing class. |
| 9547 | /// |
| 9548 | /// Here rather than in the webview because the terminal and egui hosts answer |
| 9549 | /// the same question if they ever grow a document, and a second copy is a |
| 9550 | /// second answer. |
| 9551 | |
| 9552 | |
| 9553 | let mut characters = name.chars; |
| 9554 | characters.next.is_some_and |
| 9555 | && characters.all |
| 9556 | |
| 9557 | |
| 9558 | /// How a screen is found, shared and indexed. |
| 9559 | /// |
| 9560 | /// Not presentation, which is why it is here and not in `makeover-layout`: a |
| 9561 | /// terminal ignores every field, the same way it ignores [`Slot::id`]. It is an |
| 9562 | /// address-and-identity fact, and that is the line that put [`Action`] in this |
| 9563 | /// crate rather than in the vocabulary. |
| 9564 | /// |
| 9565 | /// Measured before it was added. Every `og:*` value in the MNW server's 37 |
| 9566 | /// templates is one of four things interpolated from the entity the screen is |
| 9567 | /// about: a title, a summary sentence, an image URL, or the screen's own |
| 9568 | /// address. None of them needed knowledge only a handler has, which is what |
| 9569 | /// made this the screen's to say rather than the host's. |
| 9570 | |
| 9571 | |
| 9572 | /// Whether a crawler should index this screen. |
| 9573 | /// |
| 9574 | /// Defaults to indexable, because most screens are and a default that hides |
| 9575 | /// pages is a default that hides the bug. The six screens saying otherwise |
| 9576 | /// are purchased-content pages, and this field is why that is a fact the |
| 9577 | /// type carries rather than a line in a template that a conversion can drop |
| 9578 | /// in silence. |
| 9579 | pub indexable: bool, |
| 9580 | /// The sentence a link preview shows. [`Screen::title`] is the title. |
| 9581 | pub summary: , |
| 9582 | /// The image a link preview shows, as an absolute URL. |
| 9583 | pub image: , |
| 9584 | /// What kind of thing this screen is about. |
| 9585 | pub kind: SocialKind, |
| 9586 | /// The canonical address, when the screen answers at more than one. |
| 9587 | pub canonical: , |
| 9588 | /// The syndication feed this screen offers, if it offers one. |
| 9589 | /// |
| 9590 | /// Singular, because no measured screen offers two: MNW's project blog, a |
| 9591 | /// user's page and a project page each publish exactly one. It widens to a |
| 9592 | /// `Vec` on the day a screen actually offers a second, which is the rule |
| 9593 | /// every other member here arrived under. |
| 9594 | /// |
| 9595 | /// Typed rather than a MIME string. `application/rss+xml` written out at |
| 9596 | /// each of the three sites is three chances to write `application/rss` and |
| 9597 | /// have a reader skip it, and the spelling is the same on every host, so it |
| 9598 | /// belongs to [`FeedKind::media_type`] rather than to whoever is describing |
| 9599 | /// the screen. |
| 9600 | /// |
| 9601 | /// # What a renderer with no autodiscovery does |
| 9602 | /// |
| 9603 | /// Ignores it, the way it already ignores [`image`](Self::image). A feed is |
| 9604 | /// a fact a browser acts on -- it is what `<link rel="alternate">` says -- |
| 9605 | /// and a terminal has nothing to hand it to. Said here rather than left to |
| 9606 | /// each renderer's author to guess, because a guess is how two hosts end up |
| 9607 | /// disagreeing about what a description means. |
| 9608 | pub feed: , |
| 9609 | |
| 9610 | |
| 9611 | /// A syndication feed a screen offers. |
| 9612 | /// |
| 9613 | /// Three members and no more: what kind of document it is, what it is called, |
| 9614 | /// and where it is. That is the whole of what a browser's autodiscovery reads, |
| 9615 | /// and anything else here would be describing the feed's contents rather than |
| 9616 | /// its existence. |
| 9617 | |
| 9618 | |
| 9619 | /// What kind of feed document it is. |
| 9620 | pub kind: FeedKind, |
| 9621 | /// What it is called, which is what a reader's subscribe list shows. |
| 9622 | /// |
| 9623 | /// Not [`Screen::title`], and not derived from it. A page titled "Max |
| 9624 | /// Johnson" offers a feed called "Max Johnson's posts", and a subscribe |
| 9625 | /// list holding a dozen entries called after their pages is a list nobody |
| 9626 | /// can read. |
| 9627 | pub title: String, |
| 9628 | /// Where the feed document is. |
| 9629 | pub href: String, |
| 9630 | |
| 9631 | |
| 9632 | |
| 9633 | /// A feed, by kind, name and address. |
| 9634 | |
| 9635 | Self |
| 9636 | kind, |
| 9637 | title: title.into, |
| 9638 | href: href.into, |
| 9639 | |
| 9640 | |
| 9641 | |
| 9642 | |
| 9643 | /// What kind of syndication document a feed is. |
| 9644 | /// |
| 9645 | /// The three formats a browser and every reader understand. `#[non_exhaustive]` |
| 9646 | /// for [`SocialKind`]'s reason: a fourth arriving should not be a lockstep |
| 9647 | /// event across every renderer that spells one. |
| 9648 | |
| 9649 | |
| 9650 | |
| 9651 | /// RSS 2.0. The default, and what all three measured screens publish. |
| 9652 | |
| 9653 | Rss, |
| 9654 | /// Atom. |
| 9655 | Atom, |
| 9656 | /// JSON Feed. |
| 9657 | JsonFeed, |
| 9658 | |
| 9659 | |
| 9660 | |
| 9661 | /// What this is spelled as in a `type` attribute. |
| 9662 | /// |
| 9663 | /// Named here rather than agreed between each renderer and each host, for |
| 9664 | /// [`SocialKind::as_str`]'s reason: that is how one screen ends up |
| 9665 | /// `application/rss+xml` and the next `application/rss`. |
| 9666 | |
| 9667 | pub const |
| 9668 | match self |
| 9669 | SelfRss => "application/rss+xml", |
| 9670 | SelfAtom => "application/atom+xml", |
| 9671 | SelfJsonFeed => "application/feed+json", |
| 9672 | |
| 9673 | |
| 9674 | |
| 9675 | |
| 9676 | |
| 9677 | /// Indexable, and nothing else claimed. |
| 9678 | /// |
| 9679 | /// Written out rather than derived, and the reason is the one field that |
| 9680 | /// matters: `bool::default()` is `false`, so a derived impl would deindex |
| 9681 | /// every screen that never mentioned the subject, silently, and the failure |
| 9682 | /// would show up as traffic rather than as a test. |
| 9683 | |
| 9684 | Self |
| 9685 | indexable: true, |
| 9686 | summary: None, |
| 9687 | image: None, |
| 9688 | kind: Website, |
| 9689 | canonical: None, |
| 9690 | feed: None, |
| 9691 | |
| 9692 | |
| 9693 | |
| 9694 | |
| 9695 | /// What kind of thing a screen is about. |
| 9696 | /// |
| 9697 | /// The six the server actually emits, and no more. Naming a seventh before a |
| 9698 | /// screen has one is how a description becomes a framework, which is the |
| 9699 | /// argument [`Arrangement`](layout::Arrangement) is held to two screens by. |
| 9700 | /// |
| 9701 | /// `#[non_exhaustive]`, because a seventh arriving should not be a lockstep |
| 9702 | /// event across every renderer that spells one. The match below stays |
| 9703 | /// exhaustive: within this crate the attribute does not apply, and a wildcard |
| 9704 | /// here would only hide a member added without a spelling. |
| 9705 | |
| 9706 | |
| 9707 | |
| 9708 | /// A page. The default, and four of the server's screens. |
| 9709 | |
| 9710 | Website, |
| 9711 | /// Something written, with an author and a date. |
| 9712 | Article, |
| 9713 | /// A person or an account. |
| 9714 | Profile, |
| 9715 | /// Something for sale. |
| 9716 | Product, |
| 9717 | /// A video. |
| 9718 | Video, |
| 9719 | /// A piece of music. |
| 9720 | Song, |
| 9721 | |
| 9722 | |
| 9723 | |
| 9724 | /// What this is spelled as in `og:type`. |
| 9725 | /// |
| 9726 | /// Named here rather than agreed between each renderer and each host, which |
| 9727 | /// is how one screen ends up `video.other` and the next `video`. |
| 9728 | |
| 9729 | pub const |
| 9730 | match self |
| 9731 | SelfWebsite => "website", |
| 9732 | SelfArticle => "article", |
| 9733 | SelfProfile => "profile", |
| 9734 | SelfProduct => "product", |
| 9735 | SelfVideo => "video.other", |
| 9736 | SelfSong => "music.song", |
| 9737 | |
| 9738 | |
| 9739 | |
| 9740 | |
| 9741 | |
| 9742 | /// An empty screen with the given arrangement. |
| 9743 | |
| 9744 | Self |
| 9745 | title: title.into, |
| 9746 | arrangement, |
| 9747 | slots: Vecnew, |
| 9748 | notices: Vecnew, |
| 9749 | discovery: default, |
| 9750 | place: None, |
| 9751 | selection: None, |
| 9752 | opens_at: None, |
| 9753 | measure: default, |
| 9754 | document: default, |
| 9755 | |
| 9756 | |
| 9757 | |
| 9758 | /// Say what this screen is about the document it is drawn into. |
| 9759 | /// |
| 9760 | /// See [`document`](Self::document). Replaces rather than adds, because a |
| 9761 | /// [`Document`] is one statement built with its own builders: |
| 9762 | /// `screen.documented(Document::default().classed("admin-page"))`. |
| 9763 | |
| 9764 | |
| 9765 | self.document = document; |
| 9766 | self |
| 9767 | |
| 9768 | |
| 9769 | /// Say which of the app's places this screen is. |
| 9770 | /// |
| 9771 | /// See [`place`](Self::place). The key is a |
| 9772 | /// [`Place::key`](crate::chrome::Place::key), not a label and not an |
| 9773 | /// address. |
| 9774 | |
| 9775 | |
| 9776 | self.place = Some; |
| 9777 | self |
| 9778 | |
| 9779 | |
| 9780 | /// How wide this screen's content runs, chaining. |
| 9781 | /// |
| 9782 | /// See [`measure`](Self::measure). [`Measure::Wide`](layout::Measure::Wide) |
| 9783 | /// is the default and does not need saying. |
| 9784 | |
| 9785 | pub const |
| 9786 | self.measure = measure; |
| 9787 | self |
| 9788 | |
| 9789 | |
| 9790 | /// This screen holds a set of ticks under this name, chaining. |
| 9791 | /// |
| 9792 | /// The rows that join it say so with [`Row::ticking`], and the control that |
| 9793 | /// acts on it with [`Act::over`]. See [`selection`](Self::selection). |
| 9794 | |
| 9795 | |
| 9796 | self.selection = Some; |
| 9797 | self |
| 9798 | |
| 9799 | |
| 9800 | /// The caret starts in this question, chaining. |
| 9801 | /// |
| 9802 | /// The name is a [`Field::name`], which is what the value is submitted |
| 9803 | /// under, and not the label. See [`opens_at`](Self::opens_at). |
| 9804 | |
| 9805 | |
| 9806 | self.opens_at = Some; |
| 9807 | self |
| 9808 | |
| 9809 | |
| 9810 | /// Whether a crawler should index this screen, chaining. |
| 9811 | |
| 9812 | |
| 9813 | self.discovery.indexable = indexable; |
| 9814 | self |
| 9815 | |
| 9816 | |
| 9817 | /// The sentence a link preview shows, chaining. |
| 9818 | |
| 9819 | |
| 9820 | self.discovery.summary = Some; |
| 9821 | self |
| 9822 | |
| 9823 | |
| 9824 | /// The image a link preview shows, chaining. An absolute URL. |
| 9825 | |
| 9826 | |
| 9827 | self.discovery.image = Some; |
| 9828 | self |
| 9829 | |
| 9830 | |
| 9831 | /// What kind of thing this screen is about, chaining. |
| 9832 | |
| 9833 | |
| 9834 | self.discovery.kind = kind; |
| 9835 | self |
| 9836 | |
| 9837 | |
| 9838 | /// The address this screen should be known by, chaining. |
| 9839 | |
| 9840 | |
| 9841 | self.discovery.canonical = Some; |
| 9842 | self |
| 9843 | |
| 9844 | |
| 9845 | /// The feed this screen offers, chaining. |
| 9846 | /// |
| 9847 | /// See [`Discovery::feed`]. `screen.syndicating(Feed::new(FeedKind::Rss, |
| 9848 | /// "Project updates", "/p/thing/feed.xml"))`. |
| 9849 | |
| 9850 | |
| 9851 | self.discovery.feed = Some; |
| 9852 | self |
| 9853 | |
| 9854 | |
| 9855 | /// A list that chooses what the detail beside it shows. |
| 9856 | |
| 9857 | Selfnew |
| 9858 | |
| 9859 | |
| 9860 | /// Navigation down the side, content filling the rest. |
| 9861 | |
| 9862 | Selfnew |
| 9863 | |
| 9864 | |
| 9865 | /// One region, filling the document. |
| 9866 | /// |
| 9867 | /// What a screen with nothing beside anything says. |
| 9868 | |
| 9869 | Selfnew |
| 9870 | |
| 9871 | |
| 9872 | /// Add a region, chaining. |
| 9873 | |
| 9874 | |
| 9875 | self.slots.push; |
| 9876 | self |
| 9877 | |
| 9878 | |
| 9879 | /// Raise a message on this screen, chaining. |
| 9880 | /// |
| 9881 | /// # Panics |
| 9882 | /// |
| 9883 | /// If the node is not a [`Node::Notice`]. The field is typed as a [`Node`] |
| 9884 | /// so a renderer walks one kind of thing, and this is the constructor that |
| 9885 | /// keeps that from meaning anything can go in it. |
| 9886 | |
| 9887 | |
| 9888 | assert! |
| 9889 | matches!, |
| 9890 | "Screen::saying takes a Node::Notice" |
| 9891 | ; |
| 9892 | self.notices.push; |
| 9893 | self |
| 9894 | |
| 9895 | |
| 9896 | /// What this screen offers under a control name. |
| 9897 | /// |
| 9898 | /// The described half of a [`Reveal`], asked of the whole screen. See |
| 9899 | /// [`Slot::holds`], and [`Node::holds`] for what the walk reaches. |
| 9900 | |
| 9901 | |
| 9902 | self.slots.iter.find_map |
| 9903 | |
| 9904 | |
| 9905 | /// The slot under this address, at any depth. |
| 9906 | |
| 9907 | |
| 9908 | self.slots.iter.find_map |
| 9909 | |
| 9910 | |
| 9911 | /// Whether any row on this screen is part of a live selection. |
| 9912 | /// |
| 9913 | /// [`Row::chosen`] is a fact on a row rather than on the screen -- |
| 9914 | /// unlike [`selection`](Self::selection), which names the staged set -- so |
| 9915 | /// the answer is a walk. Every region, nested ones included, for |
| 9916 | /// [`slot`](Self::slot)'s reason: a table inside a pane inside the shell is |
| 9917 | /// where every real app puts its list. |
| 9918 | /// |
| 9919 | /// Walked rather than cached. A screen is built fresh per answer, and a |
| 9920 | /// cached fact about its rows is one more thing that can disagree with them. |
| 9921 | |
| 9922 | |
| 9923 | self.slots.iter.any |
| 9924 | |
| 9925 | |
| 9926 | /// Whether this screen carries the thing an anchor names. |
| 9927 | /// |
| 9928 | /// One answer for all three renderers rather than three walks that can |
| 9929 | /// disagree, which is the same argument [`replace`](Self::replace) makes: |
| 9930 | /// deciding what a description means is this crate's, and a host writing |
| 9931 | /// it means every host picks its own answer. |
| 9932 | /// |
| 9933 | /// A renderer asks this to know whether it can draw an |
| 9934 | /// [`Outcome::Anchored`](crate::Outcome::Anchored) *at* something or has to |
| 9935 | /// fall back to drawing it over everything. `false` is a description bug -- |
| 9936 | /// a route answered with an anchor naming what is not on the screen -- and |
| 9937 | /// every renderer degrades rather than refusing, the way a missing region |
| 9938 | /// does. |
| 9939 | |
| 9940 | |
| 9941 | match anchor |
| 9942 | crateRegion => self.slot.is_some, |
| 9943 | // Set at all is the whole of it, matching how `Act::over` reads its |
| 9944 | // own name. See `Anchor::Selection`. |
| 9945 | // |
| 9946 | // Either kind of selection, since `1894e95d`: a screen naming a |
| 9947 | // staged set has one, and so does a screen whose rows carry a live |
| 9948 | // one. The second is what audiofiles' file list has, and reading |
| 9949 | // only the first is what made this answer `false` there while the |
| 9950 | // reader was looking at eleven chosen rows. |
| 9951 | crateSelection => self.selection.is_some || self.chooses, |
| 9952 | crateControl => |
| 9953 | self.notices.iter.any |
| 9954 | || self.slots.iter.any |
| 9955 | |
| 9956 | |
| 9957 | |
| 9958 | |
| 9959 | /// Every call this screen's regions are waiting on, in draw order. |
| 9960 | /// |
| 9961 | /// What a host performs after putting a screen up: each answer comes back as |
| 9962 | /// a [`Response::Fragment`] naming the region, and [`replace`](Self::replace) |
| 9963 | /// clears the feed as it lands, so asking again after applying one is |
| 9964 | /// answered with what is still outstanding rather than with the same list. |
| 9965 | /// |
| 9966 | /// Empty for every screen that has all of its content, which is nearly all |
| 9967 | /// of them. |
| 9968 | |
| 9969 | |
| 9970 | let mut out = Vecnew; |
| 9971 | for slot in &self.slots |
| 9972 | slot.feeds_into; |
| 9973 | |
| 9974 | out |
| 9975 | |
| 9976 | |
| 9977 | /// Every region on this screen that asks a question of its own, at any |
| 9978 | /// depth, in draw order. |
| 9979 | /// |
| 9980 | /// What a renderer walks when a question moves: the regions holding that |
| 9981 | /// question are the ones whose [`Slot::consults`] the keystroke set off, |
| 9982 | /// and [`Slot::questions`] says which those are. |
| 9983 | /// |
| 9984 | /// Empty for nearly every screen, which is every screen written before |
| 9985 | /// [`Slot::consults`] existed, so the ordinary keystroke pays a walk and no |
| 9986 | /// more. |
| 9987 | |
| 9988 | |
| 9989 | let mut out = Vecnew; |
| 9990 | for slot in &self.slots |
| 9991 | slot.consulting_into; |
| 9992 | |
| 9993 | out |
| 9994 | |
| 9995 | |
| 9996 | /// Every call this screen's regions re-ask on a cadence, in draw order. |
| 9997 | /// |
| 9998 | /// [`feeds`](Self::feeds)'s counterpart, and the two never return the same |
| 9999 | /// call: a feed arrives once and a refresh never stops. What a host does |
| 10000 | /// with these is ask again on whatever interval its renderer picked, and |
| 10001 | /// keep doing it for as long as the screen is up. |
| 10002 | /// |
| 10003 | /// Empty for every screen with no [`Slot::live`] region, which is nearly |
| 10004 | /// all of them. A live region that names no call is not here either — there |
| 10005 | /// is nothing to ask — and the host re-reads it by redrawing. |
| 10006 | |
| 10007 | |
| 10008 | let mut out = Vecnew; |
| 10009 | for slot in &self.slots |
| 10010 | slot.refreshes_into; |
| 10011 | |
| 10012 | out |
| 10013 | |
| 10014 | |
| 10015 | /// Whether anything on this screen changes without the user. |
| 10016 | /// |
| 10017 | /// What a retained host asks to decide whether to keep drawing. True for a |
| 10018 | /// live region whether or not it names a call, which is the difference from |
| 10019 | /// [`refreshes`](Self::refreshes): the audiofiles sync panel reads state the |
| 10020 | /// host already holds, so its cadence is a repaint and there is no request |
| 10021 | /// to make. |
| 10022 | |
| 10023 | |
| 10024 | self.slots.iter.any |
| 10025 | |
| 10026 | |
| 10027 | /// Every kind of time-derived readout on this screen, at any depth. |
| 10028 | /// |
| 10029 | /// What a renderer reads to pick a cadence: the description says a readout |
| 10030 | /// is derived from now and which way it runs, and how often to redraw |
| 10031 | /// follows the granularity the renderer chose to show it at. A screen of |
| 10032 | /// running stopwatches wants a second and a screen of last-modified stamps |
| 10033 | /// does not, and this is the difference said in the one place a host can |
| 10034 | /// act on it. |
| 10035 | /// |
| 10036 | /// A set rather than a count, because the cadence question is per kind: |
| 10037 | /// every [`Clock::Since`] on the screen redraws together, which is |
| 10038 | /// many-readouts-one-tick falling out of the vocabulary rather than being |
| 10039 | /// arranged by hand. |
| 10040 | /// |
| 10041 | /// Empty for nearly every screen, which is the cheap answer a host asks for |
| 10042 | /// on each draw. [`is_live`](Self::is_live) is the region-level fact beside |
| 10043 | /// it, and the two are independent: a still region can hold a stopwatch, |
| 10044 | /// and a live region usually holds none. |
| 10045 | |
| 10046 | |
| 10047 | let mut found = new; |
| 10048 | for node in self.notices.iter.chain |
| 10049 | self.slots |
| 10050 | .iter |
| 10051 | .flat_map, |
| 10052 | |
| 10053 | node.clocks_into; |
| 10054 | |
| 10055 | found |
| 10056 | |
| 10057 | |
| 10058 | /// Apply a fragment: put `node` in the region under `region`, replacing |
| 10059 | /// whatever was there. Returns whether the region was found. |
| 10060 | /// |
| 10061 | /// This is what a host holding a `Screen` does with |
| 10062 | /// [`Outcome::Fragment`](crate::Outcome::Fragment). A webview host needs |
| 10063 | /// none of it -- `quasi-http` turns the same outcome into an `hx-retarget` |
| 10064 | /// header and the browser performs the swap against a document it already |
| 10065 | /// has -- but a host that retains the description rather than the markup |
| 10066 | /// has nothing between the fragment and the tree. |
| 10067 | /// |
| 10068 | /// It lives here and not in a host because applying a fragment is surgery |
| 10069 | /// on this crate's own type. A host writing it means every retained-screen |
| 10070 | /// host writes it separately and each picks its own answer for the three |
| 10071 | /// decisions below, which is the thing this crate's no-host-imports rule |
| 10072 | /// exists to prevent. |
| 10073 | /// |
| 10074 | /// **A region that is not there answers `false`, not a panic.** The caller |
| 10075 | /// is the one that can act on it: a host can fall back to a redraw, and a |
| 10076 | /// test can assert it. What is worth avoiding is the silent no-op, because |
| 10077 | /// a miss means a route naming a slot that no longer exists, and that is a |
| 10078 | /// description bug rather than a rendering one. |
| 10079 | /// |
| 10080 | /// **It replaces rather than appends.** `Outcome::Fragment` is one region's |
| 10081 | /// new contents, which is the whole reason it can be smaller than a screen. |
| 10082 | /// |
| 10083 | /// **The region becomes [`Ready`](layout::Readiness::Ready).** A fragment |
| 10084 | /// arriving is the content arriving, so a slot marked |
| 10085 | /// [`Pending`](layout::Readiness::Pending) while it was in flight stops |
| 10086 | /// being pending here, and a [`Slot::fed_by`] naming the call that just |
| 10087 | /// answered is cleared with it. Emptiness is a different axis and rides on the node: |
| 10088 | /// a [`Node::StandIn`] carries its own state, and replacing with one is a |
| 10089 | /// region that is ready and has nothing to show. |
| 10090 | |
| 10091 | let Some = self.slots.iter_mut.find_map else |
| 10092 | return false; |
| 10093 | ; |
| 10094 | slot.body.clear; |
| 10095 | slot.body.push; |
| 10096 | slot.readiness = Ready; |
| 10097 | // The call that fed it has answered, so the region stops naming one. A |
| 10098 | // retained-screen host redraws from this tree, and a region still |
| 10099 | // pointing at its feed would ask again on the next paint. |
| 10100 | // |
| 10101 | // A live region keeps it. There the call is the cadence rather than an |
| 10102 | // arrival, so clearing it would make the region live exactly once and |
| 10103 | // then go still, and [`feeds`](Self::feeds) already refuses to return |
| 10104 | // it — the repeat is [`refreshes`](Self::refreshes)' and is paced. |
| 10105 | if !slot.live |
| 10106 | slot.fed_by = None; |
| 10107 | |
| 10108 | true |
| 10109 | |
| 10110 | |
| 10111 | /// Mark a region as waiting on work that has been handed off, saying so. |
| 10112 | /// |
| 10113 | /// [`replace`](Self::replace)'s opposite number, and what a host holding a |
| 10114 | /// `Screen` does with [`Outcome::Started`](crate::Outcome::Started). It |
| 10115 | /// lives here for the same reason: applying an outcome to a retained |
| 10116 | /// description is surgery on this crate's own type, and a host writing it |
| 10117 | /// means every retained-screen host writes it separately. |
| 10118 | /// |
| 10119 | /// **A region that is not there answers `false`.** Same contract, same |
| 10120 | /// reason: a route naming a slot that is gone is a description bug, and the |
| 10121 | /// caller is the party that can say so. |
| 10122 | /// |
| 10123 | /// **[`readiness`](Slot::readiness) becomes |
| 10124 | /// [`Pending`](layout::Readiness::Pending)**, which is what every renderer |
| 10125 | /// draws its wait from. The message goes in as a |
| 10126 | /// [`Node::pending`](Node::pending) stand-in, replacing what was there — |
| 10127 | /// so a host that draws the body under a pending region shows the sentence, |
| 10128 | /// and one that draws its own wait and returns early loses nothing it |
| 10129 | /// wanted. |
| 10130 | /// |
| 10131 | /// **[`fed_by`](Slot::fed_by) is left exactly as it is.** That call is how |
| 10132 | /// the finish gets reported, so clearing it here would be marking a region |
| 10133 | /// as waiting and removing the thing it waits on in the same breath. A |
| 10134 | /// region with no call keeps having none, and stays pending until something |
| 10135 | /// else tells it otherwise; see [`Outcome::Started`](crate::Outcome::Started) |
| 10136 | /// on why that is a description bug this cannot catch. |
| 10137 | |
| 10138 | let Some = self.slots.iter_mut.find_map else |
| 10139 | return false; |
| 10140 | ; |
| 10141 | slot.body.clear; |
| 10142 | slot.body.push; |
| 10143 | slot.readiness = Pending; |
| 10144 | true |
| 10145 | |
| 10146 | |
| 10147 | |
| 10148 | |
| 10149 | |
| 10150 | use ; |
| 10151 | |
| 10152 | /// The whole reason a cell names its column. |
| 10153 | /// |
| 10154 | /// `git_repos` builds a visibility cell only for an owner, by pushing onto |
| 10155 | /// a `Vec` whose length has to agree with a column list built under a |
| 10156 | /// second, separate conditional. Named, the two cannot disagree: the row |
| 10157 | /// answers the columns the table has. |
| 10158 | |
| 10159 | |
| 10160 | let columns = |
| 10161 | |
| 10162 | new, |
| 10163 | new, |
| 10164 | new, |
| 10165 | ] |
| 10166 | ; |
| 10167 | let row = |
| 10168 | let mut cells = default |
| 10169 | .at |
| 10170 | .at; |
| 10171 | if is_owner |
| 10172 | cells = cells.at; |
| 10173 | |
| 10174 | cells |
| 10175 | ; |
| 10176 | |
| 10177 | let owner = new.row; |
| 10178 | let stranger = new.row; |
| 10179 | |
| 10180 | let read = |
| 10181 | table.rows |
| 10182 | .cells |
| 10183 | .iter |
| 10184 | .map |
| 10185 | . |
| 10186 | ; |
| 10187 | |
| 10188 | assert_eq!; |
| 10189 | // The description stays in its own column rather than sliding left. |
| 10190 | assert_eq!; |
| 10191 | |
| 10192 | |
| 10193 | /// A column nothing named is empty rather than absent. |
| 10194 | |
| 10195 | |
| 10196 | let table = new |
| 10197 | .row; |
| 10198 | |
| 10199 | assert_eq!; |
| 10200 | assert_eq!; |
| 10201 | assert_eq!; |
| 10202 | |
| 10203 | |
| 10204 | /// Naming a column the table does not have is a bug, and it is loud. |
| 10205 | /// |
| 10206 | /// The one failure mode naming introduces: the cell is dropped, so a typo |
| 10207 | /// is an empty column rather than a compile error. Nothing above this can |
| 10208 | /// catch it, because the row and the column list are written apart, so the |
| 10209 | /// check lives where both are in hand. |
| 10210 | |
| 10211 | |
| 10212 | |
| 10213 | let _ = |
| 10214 | new.row; |
| 10215 | |
| 10216 | |
| 10217 | /// Two columns with one name cannot be addressed apart. |
| 10218 | |
| 10219 | |
| 10220 | |
| 10221 | let _ = new |
| 10222 | .row; |
| 10223 | |
| 10224 | |
| 10225 | /// A row built by position still works and is untouched by resolution. |
| 10226 | |
| 10227 | |
| 10228 | let table = new |
| 10229 | .row; |
| 10230 | |
| 10231 | assert_eq!; |
| 10232 | assert_eq!; |
| 10233 | |
| 10234 | |
| 10235 | /// The table is the node, so nothing reaches for the variant by hand. |
| 10236 | |
| 10237 | |
| 10238 | let node = from; |
| 10239 | |
| 10240 | match node |
| 10241 | Table => |
| 10242 | assert_eq!; |
| 10243 | assert_eq!; |
| 10244 | |
| 10245 | other => panic!, |
| 10246 | |
| 10247 | |
| 10248 | |
| 10249 | use *; |
| 10250 | |
| 10251 | |
| 10252 | |
| 10253 | .map |
| 10254 | .collect |
| 10255 | |
| 10256 | |
| 10257 | /// `drums` shut, `drums.kick` and `drums.snare` under it, `genre` after. |
| 10258 | |
| 10259 | vec! |
| 10260 | new.disclosing, |
| 10261 | new.depth, |
| 10262 | new.depth, |
| 10263 | new.disclosing, |
| 10264 | new.depth, |
| 10265 | ] |
| 10266 | |
| 10267 | |
| 10268 | |
| 10269 | |
| 10270 | // Every row described before these members existed, unchanged. |
| 10271 | let row = new; |
| 10272 | assert_eq!; |
| 10273 | assert_eq!; |
| 10274 | assert_eq!; |
| 10275 | assert_eq!; |
| 10276 | |
| 10277 | |
| 10278 | |
| 10279 | |
| 10280 | assert_eq! |
| 10281 | folded, |
| 10282 | , |
| 10283 | "the two under the shut `drums` go, the open `genre`'s child stays" |
| 10284 | ; |
| 10285 | |
| 10286 | |
| 10287 | |
| 10288 | |
| 10289 | let rows = vec! |
| 10290 | new.disclosing, |
| 10291 | new |
| 10292 | .depth |
| 10293 | .disclosing, |
| 10294 | new.depth, |
| 10295 | new, |
| 10296 | ]; |
| 10297 | assert_eq!; |
| 10298 | |
| 10299 | |
| 10300 | |
| 10301 | |
| 10302 | // The graceful-degradation case, from the other side: depth alone is an |
| 10303 | // indent and never hides a row. |
| 10304 | let rows: = |
| 10305 | .map |
| 10306 | .collect; |
| 10307 | assert_eq!; |
| 10308 | |
| 10309 | |
| 10310 | |
| 10311 | |
| 10312 | // Including a sibling at its own depth, which is the boundary case a |
| 10313 | // greater-than would get wrong. |
| 10314 | let rows = vec! |
| 10315 | new |
| 10316 | .depth |
| 10317 | .disclosing, |
| 10318 | new.depth, |
| 10319 | new.depth, |
| 10320 | new.depth, |
| 10321 | ]; |
| 10322 | assert_eq!; |
| 10323 | |
| 10324 | |
| 10325 | |
| 10326 | |
| 10327 | let rows = vec! |
| 10328 | cells.disclosing, |
| 10329 | cells.depth, |
| 10330 | cells, |
| 10331 | ]; |
| 10332 | assert_eq!; |
| 10333 | |
| 10334 | |
| 10335 | |
| 10336 | |
| 10337 | // `00ee7af5`. The mark is a member beside the others rather than a kind |
| 10338 | // of destination, so where the call goes is untouched by it. |
| 10339 | let action = get |
| 10340 | .carrying |
| 10341 | .navigating; |
| 10342 | assert!; |
| 10343 | assert_eq!; |
| 10344 | assert_eq!; |
| 10345 | assert_eq!; |
| 10346 | assert!; |
| 10347 | assert!; |
| 10348 | |
| 10349 | |
| 10350 | |
| 10351 | |
| 10352 | // Off by default in every constructor, so nothing described before the |
| 10353 | // member existed says anything new. |
| 10354 | assert!; |
| 10355 | assert!; |
| 10356 | assert!; |
| 10357 | assert!; |
| 10358 | assert!; |
| 10359 | |
| 10360 | |
| 10361 | |
| 10362 | |
| 10363 | let f = interval |
| 10364 | .value |
| 10365 | .upper_value; |
| 10366 | assert_eq!; |
| 10367 | assert_eq!; |
| 10368 | assert_eq!; |
| 10369 | assert_eq!; |
| 10370 | assert_eq!; |
| 10371 | // The extent stays optional, unlike a range's: an interval's bounds are |
| 10372 | // a rule on each end rather than the control. |
| 10373 | assert_eq!; |
| 10374 | assert_eq!; |
| 10375 | |
| 10376 | |
| 10377 | |
| 10378 | |
| 10379 | // The half option (b) would not have paid. Without a second value a |
| 10380 | // refusal hands back the low end and silently drops the high one, so |
| 10381 | // the user retypes half of what they already answered. |
| 10382 | let mut params = cratenew; |
| 10383 | params.insert; |
| 10384 | params.insert; |
| 10385 | let f = interval.refilled; |
| 10386 | assert_eq!; |
| 10387 | assert_eq!; |
| 10388 | |
| 10389 | |
| 10390 | |
| 10391 | |
| 10392 | // "Over 120 BPM" is an answer rather than a half-filled form, so an |
| 10393 | // absent end stays absent instead of being filled with a bound. |
| 10394 | let mut params = cratenew; |
| 10395 | params.insert; |
| 10396 | let f = interval.refilled; |
| 10397 | assert_eq!; |
| 10398 | assert_eq!; |
| 10399 | |
| 10400 | |
| 10401 | |
| 10402 | |
| 10403 | // Every other kind is untouched: `refilled` returns after the lower |
| 10404 | // half when there is no second name to read. |
| 10405 | let mut params = cratenew; |
| 10406 | params.insert; |
| 10407 | let f = new.refilled; |
| 10408 | assert_eq!; |
| 10409 | assert_eq!; |
| 10410 | |
| 10411 | |
| 10412 | |
| 10413 | |
| 10414 | let consult = new; |
| 10415 | assert_eq!; |
| 10416 | assert!; |
| 10417 | assert!; |
| 10418 | |
| 10419 | |
| 10420 | /// The pages a strip offers are the description's, and so is each one's |
| 10421 | /// address: a renderer cannot build page 5's out of prev and next without |
| 10422 | /// knowing the address grammar. |
| 10423 | |
| 10424 | |
| 10425 | let rest = page |
| 10426 | .of |
| 10427 | .back |
| 10428 | .forward |
| 10429 | .jumping |
| 10430 | .jumping |
| 10431 | .jumping; |
| 10432 | |
| 10433 | // Five pages out of eight, which is the window MNW already computes. |
| 10434 | // Nothing here windows anything: a renderer choosing its own would give |
| 10435 | // a different answer per host for one list. |
| 10436 | assert_eq!; |
| 10437 | assert_eq!; |
| 10438 | assert_eq!; |
| 10439 | |
| 10440 | // The page is carried rather than implied by position, because a window |
| 10441 | // around the reader does not start at one. |
| 10442 | assert!; |
| 10443 | assert!; |
| 10444 | assert!; |
| 10445 | |
| 10446 | |
| 10447 | /// Empty jumps is prev/next paging, which is every site that existed before |
| 10448 | /// the member did. |
| 10449 | |
| 10450 | |
| 10451 | assert!; |
| 10452 | assert!; |
| 10453 | |
| 10454 | |
| 10455 | /// A screen may say what is true of the document it is drawn into, and |
| 10456 | /// every host without one drops it whole. |
| 10457 | |
| 10458 | |
| 10459 | let plain = new; |
| 10460 | assert_eq!; |
| 10461 | assert!; |
| 10462 | assert!; |
| 10463 | |
| 10464 | let said = plain.documented |
| 10465 | default |
| 10466 | .classed |
| 10467 | .rooted |
| 10468 | .rooted, |
| 10469 | ; |
| 10470 | assert_eq!; |
| 10471 | // Adds rather than replaces: a document saying two things about its |
| 10472 | // root is saying two things. |
| 10473 | assert_eq!; |
| 10474 | |
| 10475 | |
| 10476 | /// The name reaches markup as a name rather than as a value, so a gate is |
| 10477 | /// what protects it and escaping is not. |
| 10478 | |
| 10479 | |
| 10480 | assert!; |
| 10481 | assert!; |
| 10482 | assert!; |
| 10483 | |
| 10484 | assert!; |
| 10485 | assert!; |
| 10486 | assert!; |
| 10487 | // The shapes that are the reason for the gate. |
| 10488 | assert!; |
| 10489 | assert!; |
| 10490 | assert!; |
| 10491 | |
| 10492 | |
| 10493 | /// A control chosen in one gesture has nothing to wait out, and the wait |
| 10494 | /// stays the description's rather than becoming something a kind implies. |
| 10495 | |
| 10496 | |
| 10497 | let at_once = at_once; |
| 10498 | assert_eq!; |
| 10499 | // Everything else is what `new` gives it: a floor of nothing, and the |
| 10500 | // value travelling under the field's own name. |
| 10501 | assert_eq!; |
| 10502 | assert!; |
| 10503 | assert_eq! |
| 10504 | new.after, |
| 10505 | SETTLES |
| 10506 | ; |
| 10507 | |
| 10508 | |
| 10509 | /// The question a field owns and the questions it merely asks are separate |
| 10510 | /// members, because MNW's discover box has both: a list of its own, and a |
| 10511 | /// results route that lands in a region. |
| 10512 | |
| 10513 | |
| 10514 | let field = new |
| 10515 | .suggesting |
| 10516 | .consulting; |
| 10517 | |
| 10518 | let owned = field.suggests.as_ref.expect; |
| 10519 | assert_eq!; |
| 10520 | assert_eq!; |
| 10521 | assert_eq!; |
| 10522 | |
| 10523 | // One list, so a second call is a description changing its mind rather |
| 10524 | // than asking twice. `consults` adds, for the opposite reason. |
| 10525 | let field = field.suggests; |
| 10526 | assert_eq! |
| 10527 | field |
| 10528 | .suggests |
| 10529 | .expect |
| 10530 | .action |
| 10531 | .destination |
| 10532 | .as_str, |
| 10533 | "/other" |
| 10534 | ; |
| 10535 | |
| 10536 | |
| 10537 | /// A list row and a table row say the same facts the same way. |
| 10538 | /// |
| 10539 | /// A table row gained `current` and the plural `menu` on 2026-09-02 and a |
| 10540 | /// list row did not, although both carried the same fact under the same |
| 10541 | /// name. So a screen holding both had to write one in the chain and the |
| 10542 | /// other by assignment. The 2026-09-05 collapse ended the drift by ending |
| 10543 | /// the second type; this test is what holds the parity it left behind. |
| 10544 | /// |
| 10545 | |
| 10546 | |
| 10547 | let acts = |
| 10548 | |
| 10549 | new, |
| 10550 | new, |
| 10551 | ] |
| 10552 | ; |
| 10553 | |
| 10554 | let row = new.current.menu; |
| 10555 | let cells = cells.current.menu; |
| 10556 | |
| 10557 | assert!; |
| 10558 | assert!; |
| 10559 | assert_eq!; |
| 10560 | assert_eq!; |
| 10561 | |
| 10562 | // The plural extends rather than replacing, so it composes with the |
| 10563 | // singular. |
| 10564 | let row = row.offers; |
| 10565 | assert_eq!; |
| 10566 | |
| 10567 | // And neither is set until it is said. |
| 10568 | let plain = new; |
| 10569 | assert!; |
| 10570 | assert!; |
| 10571 | |
| 10572 | |
| 10573 | /// Every fact a field carries is reachable without leaving the chain. |
| 10574 | /// |
| 10575 | /// Five members had no builder and were set by assigning the public field: |
| 10576 | /// `placeholder`, `max_length`, `min`, `max` and `extended`. That is |
| 10577 | /// the missing-builder defect on the vocabulary's largest struct, and the |
| 10578 | /// measured cost was 66 sites across the three shape trees breaking out of |
| 10579 | /// a builder chain to write one of them. It also made those five unsayable |
| 10580 | /// in a declared description, which resolves an attribute to a builder's |
| 10581 | /// name and so cannot reach a member that has none. |
| 10582 | /// |
| 10583 | |
| 10584 | |
| 10585 | let field = new |
| 10586 | .placeholder |
| 10587 | .limited_to |
| 10588 | .extended; |
| 10589 | |
| 10590 | assert_eq!; |
| 10591 | assert_eq!; |
| 10592 | assert!; |
| 10593 | // A placeholder is not a hint: one disappears when the reader types and |
| 10594 | // the other does not, so writing one must not write the other. |
| 10595 | assert!; |
| 10596 | |
| 10597 | // The extent, both ends at once, which is the ordinary spelling. |
| 10598 | let dial = new.within; |
| 10599 | assert_eq!; |
| 10600 | assert_eq!; |
| 10601 | |
| 10602 | // And one end alone, because an open end is a real answer rather than a |
| 10603 | // missing one. `Field::interval`'s own doc says so. |
| 10604 | let floor = new.at_least; |
| 10605 | assert_eq!; |
| 10606 | assert!; |
| 10607 | let ceiling = new.at_most; |
| 10608 | assert!; |
| 10609 | assert_eq!; |
| 10610 | |
| 10611 | // Nothing is set until it is said, so a field written the short way |
| 10612 | // still measures exactly as it did before these existed. |
| 10613 | let plain = new; |
| 10614 | assert!; |
| 10615 | assert!; |
| 10616 | assert!; |
| 10617 | assert!; |
| 10618 | assert!; |
| 10619 | |
| 10620 | |
| 10621 | /// A field asks nothing about its own value until it says so, and owns no |
| 10622 | /// list until it says so either. |
| 10623 | |
| 10624 | |
| 10625 | let field = new; |
| 10626 | assert!; |
| 10627 | assert!; |
| 10628 | |
| 10629 | |
| 10630 | |
| 10631 | |
| 10632 | let act = new; |
| 10633 | assert!; |
| 10634 | |
| 10635 | |
| 10636 | |
| 10637 | |
| 10638 | // The member has to be absent by default or every renderer starts |
| 10639 | // writing into a box on a press that never did before. |
| 10640 | let act = new; |
| 10641 | assert!; |
| 10642 | |
| 10643 | |
| 10644 | |
| 10645 | |
| 10646 | // MNW's media picker, measured 2026-08-19: the card reads as a file |
| 10647 | // name and deposits a markdown reference, and the two are different |
| 10648 | // strings, which is why a destination on its own is not enough. |
| 10649 | let act = new.filling; |
| 10650 | |
| 10651 | let fill = act.fills.as_ref.expect; |
| 10652 | assert_eq!; |
| 10653 | assert_eq!; |
| 10654 | // Nothing about a caret, in either direction. `d52884b0` stands. |
| 10655 | assert_eq!; |
| 10656 | |
| 10657 | |
| 10658 | |
| 10659 | |
| 10660 | // Unlike `asking`. One press deposits one value, and a control writing |
| 10661 | // into two boxes is a description doing two things at once. |
| 10662 | let act = new |
| 10663 | .filling |
| 10664 | .filling; |
| 10665 | assert_eq!; |
| 10666 | |
| 10667 | |
| 10668 | |
| 10669 | |
| 10670 | // Where a value lands is quasi's, the same as an address and a |
| 10671 | // confirmation. `layout::Act` claims to be what a renderer needs to |
| 10672 | // *draw* the control and nothing more. |
| 10673 | let act = new.filling; |
| 10674 | let drawn = act.as_layout; |
| 10675 | assert_eq!; |
| 10676 | assert_eq!; |
| 10677 | |
| 10678 | |
| 10679 | |
| 10680 | |
| 10681 | // MNW's bulk bar, measured 2026-08-18: "Set Price" and "Add Tag" each |
| 10682 | // reveal one box and apply it to whatever is ticked. Both halves are on |
| 10683 | // the control, so a renderer never has to pair a form with a verb by |
| 10684 | // where they sit on the screen. |
| 10685 | let act = new |
| 10686 | .over |
| 10687 | .asking |
| 10688 | new |
| 10689 | .hint, |
| 10690 | ; |
| 10691 | |
| 10692 | assert_eq!; |
| 10693 | assert_eq!; |
| 10694 | assert_eq!; |
| 10695 | |
| 10696 | |
| 10697 | |
| 10698 | |
| 10699 | // The value is answered by the press. A write of the box's own would |
| 10700 | // send it twice. |
| 10701 | let asked = new |
| 10702 | .writes |
| 10703 | .consulting |
| 10704 | .as_asked; |
| 10705 | |
| 10706 | assert!; |
| 10707 | // A question about the value survives: asking whether a slug is taken |
| 10708 | // is not a write of it. |
| 10709 | assert_eq!; |
| 10710 | |
| 10711 | |
| 10712 | |
| 10713 | |
| 10714 | let field = new; |
| 10715 | assert!; |
| 10716 | |
| 10717 | |
| 10718 | |
| 10719 | |
| 10720 | // MNW's discover search, measured 2026-08-18: `#search-input` re-reads |
| 10721 | // the results after 150ms and a hand-written `fetch` asks for |
| 10722 | // suggestions on its own schedule. Two questions about one value, and |
| 10723 | // only one of them was sayable. |
| 10724 | let field = new |
| 10725 | .consulting |
| 10726 | new |
| 10727 | .after |
| 10728 | .at_least, |
| 10729 | |
| 10730 | .consulting |
| 10731 | new |
| 10732 | .after |
| 10733 | .at_least |
| 10734 | .sending, |
| 10735 | ; |
| 10736 | |
| 10737 | assert_eq!; |
| 10738 | assert_eq! |
| 10739 | field.consults.after, |
| 10740 | from_millis |
| 10741 | ; |
| 10742 | assert!; |
| 10743 | assert_eq!; |
| 10744 | |
| 10745 | |
| 10746 | |
| 10747 | |
| 10748 | // The builder reading that makes two questions writable at all. A |
| 10749 | // builder taking the last call would make the shape unsayable rather |
| 10750 | // than merely awkward. |
| 10751 | let field = new |
| 10752 | .consults |
| 10753 | .consults; |
| 10754 | |
| 10755 | assert_eq!; |
| 10756 | |
| 10757 | |
| 10758 | |
| 10759 | |
| 10760 | // The common field, and the reason `sends` is a list rather than a |
| 10761 | // required argument: a validate route asks about the box and nothing |
| 10762 | // else. |
| 10763 | let consult = new; |
| 10764 | assert!; |
| 10765 | |
| 10766 | |
| 10767 | |
| 10768 | |
| 10769 | let consult = new |
| 10770 | .sending |
| 10771 | .sending; |
| 10772 | assert_eq!; |
| 10773 | |
| 10774 | |
| 10775 | |
| 10776 | |
| 10777 | // The difference the renderers are spared: "el" and "él" are the same |
| 10778 | // question, and a byte count refuses one of them. |
| 10779 | let consult = new.at_least; |
| 10780 | assert!; |
| 10781 | assert!; |
| 10782 | assert!; |
| 10783 | assert!; |
| 10784 | |
| 10785 | |
| 10786 | |
| 10787 | |
| 10788 | // The default has to be the old behaviour: a screen written before this |
| 10789 | // field existed says the same thing after it arrives. |
| 10790 | let slot = new; |
| 10791 | assert!; |
| 10792 | assert!; |
| 10793 | |
| 10794 | |
| 10795 | |
| 10796 | |
| 10797 | // The split that keeps a host from having to work out which kind of |
| 10798 | // call it is holding. One walk answers "ask now", the other "keep |
| 10799 | // asking", and no action is in both. |
| 10800 | let screen = sidebar_content |
| 10801 | .with |
| 10802 | .with |
| 10803 | new |
| 10804 | .fed_by |
| 10805 | .live, |
| 10806 | ; |
| 10807 | |
| 10808 | let feeds: = screen |
| 10809 | .feeds |
| 10810 | .iter |
| 10811 | .map |
| 10812 | .collect; |
| 10813 | let refreshes: = screen |
| 10814 | .refreshes |
| 10815 | .iter |
| 10816 | .map |
| 10817 | .collect; |
| 10818 | |
| 10819 | assert_eq!; |
| 10820 | assert_eq!; |
| 10821 | |
| 10822 | |
| 10823 | |
| 10824 | |
| 10825 | // The half `replace` had to learn. A feed is cleared as it answers so a |
| 10826 | // retained host does not ask twice; a cadence cleared on its first |
| 10827 | // answer would tick once and stop. |
| 10828 | let mut screen = sidebar_content.with |
| 10829 | new |
| 10830 | .fed_by |
| 10831 | .live, |
| 10832 | ; |
| 10833 | |
| 10834 | assert!; |
| 10835 | |
| 10836 | assert!; |
| 10837 | assert_eq! |
| 10838 | screen.refreshes.len, |
| 10839 | 1, |
| 10840 | "the cadence survives an answer" |
| 10841 | ; |
| 10842 | |
| 10843 | |
| 10844 | |
| 10845 | |
| 10846 | let mut screen = sidebar_content |
| 10847 | .with; |
| 10848 | |
| 10849 | assert!; |
| 10850 | |
| 10851 | assert!; |
| 10852 | assert!; |
| 10853 | |
| 10854 | |
| 10855 | |
| 10856 | |
| 10857 | // The predicate a retained host reads to decide whether to keep |
| 10858 | // drawing. It has to see through nesting, because the live thing is |
| 10859 | // usually a panel of something rather than a top-level region. |
| 10860 | let still = sidebar_content.with; |
| 10861 | assert!; |
| 10862 | |
| 10863 | let nested = sidebar_content.with |
| 10864 | new |
| 10865 | .with, |
| 10866 | ; |
| 10867 | assert!; |
| 10868 | |
| 10869 | |
| 10870 | |
| 10871 | |
| 10872 | let still = sidebar_content |
| 10873 | .with; |
| 10874 | assert!; |
| 10875 | |
| 10876 | |
| 10877 | |
| 10878 | |
| 10879 | // The walk that matters. goingson puts the elapsed time on a task row |
| 10880 | // beside its title, so a search that stopped at the region's own blocks |
| 10881 | // would answer that a screen full of running timers needs no clock. |
| 10882 | let started = UNIX_EPOCH; |
| 10883 | let screen = sidebar_content.with |
| 10884 | new.with |
| 10885 | new.part |
| 10886 | ], |
| 10887 | ; |
| 10888 | |
| 10889 | assert_eq!; |
| 10890 | |
| 10891 | |
| 10892 | |
| 10893 | |
| 10894 | // Many readouts, one tick: the renderer asks which kinds it is holding |
| 10895 | // and picks a cadence per kind, rather than a timer per readout. |
| 10896 | let at = UNIX_EPOCH; |
| 10897 | let screen = sidebar_content.with |
| 10898 | new |
| 10899 | .with |
| 10900 | .with |
| 10901 | .with |
| 10902 | new.with, |
| 10903 | , |
| 10904 | ; |
| 10905 | |
| 10906 | assert_eq!; |
| 10907 | // Finest first, which is what a renderer taking the minimum wants. |
| 10908 | assert_eq!; |
| 10909 | |
| 10910 | |
| 10911 | |
| 10912 | |
| 10913 | let at = UNIX_EPOCH; |
| 10914 | assert_eq!; |
| 10915 | assert_eq!; |
| 10916 | assert_eq!; |
| 10917 | assert_eq!; |
| 10918 | |
| 10919 | |
| 10920 | |
| 10921 | |
| 10922 | // Liveness and a route are separate halves. The audiofiles sync panel |
| 10923 | // reads state the host already holds, so its cadence is a repaint and |
| 10924 | // there is nothing here for a host to perform. |
| 10925 | let screen = sidebar_content.with |
| 10926 | new |
| 10927 | .live |
| 10928 | .with, |
| 10929 | ; |
| 10930 | |
| 10931 | assert!; |
| 10932 | assert!; |
| 10933 | |
| 10934 | |
| 10935 | |
| 10936 | |
| 10937 | // The default has to be the old behaviour, or every description written |
| 10938 | // before this field existed changes meaning when it arrives. |
| 10939 | let pane = new.extend; |
| 10940 | |
| 10941 | assert_eq!; |
| 10942 | assert_eq!; |
| 10943 | |
| 10944 | |
| 10945 | |
| 10946 | |
| 10947 | // `Showing::One` says exactly one is up, so there is no honest reading |
| 10948 | // of a missing index other than the first. A renderer never has to |
| 10949 | // decide this for itself, which is the point of the method. |
| 10950 | let mut carousel = widget.extend; |
| 10951 | carousel = carousel.showing_one; |
| 10952 | |
| 10953 | assert_eq!; |
| 10954 | |
| 10955 | |
| 10956 | |
| 10957 | |
| 10958 | // An out-of-range index is an app bug either way. Clamping reports it as |
| 10959 | // a carousel stuck on its last frame, which is findable; drawing nothing |
| 10960 | // reports it as a region that disappeared, which is not. |
| 10961 | let carousel = widget |
| 10962 | .extend |
| 10963 | .showing_one; |
| 10964 | |
| 10965 | assert_eq!; |
| 10966 | |
| 10967 | // And an empty body has no frame to clamp to. |
| 10968 | assert_eq! |
| 10969 | widget.showing_one.current, |
| 10970 | None |
| 10971 | ; |
| 10972 | |
| 10973 | |
| 10974 | |
| 10975 | |
| 10976 | let closed = widget |
| 10977 | .extend |
| 10978 | .showing_at_most_one; |
| 10979 | let open = widget |
| 10980 | .extend |
| 10981 | .showing_at_most_one; |
| 10982 | |
| 10983 | assert_eq!; |
| 10984 | assert_eq!; |
| 10985 | |
| 10986 | // Closed and `Showing::All` answer the same here on purpose: they differ |
| 10987 | // in the chrome around the body, not in what a renderer does with it. |
| 10988 | assert!; |
| 10989 | |
| 10990 | |
| 10991 | |
| 10992 | |
| 10993 | // A strip with a hole in it is worse than the prev/next row it would |
| 10994 | // have replaced, so a half-labelled body gets the row. |
| 10995 | let tabs = new |
| 10996 | .frame |
| 10997 | "Overview", |
| 10998 | Region, |
| 10999 | |
| 11000 | .frame; |
| 11001 | assert_eq!; |
| 11002 | |
| 11003 | let half = tabs |
| 11004 | .clone |
| 11005 | .with; |
| 11006 | assert!; |
| 11007 | |
| 11008 | |
| 11009 | |
| 11010 | |
| 11011 | // Which idiom a renderer draws falls out of this rather than out of the |
| 11012 | // widget's name. A frame has a caption; only a region has a tab name. |
| 11013 | let carousel = widget |
| 11014 | .extend |
| 11015 | .showing_one; |
| 11016 | |
| 11017 | assert!; |
| 11018 | assert_eq!; |
| 11019 | |
| 11020 | |
| 11021 | |
| 11022 | |
| 11023 | // The whole of what makes `Ranked` additive. Every screen in the tree |
| 11024 | // was written before it existed and every one of them still says the |
| 11025 | // same thing. |
| 11026 | let pane = new |
| 11027 | .with |
| 11028 | .extend; |
| 11029 | |
| 11030 | for placed in pane.body.iter |
| 11031 | assert_eq!; |
| 11032 | for cutoff in CUTOFFS |
| 11033 | assert!; |
| 11034 | |
| 11035 | |
| 11036 | |
| 11037 | |
| 11038 | |
| 11039 | |
| 11040 | // goingson's bug, said in the description instead of in a stylesheet. |
| 11041 | // The toolbar is not in the pane and is not out of flow; it is a member |
| 11042 | // of the tab strip's row, and it says what it is worth when that row |
| 11043 | // runs out of space. |
| 11044 | let view = new.across |
| 11045 | new.beside |
| 11046 | Region, |
| 11047 | Secondary, |
| 11048 | , |
| 11049 | ; |
| 11050 | |
| 11051 | let run = view.run.as_ref.expect; |
| 11052 | assert_eq!; |
| 11053 | assert_eq!; |
| 11054 | // The strip itself is not a member. It is what the tab group already |
| 11055 | // puts in the row, generated from its children's labels, and a member |
| 11056 | // standing for it would be a second source for the same fact. |
| 11057 | assert!; |
| 11058 | |
| 11059 | |
| 11060 | |
| 11061 | |
| 11062 | // The property that makes the toolbar's move out of the pane free. A |
| 11063 | // region that stopped being findable would stop updating, which is a |
| 11064 | // worse bug than the overlap it was moved to fix. |
| 11065 | let view = new.across |
| 11066 | new.beside |
| 11067 | Region |
| 11068 | new.fed_by, |
| 11069 | , |
| 11070 | Secondary, |
| 11071 | , |
| 11072 | ; |
| 11073 | |
| 11074 | assert!; |
| 11075 | let mut screen = sidebar_content.with; |
| 11076 | assert_eq!; |
| 11077 | assert!; |
| 11078 | |
| 11079 | |
| 11080 | |
| 11081 | |
| 11082 | let members = |
| 11083 | new |
| 11084 | .across |
| 11085 | new |
| 11086 | .beside |
| 11087 | .beside, |
| 11088 | |
| 11089 | .run |
| 11090 | .expect |
| 11091 | ; |
| 11092 | |
| 11093 | // Wrap and Stack rearrange, so every member survives however tight the |
| 11094 | // row is and the rank goes unread. |
| 11095 | for keeping in |
| 11096 | let run = members; |
| 11097 | assert!; |
| 11098 | assert_eq!; |
| 11099 | |
| 11100 | |
| 11101 | // Shed and Menu take members out of the row, by rank and never by |
| 11102 | // position, which is the whole reason the rank is on the member. |
| 11103 | for shedding in |
| 11104 | let run = members; |
| 11105 | assert!; |
| 11106 | assert_eq!; |
| 11107 | assert_eq!; |
| 11108 | assert!; |
| 11109 | |
| 11110 | |
| 11111 | |
| 11112 | |
| 11113 | |
| 11114 | // Rule 2, held by the type instead of by a check. This was a |
| 11115 | // `#[should_panic]` test while `beside` was a method on the region: it |
| 11116 | // had to look for a row at runtime and refuse when there was none, |
| 11117 | // which made a correct call and an incorrect one identical to a |
| 11118 | // compiler. There is nothing left to panic on. `beside` is on `Run`, |
| 11119 | // `Run::new` takes the fallback, and a region that never declared a |
| 11120 | // row offers no method that would put a member in one. |
| 11121 | // |
| 11122 | // What a running program can still observe is the half below: silence |
| 11123 | // stays silence, and both ways of declaring a row arrive with the |
| 11124 | // answer the panic used to stand in for. |
| 11125 | let quiet = new; |
| 11126 | assert!; |
| 11127 | |
| 11128 | let empty = new.across; |
| 11129 | let run = empty.run.expect; |
| 11130 | assert_eq!; |
| 11131 | assert!; |
| 11132 | |
| 11133 | let filled = new.across |
| 11134 | new |
| 11135 | .beside, |
| 11136 | ; |
| 11137 | let run = filled.run.expect; |
| 11138 | assert_eq!; |
| 11139 | assert_eq!; |
| 11140 | |
| 11141 | |
| 11142 | |
| 11143 | |
| 11144 | // The row is one value, so handing the region another one replaces it |
| 11145 | // whole. Correcting a fallback means correcting it on the `Run`, where |
| 11146 | // the members it belongs to are, rather than reaching past them. |
| 11147 | let head = new |
| 11148 | .across |
| 11149 | new |
| 11150 | .beside, |
| 11151 | |
| 11152 | .across; |
| 11153 | |
| 11154 | let run = head.run.expect; |
| 11155 | assert_eq!; |
| 11156 | assert!; |
| 11157 | |
| 11158 | |
| 11159 | |
| 11160 | |
| 11161 | // `makeover-tui`'s table states this for columns and tests it; this is |
| 11162 | // the same property for a region's members, and it is the property the |
| 11163 | // whole rank exists to buy. Positional narrowing -- goingson's |
| 11164 | // `nth-child(n+5)` -- fails it, which is the bug `Priority` replaced. |
| 11165 | let before = new |
| 11166 | .with |
| 11167 | .with_ranked; |
| 11168 | |
| 11169 | let after = new |
| 11170 | .with |
| 11171 | .with |
| 11172 | .with_ranked; |
| 11173 | |
| 11174 | let dropped = |
| 11175 | slot.body |
| 11176 | .iter |
| 11177 | .filter |
| 11178 | .map |
| 11179 | .collect |
| 11180 | ; |
| 11181 | |
| 11182 | assert_eq!; |
| 11183 | assert_eq!; |
| 11184 | |
| 11185 | |
| 11186 | |
| 11187 | |
| 11188 | // A renderer walks these in order and stops at the first that fits, so |
| 11189 | // the order is the whole meaning of the constant. A tier added |
| 11190 | // upstream and appended here rather than placed would narrow to it |
| 11191 | // last, whatever it said. |
| 11192 | assert_eq! |
| 11193 | CUTOFFS, |
| 11194 | |
| 11195 | Optional, |
| 11196 | Secondary, |
| 11197 | Essential, |
| 11198 | ] |
| 11199 | ; |
| 11200 | assert!; |
| 11201 | |
| 11202 | |
| 11203 | |
| 11204 | |
| 11205 | // `dc2f2b46`. `replace`'s opposite number, and the pair is the whole |
| 11206 | // cycle: the work is handed off, the region goes pending, and the live |
| 11207 | // call that was already declared is what eventually puts content back. |
| 11208 | let mut screen = list_detail.with |
| 11209 | new |
| 11210 | .with |
| 11211 | .fed_by |
| 11212 | .live, |
| 11213 | ; |
| 11214 | // A live region is asked again on a cadence rather than once, so it is |
| 11215 | // `refreshes` and not `feeds` that names its call. |
| 11216 | assert!; |
| 11217 | assert_eq!; |
| 11218 | |
| 11219 | assert!; |
| 11220 | assert_eq!; |
| 11221 | assert_eq! |
| 11222 | screen.slots.body.iter.next.expect.node, |
| 11223 | pending, |
| 11224 | "the sentence stands where the content was" |
| 11225 | ; |
| 11226 | // The call is untouched. Clearing it here would mark the region as |
| 11227 | // waiting and remove the thing it waits on in one breath. |
| 11228 | assert_eq!; |
| 11229 | |
| 11230 | // And the finish is an ordinary fragment, which takes it back out. |
| 11231 | assert!; |
| 11232 | assert_eq!; |
| 11233 | assert_eq! |
| 11234 | screen.refreshes.len, |
| 11235 | 1, |
| 11236 | "a live region keeps its call after an answer lands" |
| 11237 | ; |
| 11238 | |
| 11239 | // A region that is not there is the description bug a fragment naming |
| 11240 | // one is, and answers the same way rather than panicking. |
| 11241 | assert!; |
| 11242 | |
| 11243 | |
| 11244 | |
| 11245 | |
| 11246 | // `d8d6f380`. The two facts move together: a region that says where its |
| 11247 | // content is coming from does not have it, and a region that has been |
| 11248 | // filled is no longer asking. |
| 11249 | let mut screen = list_detail.with |
| 11250 | new |
| 11251 | .fed_by, |
| 11252 | ; |
| 11253 | assert_eq!; |
| 11254 | assert_eq! |
| 11255 | screen.slots.readiness, |
| 11256 | Pending, |
| 11257 | "a fed region has not arrived" |
| 11258 | ; |
| 11259 | |
| 11260 | assert!; |
| 11261 | assert_eq!; |
| 11262 | assert! |
| 11263 | screen.feeds.is_empty, |
| 11264 | "a filled region asks for itself again" |
| 11265 | ; |
| 11266 | |
| 11267 | |
| 11268 | |
| 11269 | |
| 11270 | // f7261a5a. What it takes and how many are the description's; where the |
| 11271 | // bytes go and how far along it is are the action's, and both already |
| 11272 | // existed. The test is that reading the field as the layer's own type |
| 11273 | // carries the first two across the owned/borrowed seam. |
| 11274 | let field = upload |
| 11275 | "media", |
| 11276 | "Media", |
| 11277 | |
| 11278 | family, |
| 11279 | media_type, |
| 11280 | suffix, |
| 11281 | ], |
| 11282 | |
| 11283 | .many |
| 11284 | .writes; |
| 11285 | |
| 11286 | assert!; |
| 11287 | // A suffix names no family and a csv is not media, so what earns the |
| 11288 | // preview here is the one entry that says so. |
| 11289 | assert!; |
| 11290 | assert!; |
| 11291 | // The destination and the size ride on the action, unchanged by any of |
| 11292 | // this. |
| 11293 | let action = field.writes.clone.expect; |
| 11294 | assert_eq! |
| 11295 | action.awaiting.and_then, |
| 11296 | Some |
| 11297 | ; |
| 11298 | |
| 11299 | field.with_layout |
| 11300 | assert_eq!; |
| 11301 | assert!; |
| 11302 | assert_eq! |
| 11303 | borrowed.accept, |
| 11304 | |
| 11305 | Family, |
| 11306 | Type, |
| 11307 | Suffix, |
| 11308 | ] |
| 11309 | ; |
| 11310 | assert!; |
| 11311 | ; |
| 11312 | |
| 11313 | |
| 11314 | |
| 11315 | |
| 11316 | let text = new; |
| 11317 | assert!; |
| 11318 | assert!; |
| 11319 | assert!; |
| 11320 | // An upload with an empty list is a field that takes any file, which is |
| 11321 | // a different sentence from a field that takes none. |
| 11322 | let any = upload; |
| 11323 | assert!; |
| 11324 | assert!; |
| 11325 | |
| 11326 | |
| 11327 | |
| 11328 | |
| 11329 | // The ruling on `5fa96a82`: an amount is a fact about the payload, and |
| 11330 | // a call with nothing countable about it says nothing rather than |
| 11331 | // guessing. |
| 11332 | let stripe = post.awaiting; |
| 11333 | let upload = post.awaiting_amount; |
| 11334 | assert!; |
| 11335 | assert_eq!; |
| 11336 | assert_eq! |
| 11337 | upload.awaiting.and_then, |
| 11338 | Some |
| 11339 | ; |
| 11340 | assert!; |
| 11341 | |
| 11342 | |
| 11343 | |
| 11344 | |
| 11345 | // The walk descends the way `find_mut` does, or a region inside a pane |
| 11346 | // never asks for itself and shows its stand-in forever. |
| 11347 | let inner = new.fed_by; |
| 11348 | let screen = list_detail |
| 11349 | .with; |
| 11350 | let feeds = screen.feeds; |
| 11351 | assert_eq!; |
| 11352 | assert_eq!; |
| 11353 | |
| 11354 | |
| 11355 | |
| 11356 | |
| 11357 | // `dfbc88ce`. A labelled child's `fed_by` is the tab's address, so a |
| 11358 | // host asking for every one of them fetches five frames for a reader |
| 11359 | // looking at one. Measured on MNW's library page, which is the screen |
| 11360 | // that found this. |
| 11361 | let screen = list_detail.with |
| 11362 | new |
| 11363 | .frame |
| 11364 | "Purchases", |
| 11365 | Region |
| 11366 | new |
| 11367 | .fed_by, |
| 11368 | , |
| 11369 | |
| 11370 | .frame |
| 11371 | "Feed", |
| 11372 | Region |
| 11373 | new |
| 11374 | .fed_by, |
| 11375 | , |
| 11376 | |
| 11377 | .showing_one, |
| 11378 | ; |
| 11379 | |
| 11380 | assert!; |
| 11381 | |
| 11382 | |
| 11383 | |
| 11384 | |
| 11385 | // The unlabelled half of the same region. Nothing here changed; the |
| 11386 | // assertion exists so the tab rule cannot be widened into one that |
| 11387 | // silences an ordinary nested feed. |
| 11388 | let screen = list_detail.with |
| 11389 | new |
| 11390 | .with |
| 11391 | .with |
| 11392 | new.fed_by, |
| 11393 | |
| 11394 | .showing_one, |
| 11395 | ; |
| 11396 | |
| 11397 | let feeds = screen.feeds; |
| 11398 | assert_eq!; |
| 11399 | assert_eq!; |
| 11400 | |
| 11401 | |
| 11402 | |
| 11403 | |
| 11404 | // The walk descends past the panel, because a panel that has arrived can |
| 11405 | // hold something genuinely slow and that thing is nobody's tab. |
| 11406 | let panel = new |
| 11407 | .fed_by |
| 11408 | .with |
| 11409 | new.fed_by, |
| 11410 | ; |
| 11411 | let screen = list_detail.with |
| 11412 | new |
| 11413 | .frame |
| 11414 | .frame |
| 11415 | .showing_one, |
| 11416 | ; |
| 11417 | |
| 11418 | let feeds = screen.feeds; |
| 11419 | assert_eq!; |
| 11420 | assert_eq!; |
| 11421 | |
| 11422 | |
| 11423 | |
| 11424 | |
| 11425 | // `asked_for` is what a retained-screen host reads when a tab is |
| 11426 | // pressed. A panel already read is not asked for again, which is what |
| 11427 | // going back to a tab means. |
| 11428 | let empty = new.fed_by; |
| 11429 | assert_eq! |
| 11430 | empty.asked_for.and_then, |
| 11431 | Some |
| 11432 | ; |
| 11433 | |
| 11434 | let read = empty.clone.with; |
| 11435 | assert!; |
| 11436 | |
| 11437 | |
| 11438 | |
| 11439 | |
| 11440 | // The three answers a renderer asks for, and the reason the third one |
| 11441 | // is empty rather than absent: `as_str` is for rendering, and there is |
| 11442 | // nothing to render. A renderer reading it for a local action has |
| 11443 | // skipped a branch, which is what the empty string makes visible |
| 11444 | // instead of an address that half-works. |
| 11445 | let action = local; |
| 11446 | assert_eq!; |
| 11447 | assert_eq!; |
| 11448 | assert!; |
| 11449 | // Not external. The two are the whole of "not a route this app answers" |
| 11450 | // and they are opposite kinds of not: one leaves and is still a |
| 11451 | // request, the other asks nothing at all. |
| 11452 | assert!; |
| 11453 | assert!; |
| 11454 | assert!; |
| 11455 | |
| 11456 | |
| 11457 | |
| 11458 | |
| 11459 | // Which suggestion was picked is a value, not an address, so it rides |
| 11460 | // where every other value does. Losing it here would leave the mark |
| 11461 | // saying something happens and nothing saying to what. |
| 11462 | let action = local.with; |
| 11463 | assert_eq!; |
| 11464 | assert_eq!; |
| 11465 | |
| 11466 | |
| 11467 | |
| 11468 | |
| 11469 | use crateRenderer; |
| 11470 | |
| 11471 | assert!; |
| 11472 | assert!; |
| 11473 | |
| 11474 | |
| 11475 | /// The four shapes, and the one convention they share: a control holding |
| 11476 | /// an empty string is holding nothing, which is what an unticked box is on |
| 11477 | /// every host. |
| 11478 | |
| 11479 | |
| 11480 | let ticked = ticked; |
| 11481 | assert!; |
| 11482 | assert!; |
| 11483 | assert!; |
| 11484 | |
| 11485 | let unticked = unticked; |
| 11486 | assert!; |
| 11487 | assert!; |
| 11488 | assert!; |
| 11489 | |
| 11490 | let custom = holding; |
| 11491 | assert!; |
| 11492 | assert!; |
| 11493 | assert!; |
| 11494 | |
| 11495 | let zoned = holding_one_of; |
| 11496 | assert!; |
| 11497 | assert!; |
| 11498 | assert!; |
| 11499 | |
| 11500 | |
| 11501 | /// A region that says nothing about what reveals it is always out, which is |
| 11502 | /// what every region did before the member existed. |
| 11503 | |
| 11504 | |
| 11505 | let plain = group; |
| 11506 | assert!; |
| 11507 | assert!; |
| 11508 | assert_eq!; |
| 11509 | |
| 11510 | let conditional = group.revealed_by; |
| 11511 | assert_eq!; |
| 11512 | assert!; |
| 11513 | |
| 11514 | |
| 11515 | /// The condition is on the region and names the control, so a renderer |
| 11516 | /// asking "does this section apply" reads one member. Nothing is added to |
| 11517 | /// the control, which is the half the ruling rejected. |
| 11518 | |
| 11519 | |
| 11520 | let control = new; |
| 11521 | let form = group |
| 11522 | .with |
| 11523 | .with |
| 11524 | group |
| 11525 | .revealed_by |
| 11526 | .with, |
| 11527 | ; |
| 11528 | |
| 11529 | let section = form.find.expect; |
| 11530 | assert_eq!; |
| 11531 | // The field is what it was: nothing points back at the region it |
| 11532 | // reveals, which is the back-pointer the ruling turned down. |
| 11533 | assert_eq! |
| 11534 | control, |
| 11535 | new |
| 11536 | ; |
| 11537 | |
| 11538 | |
| 11539 | /// The described half of the condition: what the box was handed to the |
| 11540 | /// reader holding, wherever on the screen it sits. |
| 11541 | |
| 11542 | |
| 11543 | let screen = new |
| 11544 | .with |
| 11545 | action: post, |
| 11546 | submit: "Save".into, |
| 11547 | fields: vec! |
| 11548 | select |
| 11549 | .value, |
| 11550 | ], |
| 11551 | |
| 11552 | .with |
| 11553 | group.with |
| 11554 | group.with |
| 11555 | new |
| 11556 | .value, |
| 11557 | , |
| 11558 | , |
| 11559 | ; |
| 11560 | |
| 11561 | assert_eq!; |
| 11562 | // Nested, because a form inside a region is where most fields are. |
| 11563 | assert_eq!; |
| 11564 | // A control the screen does not carry holds nothing, which is not the |
| 11565 | // same as holding an empty value and is why this is an `Option`. |
| 11566 | assert_eq!; |
| 11567 | |
| 11568 | |
| 11569 | /// A field in a row of a list is still a control a region can watch. The |
| 11570 | /// walk is exhaustive over the containers rather than over the two nodes |
| 11571 | /// that hold a field directly. |
| 11572 | |
| 11573 | |
| 11574 | let row = new.part |
| 11575 | Meta, |
| 11576 | Field |
| 11577 | new.value, |
| 11578 | , |
| 11579 | ; |
| 11580 | let screen = new.with |
| 11581 | group.with |
| 11582 | columns: Vecnew, |
| 11583 | rows: vec!, |
| 11584 | more: None, |
| 11585 | , |
| 11586 | ; |
| 11587 | |
| 11588 | assert_eq!; |
| 11589 | |
| 11590 | |
| 11591 | /// The eight sites the member was measured against, each said as one |
| 11592 | /// region with one condition. Six MNW toggles and two goingson ones, and |
| 11593 | /// every one of them is a `window.<name>` function today. |
| 11594 | |
| 11595 | |
| 11596 | // MNW, static/actions-tabs.js and actions-partials.js. |
| 11597 | let pwyw = group.revealed_by; |
| 11598 | let license = group |
| 11599 | .revealed_by; |
| 11600 | let keys = group.revealed_by; |
| 11601 | let trial = group.revealed_by; |
| 11602 | let promo = |
| 11603 | group.revealed_by; |
| 11604 | let offset = group.revealed_by |
| 11605 | "placement_position", |
| 11606 | , |
| 11607 | ; |
| 11608 | |
| 11609 | assert!; |
| 11610 | assert!; |
| 11611 | assert!; |
| 11612 | assert!; |
| 11613 | assert!; |
| 11614 | assert!; |
| 11615 | |
| 11616 | // goingson: the zone picker, out on three of the four kinds, and the |
| 11617 | // recurrence detail, out while the rule is anything at all. |
| 11618 | let zone = group.revealed_by |
| 11619 | "tz_kind", |
| 11620 | , |
| 11621 | ; |
| 11622 | // `edit_fields` offers four patterns and grows a second form under any |
| 11623 | // of them, which is the same shape as the zone picker: the detail is |
| 11624 | // out on the patterns and away on "none". |
| 11625 | let recurrence = group.revealed_by |
| 11626 | "recurrence", |
| 11627 | , |
| 11628 | ; |
| 11629 | |
| 11630 | assert!; |
| 11631 | assert!; |
| 11632 | assert!; |
| 11633 | assert!; |
| 11634 | assert!; |
| 11635 | |
| 11636 | |
| 11637 | /// Found by the consumer `079a011e` was ruled for: a form's questions are |
| 11638 | /// a flat list, so a single conditional question inside one has nowhere to |
| 11639 | /// put the fact a region would carry. |
| 11640 | |
| 11641 | |
| 11642 | // goingson's `initTzKindConfig`: the box is out on one of the kinds. |
| 11643 | let zone = new |
| 11644 | .revealed_by; |
| 11645 | |
| 11646 | assert_eq!; |
| 11647 | assert!; |
| 11648 | assert!; |
| 11649 | assert!; |
| 11650 | |
| 11651 | // The ordinary question says nothing and is always asked, which is |
| 11652 | // every other field in the tree. |
| 11653 | let title = new; |
| 11654 | assert_eq!; |
| 11655 | assert!; |
| 11656 | |
| 11657 | // A slot of a repeating question does not carry the condition: the |
| 11658 | // question does, and a renderer that has reached the slots has already |
| 11659 | // answered it once for the whole group. |
| 11660 | let repeating = new |
| 11661 | .repeating |
| 11662 | .revealed_by; |
| 11663 | assert_eq!; |
| 11664 | assert_eq!; |
| 11665 | |
| 11666 | |
| 11667 | /// The wire naming, picked once here so the three renderers and whatever |
| 11668 | /// reads the submission back cannot disagree. |
| 11669 | |
| 11670 | |
| 11671 | assert_eq!; |
| 11672 | assert_eq!; |
| 11673 | |
| 11674 | assert_eq! |
| 11675 | instance_of, |
| 11676 | Some |
| 11677 | ; |
| 11678 | // An ordinary field name is not a slot, which is what makes it safe to |
| 11679 | // ask this of any name a host is holding. |
| 11680 | assert_eq!; |
| 11681 | // Nor is anything this did not write. |
| 11682 | assert_eq!; |
| 11683 | assert_eq!; |
| 11684 | assert_eq!; |
| 11685 | assert_eq!; |
| 11686 | assert_eq!; |
| 11687 | |
| 11688 | |
| 11689 | /// One slot is an ordinary field, so every renderer draws it with what it |
| 11690 | /// already does to a field. The value and the error are the slot's; the |
| 11691 | /// question's own error is not any slot's. |
| 11692 | |
| 11693 | |
| 11694 | let mut field = new |
| 11695 | .repeating; |
| 11696 | field.error = Some; |
| 11697 | field.min = Some; |
| 11698 | |
| 11699 | let second = field.instance; |
| 11700 | assert_eq!; |
| 11701 | assert_eq!; |
| 11702 | assert_eq!; |
| 11703 | assert_eq!; |
| 11704 | // Carried through: the question is what repeats, so everything it said |
| 11705 | // about itself holds for every slot. |
| 11706 | assert_eq!; |
| 11707 | assert_eq!; |
| 11708 | // And a slot cannot recurse into slots of its own. |
| 11709 | assert!; |
| 11710 | |
| 11711 | assert_eq!; |
| 11712 | // A slot past the end is an empty box under the right name, which is |
| 11713 | // exactly what a slot the reader has just added is. |
| 11714 | let added = field.instance; |
| 11715 | assert_eq!; |
| 11716 | assert_eq!; |
| 11717 | assert_eq!; |
| 11718 | |
| 11719 | |
| 11720 | /// The floor, the ceiling, and how many slots stand before anyone touches |
| 11721 | /// anything. Every renderer asks these rather than doing the arithmetic, so |
| 11722 | /// the three of them offer the same controls. |
| 11723 | |
| 11724 | |
| 11725 | let capped = answered.most; |
| 11726 | assert_eq!; |
| 11727 | assert!; |
| 11728 | assert!; |
| 11729 | assert!; |
| 11730 | |
| 11731 | let floored = answered.least; |
| 11732 | assert!; |
| 11733 | assert!; |
| 11734 | assert!; |
| 11735 | |
| 11736 | // Zero answers is a real state: the add control alone. |
| 11737 | let none = new; |
| 11738 | assert_eq!; |
| 11739 | assert!; |
| 11740 | assert!; |
| 11741 | |
| 11742 | // A question that must be answered twice opens with two boxes rather |
| 11743 | // than with none and a refusal on submit. |
| 11744 | assert_eq!; |
| 11745 | |
| 11746 | |
| 11747 | /// A refusal naming a slot the form did not offer is worth seeing on the |
| 11748 | /// screen rather than being dropped. |
| 11749 | |
| 11750 | |
| 11751 | let repeat = answered.wrong; |
| 11752 | assert_eq!; |
| 11753 | assert_eq!; |
| 11754 | assert_eq!; |
| 11755 | assert_eq!; |
| 11756 | |
| 11757 | |
| 11758 | /// The consumer, said in the vocabulary: goingson `8fdb814c`'s event form, |
| 11759 | /// against `Event.reminder_offsets_seconds` and the cap |
| 11760 | /// `sanitize_reminder_offsets` enforces. |
| 11761 | |
| 11762 | |
| 11763 | let held: = vec!; |
| 11764 | let field = new.repeating |
| 11765 | answered |
| 11766 | .most |
| 11767 | .adding, |
| 11768 | ; |
| 11769 | |
| 11770 | assert_eq!; |
| 11771 | let names: = |
| 11772 | .map |
| 11773 | .collect; |
| 11774 | assert_eq!; |
| 11775 | // Which is one submit carrying three values under one question, not |
| 11776 | // three submits and not one control taking a set. |
| 11777 | assert!; |
| 11778 | |
| 11779 | |
| 11780 | /// An ordinary field answers the repeating questions the way it always did, |
| 11781 | /// so nothing that loops over slots has to branch first. |
| 11782 | |
| 11783 | |
| 11784 | let field = new; |
| 11785 | assert!; |
| 11786 | assert_eq!; |
| 11787 | |
| 11788 | |
| 11789 | |
| 11790 | |
| 11791 | assert_eq!; |
| 11792 | assert_eq!; |
| 11793 | |
| 11794 | |
| 11795 | |
| 11796 | |
| 11797 | assert_eq!; |
| 11798 | // The two readers never both answer, which is what makes it safe to |
| 11799 | // ask either of any name a host is holding. |
| 11800 | assert_eq!; |
| 11801 | assert_eq!; |
| 11802 | assert_eq!; |
| 11803 | // Refused rather than parsed loosely, for `instance_of`'s reason. |
| 11804 | assert_eq!; |
| 11805 | assert_eq!; |
| 11806 | assert_eq!; |
| 11807 | |
| 11808 | |
| 11809 | |
| 11810 | |
| 11811 | let ordinary = new |
| 11812 | .repeating; |
| 11813 | assert_eq!; |
| 11814 | assert_eq!; |
| 11815 | |
| 11816 | let grouped = new.repeating |
| 11817 | new |
| 11818 | .of |
| 11819 | .instances_of |
| 11820 | new, |
| 11821 | new, |
| 11822 | ]], |
| 11823 | ; |
| 11824 | let slots = grouped.instance_fields; |
| 11825 | assert_eq!; |
| 11826 | assert_eq!; |
| 11827 | assert_eq!; |
| 11828 | assert_eq!; |
| 11829 | assert_eq!; |
| 11830 | assert_eq!; |
| 11831 | |
| 11832 | |
| 11833 | |
| 11834 | |
| 11835 | // What the blank a renderer offers is built from: the questions live on |
| 11836 | // the repeat, so a slot past the end still knows what it is asking. |
| 11837 | let grouped = new.repeating |
| 11838 | new.of, |
| 11839 | ; |
| 11840 | let slots = grouped.instance_fields; |
| 11841 | assert_eq!; |
| 11842 | assert_eq!; |
| 11843 | assert_eq!; |
| 11844 | assert_eq!; |
| 11845 | assert_eq!; |
| 11846 | |
| 11847 | |
| 11848 | |
| 11849 | |
| 11850 | let slot = grouped |
| 11851 | .getting; |
| 11852 | assert_eq!; |
| 11853 | // The slot's own error stays free for what is wrong with the slot |
| 11854 | // rather than with one of its questions. |
| 11855 | assert_eq!; |
| 11856 | assert!; |
| 11857 | assert!; |
| 11858 | |
| 11859 | |
| 11860 | |
| 11861 | |
| 11862 | assert_eq!; |
| 11863 | assert_eq!; |
| 11864 | assert!; |
| 11865 | assert!; |
| 11866 | assert!; |
| 11867 | assert!; |
| 11868 | |
| 11869 | |
| 11870 | |
| 11871 | |
| 11872 | let ordinary = new; |
| 11873 | assert_eq!; |
| 11874 | assert_eq!; |
| 11875 | |
| 11876 | let named = new.adding; |
| 11877 | assert_eq!; |
| 11878 | |
| 11879 | // The queue: the picker above the table makes the slots, so there is |
| 11880 | // no blank a reader could fill and every renderer draws no control. |
| 11881 | let queue = new.added_by; |
| 11882 | assert_eq!; |
| 11883 | assert_eq!; |
| 11884 | |
| 11885 | |
| 11886 | |
| 11887 | |
| 11888 | let field = new.repeating |
| 11889 | new.instances_of |
| 11890 | new.called, |
| 11891 | new, |
| 11892 | ], |
| 11893 | ; |
| 11894 | |
| 11895 | // What is in it, for a queue whose slots differ by their file. |
| 11896 | assert_eq!; |
| 11897 | // And the ordinal still, for a slot that differs only by position. |
| 11898 | assert_eq!; |
| 11899 | // A slot past the end is a blank the reader just made, and it has no |
| 11900 | // name of its own yet. |
| 11901 | assert_eq!; |
| 11902 | |
| 11903 | |
| 11904 | |
| 11905 | |
| 11906 | // `5e21dcfc`. The conversion is what keeps the mirror from drifting, so |
| 11907 | // the member is asserted through it rather than on the struct: a field |
| 11908 | // added over there and forgotten here compiles until something reads |
| 11909 | // it, and this is the something. |
| 11910 | let tier = new |
| 11911 | .detailing |
| 11912 | .unless; |
| 11913 | |
| 11914 | let borrowed = tier.as_layout; |
| 11915 | assert_eq!; |
| 11916 | assert_eq!; |
| 11917 | assert_eq! |
| 11918 | borrowed.detail, |
| 11919 | Some |
| 11920 | ; |
| 11921 | assert_eq! |
| 11922 | borrowed.unavailable, |
| 11923 | Some |
| 11924 | ; |
| 11925 | assert!; |
| 11926 | |
| 11927 | // An ordinary option says neither, which is nearly all of them. |
| 11928 | let free = plain; |
| 11929 | let plain = free.as_layout; |
| 11930 | assert_eq!; |
| 11931 | assert!; |
| 11932 | |
| 11933 | |
| 11934 | |
| 11935 | |
| 11936 | // `cb62a9dc`. The one walk all three renderers read, so "the dials |
| 11937 | // inside this panel" cannot mean three things. |
| 11938 | let calculator = group |
| 11939 | .across |
| 11940 | field, |
| 11941 | Essential, |
| 11942 | |
| 11943 | .with |
| 11944 | Number, |
| 11945 | "item_price", |
| 11946 | "Price", |
| 11947 | |
| 11948 | .with |
| 11949 | action: post, |
| 11950 | submit: "Save".into, |
| 11951 | fields: vec!, |
| 11952 | |
| 11953 | .with |
| 11954 | new, |
| 11955 | |
| 11956 | .with; |
| 11957 | |
| 11958 | let names: = calculator |
| 11959 | .questions |
| 11960 | .iter |
| 11961 | .map |
| 11962 | .collect; |
| 11963 | // The run first, because that is draw order, then the body outside in. |
| 11964 | assert_eq!; |
| 11965 | |
| 11966 | |
| 11967 | |
| 11968 | |
| 11969 | let screen = sidebar_content |
| 11970 | .with |
| 11971 | .with |
| 11972 | group |
| 11973 | .with |
| 11974 | get.replacing, |
| 11975 | |
| 11976 | .consulting |
| 11977 | get.replacing, |
| 11978 | , |
| 11979 | ; |
| 11980 | |
| 11981 | let asking: = screen |
| 11982 | .consulting |
| 11983 | .iter |
| 11984 | .map |
| 11985 | .collect; |
| 11986 | // Outside in, and a region that asks nothing is not here at all. |
| 11987 | assert_eq!; |
| 11988 | |
| 11989 | |
| 11990 |