Skip to main content

max / makeover-layout

0.29.1: an action can say it is waiting on something that resolves Task d8d6f380. Awaiting, and Awaiting::amount: a total to get through when it is a measured fact about the payload -- an upload's file length -- and None when the wait has no countable size, which a Stripe round trip does not. A duration is never described, and a renderer must not manufacture one either. Max 2026-08-18: "the best progress bars work by telling you the fraction of work completed and the time it took so far rather than trying to predict." So a determinate bar shows completed-over-total plus elapsed, never a remaining time or an extrapolated rate, and an unmeasured wait is indeterminate. Committed by a later session. The session that wrote this checkpointed without committing and did not come back, and quasi 0.28.0 already ships the router and renderer halves against it, so leaving it here meant a tree that does not build from a clone. Additive, so the 0.29 pins across the tree resolve it unchanged.
Author: Max Johnson <me@maxj.phd> · 2026-08-18 19:48 UTC
Signed with PGP, not checked
Commit: 3b0753f30f8fb2c6866d790a297b8e1e147bd72e
Parent: cb58664
2 files changed, +138 insertions, -1 deletion
M Cargo.toml +1 -1
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makeover-layout"
3 - version = "0.29.0"
3 + version = "0.29.1"
4 4 edition = "2024"
5 5 # One copy of this vocabulary per dependency graph, enforced by cargo rather
6 6 # than by remembering. Two versions of a description layer in one build means
M src/lib.rs +137
@@ -323,6 +323,33 @@
323 323 //! empty box and the instruction lived on a disabled button elsewhere. The
324 324 //! renderers moved, not the description.
325 325 //!
326 + //! 0.28.3 adds [`Awaiting`], which is the sentence [`Readiness`] could say about
327 + //! a region and could not say about a control. A described screen could state
328 + //! that a list was on its way and could not state that the button just pressed
329 + //! is doing the thing it was pressed for, so every renderer's in-flight
330 + //! treatment was the app's to hand-write. The MNW server hand-writes it 57 times
331 + //! and hand-writes the guard against a second press twice, which is the half
332 + //! that matters going missing on a codebase that sells things.
333 + //!
334 + //! The mark is the fact that something outstanding will complete, once, in
335 + //! expected finite time. Deliberately not remoteness, since a heavy local query
336 + //! waits too, and deliberately not slowness, which is a judgement rather than a
337 + //! property. It carries an optional amount, stated only when the amount is
338 + //! measured, and it carries no duration at all: a renderer draws what is done
339 + //! over what there is plus the time so far, and never an estimate of what is
340 + //! left.
341 + //!
342 + //! One mark and two readings, which is what keeps a slow region from being
343 + //! hand-split into its own route the way MNW's payout summary is: a pressed
344 + //! control goes busy and locks, a region fed by an awaiting call stands in as
345 + //! [`Readiness::Pending`] and fills when it lands.
346 + //!
347 + //! A patch release for a new member, which is the 0.27.5 precedent rather than a
348 + //! new rule: nothing existing changed shape, so every consumer already asking
349 + //! for 0.28 keeps resolving and the suite below this crate does not have to move
350 + //! for a type only quasi reads. The minor releases above were minor because they
351 + //! also narrowed something.
352 + //!
326 353 //! # Reach, focus and the focus ring
327 354 //!
328 355 //! Three terms, and no others, for what 0.19.0 moved out of the description.
@@ -1330,6 +1357,106 @@
1330 1357 }
1331 1358 }
1332 1359
1360 + /// An action is waiting on something that resolves once, in expected finite
1361 + /// time.
1362 + ///
1363 + /// The control-side sibling of [`Readiness`]. That enum names four states for a
1364 + /// region and named nothing at all for the button that is currently doing what
1365 + /// it was clicked for, so the in-flight treatment is hand-written wherever it
1366 + /// exists: the MNW server carries 57 in-flight indicators against 2 guards
1367 + /// against a second press, which is the spinner mostly present and the guard
1368 + /// mostly absent, on a codebase whose money path is a purchase button.
1369 + ///
1370 + /// # What is described here, and what is not
1371 + ///
1372 + /// The fact is that there is an outstanding thing which will complete. Not that
1373 + /// the address is remote: a heavy local query waits too, and a server calling a
1374 + /// payment provider is not the browser leaving the app. Not that the call is
1375 + /// slow either, which is a judgement about a call rather than a property of one.
1376 + ///
1377 + /// Resolving **once** is the boundary, and it is what separates this from a
1378 + /// screen that keeps changing. A live screen never resolves and has no name in
1379 + /// this crate yet.
1380 + ///
1381 + /// # One mark, two renderings
1382 + ///
1383 + /// | what reads it | what it does |
1384 + /// |---|---|
1385 + /// | a control that was pressed | goes busy and refuses a second press until it resolves |
1386 + /// | a region fed by it | stands in as [`Readiness::Pending`], then fills |
1387 + ///
1388 + /// The two were on the table separately and both were taken. Controls alone
1389 + /// leaves a slow region hand-split into its own route, which is what MNW's user
1390 + /// dashboard does with its payout summary; regions alone leaves the purchase
1391 + /// button unguarded.
1392 + ///
1393 + /// # A quantity when it is measured, never a duration
1394 + ///
1395 + /// [`amount`](Self::amount) is stated only when it is a measured fact about the
1396 + /// payload. An upload's file length, yes; a round trip to a payment provider,
1397 + /// [`None`]. A duration is described nowhere, and a renderer may not manufacture
1398 + /// one from the amount either: a determinate bar shows what is done over what
1399 + /// there is, plus the time it has taken so far, and never a remaining time, an
1400 + /// arrival time or a rate extrapolated forwards. A prediction is wrong the
1401 + /// moment the transfer stalls, and being confidently wrong is worse than being
1402 + /// honestly indeterminate.
1403 + ///
1404 + /// This is why the crate refuses to say how long an undo stays offered and
1405 + /// accepts a byte count here. The refusal is about naming a decision that
1406 + /// belongs to the renderer; a file's length is not a decision, nobody chose it.
1407 + ///
1408 + /// # Not [`Meter`]
1409 + ///
1410 + /// [`Meter`] is how much of a set is done, and its own docs refuse the progress
1411 + /// of an operation on the grounds that a description is built once and dropped
1412 + /// while an operation runs between renders. That refusal stands. This names the
1413 + /// operation and its size, which is all that is known before it starts; how much
1414 + /// of it has gone through is the renderer's to observe live, and nothing round
1415 + /// trips through a description to say so.
1416 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1417 + #[non_exhaustive]
1418 + pub struct Awaiting {
1419 + /// Total work to get through, when it is a measured fact about the payload.
1420 + ///
1421 + /// `None` when the wait has no countable size, which is the common case and
1422 + /// the default.
1423 + ///
1424 + /// Unit-agnostic on purpose. Bytes for an upload, rows for an import; what
1425 + /// is being counted is the app's business and a renderer draws a proportion
1426 + /// either way.
1427 + pub amount: Option<u64>,
1428 + }
1429 +
1430 + impl Awaiting {
1431 + /// A wait with no countable size.
1432 + #[must_use]
1433 + pub const fn unmeasured() -> Self {
1434 + Self { amount: None }
1435 + }
1436 +
1437 + /// A wait whose size is known.
1438 + ///
1439 + /// Reach for it only with a measured figure. An estimate written in here is
1440 + /// a prediction wearing a fact's clothes, and the renderer has no way to
1441 + /// tell the two apart.
1442 + #[must_use]
1443 + pub const fn of(amount: u64) -> Self {
1444 + Self {
1445 + amount: Some(amount),
1446 + }
1447 + }
1448 +
1449 + /// Whether there is a proportion to draw.
1450 + ///
1451 + /// The question every renderer asks first, answered once here rather than by
1452 + /// a `matches!` in each. False means indeterminate, which is the honest
1453 + /// drawing when nothing countable was measured.
1454 + #[must_use]
1455 + pub const fn is_determinate(self) -> bool {
1456 + self.amount.is_some()
1457 + }
1458 + }
1459 +
1333 1460 /// How much of a set is done.
1334 1461 ///
1335 1462 /// Added 0.10.0. Nine sites across the two webview apps drew a bar and nothing
@@ -5260,4 +5387,14 @@
5260 5387 assert!((1..=2).contains(&flow.lines()));
5261 5388 }
5262 5389 }
5390 +
5391 + #[test]
5392 + fn an_awaiting_mark_is_indeterminate_until_something_is_measured() {
5393 + // The default is the common case: a call waits, and nothing about it is
5394 + // countable. A determinate bar is the exception and says so.
5395 + assert_eq!(Awaiting::default(), Awaiting::unmeasured());
5396 + assert!(!Awaiting::unmeasured().is_determinate());
5397 + assert!(Awaiting::of(40 * 1024 * 1024).is_determinate());
5398 + assert_eq!(Awaiting::of(7).amount, Some(7));
5399 + }
5263 5400 }