1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
use crateTone;
// Names this module's prose links to, resolved for rustdoc.
use crate::;
/// How much of a set is done.
///
/// Nine sites across the two webview apps drew a bar and nothing here named
/// one, so every described screen concatenated the two numbers into its
/// heading text instead: "Subtasks 3/7", "Time Tracking 45m tracked / 30m est,
/// over". Every fact survives that and the reading does not, which is the same
/// loss `RowPart::Tokens` closed when a toned status badge became prose.
///
/// # Why a pair and not a percentage
///
/// Both numbers, not the percentage the apps compute from them. The percentage
/// was the obvious shape and it had already been tried: goingson's
/// `Task::time_progress` divides, rounds, and then clamps to 100, which throws
/// away the one case the bar exists to show — 45 minutes tracked against a
/// 30-minute estimate. It carries a separate `is_over_estimate` boolean beside
/// it to recover the fact the clamp dropped. A pair keeps the over-run without a
/// companion flag, and [`percent`](Meter::percent) is still one call away for a
/// renderer that wants it.
///
/// The pair is also what the apps already have at every site. All seven
/// determinate bars write the ratio into the accessible layer and never the
/// percentage: `title="3/7 subtasks"`, `aria-label="3 of 7 subtasks completed"`,
/// a milestone's own `3/7` span. Given 43 nothing can recover "3 of 7", so a
/// percentage member would have made [`label`](Meter::label) mandatory at every
/// call site, which is the concatenated text this member removes, moved one
/// layer down.
///
/// # What this is not
///
/// The progress of an *operation*. Two of the nine sites are that — goingson's
/// focus timer, Balanced Breakfast's feed fetch — and they get nothing here, on
/// purpose. Both are imperative controllers over a live handle, driven by a tick
/// or an event stream, and a description is built once and dropped. Holding one
/// would mean growing a way to update a description between renders, which is a
/// different feature. [`Readiness::Pending`] and a [`Notice::Toast`] carry the
/// honest part.
///
/// The two cases are distinguishable in the markup rather than by taste: every
/// determinate bar in both apps carries a tone, and neither operation bar
/// carries one. Two codebases drew that line the same way without coordinating.
/// One figure with a caption: a number and what it counts.
///
/// The dashboard shape. A large value over a small caption, several of them in
/// a strip: a current streak, a completion rate, a total. Four put the value
/// above the caption and one inverts it, which is drift inside the shape
/// rather than a second shape.
///
/// # Why the value is text
///
/// "17", "84%", "12/30", "3d". A figure is whatever the app computed, already
/// formatted, and the formatting is the app's because only it knows whether the
/// number is a percentage, a duration or a ratio. This carries none of the
/// arithmetic [`Meter`] carries, and that is the difference between them: a
/// meter is a proportion a renderer draws, and a figure is a fact a renderer
/// sets in type.
///
/// # Tone is carried, for [`Meter`]'s reason
///
/// Three of the five sites tone the figure by their own means — `red`/`blue` on
/// the weekly review, a `${type}` class on the monthly one, `sync-stat-warn` on
/// sync. So tone is carried at every site that needs it and derived at none, and
/// no renderer can work out that a streak of zero is worth colouring.
///
/// # What is not here
///
/// Whether the figure answers a click. One of the five is a control — sync's
/// "Not Applied: 3" opens the list — and an action is not something this crate
/// can name: nothing here knows what a route is. That belongs beside the figure
/// in whatever layer holds the actions, the same way a row's activation sits
/// beside its parts rather than inside them.
///
/// The arrangement is not here either. Several figures in a strip is a set, and
/// a renderer given them one at a time cannot tell it is looking at one; the
/// layer that holds the tree is where the set gets said.
/// Something the user can do, and what it costs to say so.
///
/// Beside [`Meter`] and [`Figure`] for the reason those are here: a renderer
/// that is handed the parts has to decide how to say them, and a renderer that
/// is handed a finished string has already had the decision made for it.
///
/// No address. Where a control goes is the app's business and every host
/// follows it differently — an `hx-get`, a protocol URL, a function call — so
/// the description says what the control *is* and the caller keeps what it
/// does. That is the same split [`Choice`] makes.
///
/// No confirmation flag either, and that one is a finding rather than an
/// omission: a question asked *after* a control is pressed belongs to whatever
/// is holding the interaction, and a renderer that drew it would be asking
/// before there was anything to answer.
/// How a picture sits in the box it is given.
///
/// An intent rather than a value, so a renderer picks the expression it has:
/// `object-fit` in a webview, a texture's UV rect in egui, and in a terminal a
/// choice about how many cells the blit gets. Named because MNW already makes
/// the distinction deliberately at 17 sites and makes it three different ways,
/// which is a policy the app decided rather than one a shared crate would be
/// picking by accident.
/// A picture's own pixel dimensions.
///
/// Deliberately not [`makeover_geometry`]'s business. Geometry answers *how
/// much space a thing should get*, which is a scale question with the same
/// answer on every screen. This is the intrinsic size of one asset, which is a
/// fact about that asset and varies per picture.
///
/// [`makeover_geometry`]: https://docs.rs/makeover-geometry
/// A run of magnitudes read against one axis.
///
/// [`Meter`] is one proportion; this is a series of them that share a maximum,
/// and the shared maximum is the whole difference. A run of meters draws each
/// bar against its own `total`, so a chart said that way states the axis once
/// per bar and nothing holds the copies together. Here the axis is stated once
/// and a bar carries only where it sits on it.
///
/// # Why the axis and not a percentage per bar
///
/// [`Meter`]'s reason, one layer out. The app that drew MNW's revenue chart
/// computed `revenue / most * 100.0` and put the percentage in the markup, and
/// what reached the reader was a width with no numbers behind it: a bar at 100%
/// because it is the largest and a bar at 100% because the axis is wrong are the
/// same width and are not the same fact. Carrying both integers keeps the fact,
/// and [`Bar::fraction`] is one call away for a renderer that wants the ratio.
///
/// It is also the only shape that survives a compiled template. A residual holds
/// numbers the description HANDS a renderer, never ones a renderer works out
/// from two of them, so a chart drawn from a supplied percentage could be
/// described and could not be compiled. See `quasi_router::stage::number_at`.
///
/// # What is worded here and what is not
///
/// [`Bar::at`] is where the bar sits on the axis and [`label`](Self::label) is
/// what the magnitudes are, which is [`Meter::label`]'s split exactly. What
/// differs is [`Bar::reading`] and [`Bar::note`]: both arrive already worded,
/// because a magnitude's own units are the app's ("$42.10", not 4210) and a
/// count's noun inflects ("1 sale", "3 sales"). A renderer that pluralised
/// would be growing a lexer for one language.
/// One magnitude in a [`Chart`], at its place on the axis.
///
/// Carries no axis of its own on purpose: a bar read against a maximum it
/// states itself is a meter, and a run of those is not a chart.