Skip to main content

max / makeover-webview

4.1 KB · 101 lines History Blame Raw
1 # makeover-webview
2
3 The webview renderer for
4 [`makeover-layout`]https://makenot.work/git/max/makeover-layout. Emits CSS.
5
6 ## The renderer that needs no palette
7
8 `makeover-immediate` and `makeover-tui` both take a `Palette`, because egui and
9 a terminal need an actual colour before they can put anything on screen. A
10 webview does not. `var(--surface-raised)` *is* the late binding, and the browser
11 resolves it against whatever the theme layer last wrote onto `:root`.
12
13 So this crate emits text naming intents and never learns a colour, which is the
14 deferral rule with no adapter in the way. It is also why the webview was always
15 the wrong renderer to derive a vocabulary from: it can express anything, so it
16 never pushes back.
17
18 ## Phase A: the stylesheet
19
20 No markup here, deliberately. Both webview apps build their markup in JS, so
21 moving markup is a migration while adopting a generated stylesheet is a
22 deletion. The apps keep every line of their markup and gain the classes.
23
24 ```css
25 :root {
26 --bevel-raised: inset 1px 1px 0 var(--bevel-light), inset -1px -1px 0 var(--bevel-dark);
27 --bevel-inset: inset 1px 1px 0 var(--bevel-dark), inset -1px -1px 0 var(--bevel-light);
28 }
29
30 .raised { background: var(--surface-raised); box-shadow: var(--bevel-raised); }
31 .well { background: var(--surface-well, var(--surface-page)); box-shadow: var(--bevel-inset); }
32 .card:active { background: var(--surface-well, var(--surface-page)); box-shadow: var(--bevel-inset); }
33 ```
34
35 Those two custom properties are byte-identical to what both apps hand-write,
36 which is asserted in the tests. Adoption removes duplicated lines rather than
37 changing a pixel.
38
39 The depth classes are surfaces and nothing more. A raised object that is not
40 pressable is spelled `.raised`, and the states belong to the named surfaces:
41 `.card` and `.button` are that same depth *and* controls, so they carry hover,
42 press, focus and disabled.
43
44 `.card:active` is the one thing this renderer gets for free: the cascade carries
45 a pressed state that an immediate-mode renderer resolves per call site.
46
47 ## Phase B: form markup
48
49 `form::field_html` renders a `makeover_layout::Field` to the group both apps
50 build by hand today: label, control, hint, error, in goingson's shape and class
51 names, so adopting it deletes `renderFormField` rather than restyling anything.
52
53 ```rust
54 use makeover_layout::{Field, FieldKind};
55 use makeover_webview::{Emit, form::{Filling, Value, field_html}};
56
57 let field = Field::new(FieldKind::Text, "title", "Title");
58 let html = field_html(&field, &Filling::of(Value::Text("Ship it")), &Emit::default());
59 ```
60
61 It emits strings because both apps interpolate fields into larger string-built
62 forms, and it escapes them itself. One escaper covers both sinks, because the
63 `textContent` serialization that forces a JS app to pick between escapers is not
64 a constraint Rust has. The single hole is `form::Markup`, which a caller has to
65 name.
66
67 Three things the emitter does that neither app does at initial render:
68
69 - `aria-invalid="true"` on an invalid control, which is what the generated
70 stylesheet keys its danger ring on. goingson sets it from its runtime
71 validation path and not from its renderer, so a field rendered already-invalid
72 is styled as though nothing were wrong.
73 - `aria-describedby` naming the hint as well as the error, so standing help
74 survives an error appearing.
75 - A secret never carries its value into the markup, on `FieldKind::confidential`.
76
77 Rows and tables are the other half of phase B and are not written yet.
78
79 ## Substitution, three ways
80
81 A theme with no `surface-well` is answered differently by each of the three
82 renderers, which is why substitution belongs to a renderer and not to the
83 description:
84
85 - `makeover-immediate` substitutes the page in Rust.
86 - `makeover-tui` refuses to substitute and draws an edge, because a terminal
87 quantises the two together.
88 - here, CSS already has the mechanism. `var(--surface-well, var(--surface-page))`
89 falls back in the browser and nothing in Rust decides anything.
90
91 ## Status
92
93 On crates.io, the CSS half only; the form markup is unreleased.
94 Consumers are GoingsOn and Balanced Breakfast, both through makeover-build.
95
96 `cargo run --example dump` prints the stylesheet.
97
98 ## Licence
99
100 MIT.
101