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
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
//! Phase B, the forms half: the escaper, the field classes and the rules a
//! described field's markup is drawn with.
//!
//! The field markup itself is drawn by the quasi hosts (`quasi-webview` for
//! this renderer), which own the vocabulary member. What stays here is what
//! every caller writing HTML needs and what the generated stylesheet has to
//! agree with: [`escape`] and [`escape_into`], [`Markup`] and [`Filling`], the
//! class lists, and the rules behind them.
//!
//! # Why one escaper is enough
//!
//! goingson carries four escapers and 543 call sites that must pick between
//! them, because `escapeHtml` is built on `textContent` serialization and
//! **`textContent` refuses to encode `"`**. That is what makes it unsound in an
//! attribute, and it is the whole reason the choice exists. Its `escape.js`
//! records the finding as the CHRONIC-XSS seal, and its test suite has a gate
//! keeping the unsafe one off the namespace.
//!
//! [`escape`] here is not built on that, so it encodes the quote along with
//! everything else, which makes one function sound in both sinks. The four-way
//! choice does not move into Rust: it disappears. A caller hands an unescaped
//! value to the output only through [`Markup`], which it has to name.
//!
//! # What the description does not carry
//!
//! One thing: the **current value**, which arrives in [`Filling`]. A webview
//! reads it back out of the DOM, an immediate-mode renderer writes through a
//! `&mut`, and a terminal keeps an edit buffer; a description carrying it would
//! have to carry a way to write it back, at which point it is a form model.
use crate::;
use ;
use Write as _;
/// Every class a field's markup can carry.
///
/// [`crate::facet::FACET_CLASSES`]' obligation. Every one of these is ruled by
/// the generated sheet, the group's four through `group_rules`; the list is
/// written down anyway, because it is what the hosts' markup is held against
/// and what the vocabulary test holds the sheet against.
///
/// What goes wrong without it: an app checking its stylesheet against
/// [`crate::vocabulary::names`] concludes that its live `.form-group` and
/// `.form-label` rules match nothing and are safe to delete.
pub const FIELD_CLASSES: & = &;
// `form-suggestions`, `form-suggestion` and `form-suggestion-detail` are
// deliberately absent: [`suggestion_rules`] writes their look and
// `quasi-webview` writes their markup, because a suggestion source is a route
// and no description layer carries one. They reach the vocabulary through the
// generated sheet, which is where a name this crate rules but does not emit
// belongs.
/// The state classes a field carries, which take no prefix.
///
/// `chosen` and `latched`'s convention, stated in
/// [`crate::vocabulary::vocabulary`]: a state qualifies a prefixed component
/// (`.mk-form-group.has-error`) rather than standing on its own, so a prefix
/// moves the thing and not its state.
///
/// `has-error` marks the group and `visible` marks the message: a renderer
/// with no descendant selectors cannot find the group from the message, so
/// both are told.
pub const FIELD_STATE_CLASSES: & = &;
/// A string that is already markup, and is emitted without escaping.
///
/// The one hole in the escaping, and it has to be named to be used. goingson
/// has two live callers that need it, both passing a recurrence-config block
/// built elsewhere, and both would otherwise have their markup rendered as
/// visible angle brackets. A caller constructing this is stating that the
/// contents are trusted; nothing here can check that for them.
;
/// Everything about the field that the description does not carry.
/// Encode the five characters that let a value stop being a value, into a
/// buffer the caller already has.
///
/// The form an emitter uses. [`escape`] is this with a `String` allocated
/// around it, and the allocation is the whole difference: a described screen
/// escapes once per attribute and once per run of text, so a function that
/// returns a `String` allocates a few thousand times to produce one page,
/// where a template engine writes its escaped bytes straight into the output
/// buffer.
///
/// Sound in element text and in a double-quoted attribute alike, which is the
/// property `textContent`-based escaping cannot have. Both sinks are covered by
/// one function so that no call site has to choose, here or downstream.
///
/// Copies in runs rather than per character. All five encoded characters are
/// ASCII, so a byte scan cannot land inside a multi-byte character and the
/// slice between two of them is always a valid `&str`. Text with nothing to
/// encode, which is most text, is one `push_str` of the whole thing.
/// Encode the five characters that let a value stop being a value.
///
/// [`escape_into`] with a buffer of its own, for the callers that want a value
/// rather than an append: a caller assembling an attribute out of several
/// pieces, and everything outside this crate that took this function before the
/// buffer-writing form existed. Emitting into a buffer you already hold is the
/// cheaper path and the one the hosts' emitters take.
/// The rules the markdown editor's chrome needs.
///
/// The two names here have no app counterpart to keep, because the chrome did
/// not exist before the member did. The markup is a Write/Preview pair of
/// segments and an empty preview pane, drawn by the hosts; converting markdown
/// stays the host's, where its sanitiser already is.
///
/// Every rule is gated on `[data-format="markdown"]`, which is what keeps them
/// off a plain textarea, and every rule that hides content is gated on
/// `data-ready` as well, which is what keeps them out of a document with no
/// script.
pub
/// The rules a field's group needs: its label, its hint, its message, and the
/// arrangement of the controls that answer one question.
///
/// These were the apps' names from phase A, left unruled so that adoption would
/// delete goingson's `renderFormField` rather than restyle anything. Adoption
/// happened and the argument went with it: a described form in an app that had
/// never written the rules drew its label as body text and its error as a plain
/// sentence, and MNW was that app. wiki `look-restoration`: the renderer owns
/// the look.
///
/// The values are the ones goingson shipped, less the group's own margin. A
/// form spaces its groups with a gap, so a margin here would space them twice;
/// an app laying groups out in normal flow keeps a margin of its own. The radio,
/// checkbox, reason and interval rules came from quasi-webview's arrangement
/// sheet unchanged, where they styled names this crate emits.
///
/// `visible` is the message's state rather than decoration. goingson's scripts
/// raise and lower it on a message that stays in the document, and this crate
/// writes it on every message it emits, so a lowered message is hidden and a
/// written one shows.
///
/// `has-error` tones the label. The control already carries the danger edge
/// through `aria-invalid`; the label is what a reader scanning a long form reads
/// first, and it is the part of the group that says which question failed.
pub
/// The rules a field's note needs.
///
/// Nothing emitted a note before the described field could carry one, so
/// there was no app rule to keep.
///
/// Colour only, and the tones are the four a badge carries. The bare class is
/// `content` rather than `content-muted`: a note is a consequence the user is
/// meant to read before answering, so muting it by default would be this crate
/// deciding it does not matter.
pub
/// The rule a field's unit needs.
///
/// Nothing emitted a unit before the described field could carry one, so
/// there was no app rule to keep.
///
/// One declaration, and it is the whole look. A unit is a fact about the number
/// beside it rather than a second thing to read, so it takes the muted content
/// intent, the same reading `.figure-caption` and `.track-tick` take, and for
/// the same reason.
///
/// And where it sits, which the first version left to the flow and got wrong. A
/// control fills its group's width, so the span after it wrapped onto a line of
/// its own and "min" hung under the box it measures. A group carrying a unit is
/// two columns instead: the control and its unit share the first row's line,
/// and the label, the hint, the note and the message span both. The control's
/// track is `auto`, so it is as wide as the control and the unit reads straight
/// after the number; the spare room goes to the unit's track, where it is
/// empty. The other way round put "min" at the far edge of the pane.
pub
/// The rules an option's second line needs.
///
/// [`unit_rules`]' argument: rule what has no app counterpart to keep. An
/// unruled second line renders identically to the label it sits under, which is
/// a worse default than the hand-written markup it replaces.
///
/// Colour only, and muted, which is the same reading `.form-unit` and
/// `.form-suggestion-detail` take: the line orients the label rather than
/// competing with it. Nothing about placement or spacing, for `unit_rules`'
/// reason, a magnitude asserted here belongs to `makeover-geometry`.
pub
/// The rules a field's suggestion list needs.
///
/// [`editor_rules`]' precedent and its argument: these three have no app
/// counterpart to keep because the list did not exist before the member did.
/// The markup is `quasi-webview`'s, a suggestion source is a route, which no
/// description layer carries, and the look is
/// still this crate's, because a renderer inventing how a list of candidates
/// reads is the drift the vocabulary check exists to catch.
///
/// # In flow, and not floating
///
/// An absolutely positioned list needs a positioned ancestor, and the only
/// candidate is `.form-group`, which is the app's class and deliberately
/// unruled here. So the list stands under the control and moves what is below
/// it. An app that wants it over the form positions the group itself, which is
/// one declaration and is the app's call about its own layout.
///
/// `:empty` is what takes it away, so a route that answers with no candidates
/// leaves no box behind. It is a content question rather than a whitespace one
/// only because the emitter writes no whitespace inside the container, which is
/// stated in `quasi-webview`'s own test.
///
/// # Placement, and nothing about size
///
/// One candidate to a line, its detail at the line's end. Without it the
/// entries are bare anchors and run together as one paragraph: MNW's discover
/// search drew "Blue HourProjectKickItem". Spacing comes from the geometry
/// tokens every other form rule uses, so no magnitude is asserted here.
///
/// No height and no scroll ceiling. How tall a list of candidates gets to be
/// before it scrolls is a magnitude, and magnitudes are `makeover-geometry`'s,
/// exactly as the preview pane's height is.
pub