Skip to main content

max / makeover-webview

Emit the clamp a relaxed part asks for makeover-layout 0.28.1 names Flow, and this is the renderer that can honour it cheaply: the class carries a two-line clamp and the description says which parts get it. The shape is what both webview apps had already written by hand and commented, which is the whole argument for the description carrying it. Flow::Tight gets no class. One line is what a run already does, so a rule for it would restate the default on every part in every row, and a renderer emitting nothing for an unknown flow is then correct rather than lossy. The count comes from Flow::Relaxed.lines() rather than being written here, so if the tier ever means three lines this crate follows without being edited. Three literals joined the colour test's allow-list. -webkit-box and vertical are structure, the only way CSS lets anyone ask for a clamp. The 2 is the awkward one and is a count of lines rather than a length: a line's height is still the app's, so two lines is whatever two of the app's lines come to, and nothing here decides how tall that is. FLOW_CLASSES joins vocabulary::names, or an app's dead-vocabulary seal and its overlap check would both be blind to a class this crate can write.
Author: Max Johnson <me@maxj.phd> · 2026-08-17 23:45 UTC
Signed with PGP, not checked
Commit: 4715b3d7327cef24deb83b15a2dc4991eaa308a3
Parent: 0080aaf
4 files changed, +87 insertions, -5 deletions
M Cargo.toml +2 -2
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makeover-webview"
3 - version = "0.47.0"
3 + version = "0.48.0"
4 4 edition = "2024"
5 5 # One copy of this renderer per dependency graph, enforced by cargo rather than
6 6 # by remembering. Two versions means the generated stylesheet and the emitted
@@ -17,7 +17,7 @@
17 17 # patch satisfy the requirement and still fail to compile. That happened once
18 18 # with `form::radio_html` calling `FieldKind::Radio`, and makeover-build is where
19 19 # it surfaced, one release later.
20 - makeover-layout = "0.28.0"
20 + makeover-layout = "0.28.1"
21 21 # The capability axis. `makeover-touch` decides whether a hover rule should be
22 22 # gated at all; `makeover-geometry` spells the gate as a media condition. Both
23 23 # answers are owned elsewhere and neither is re-derived here.
M src/lib.rs +58 -1
@@ -290,7 +290,7 @@
290 290 // rules are as generated as the ones here.
291 291 pub use makeover_geometry::{CSS_LAYER, in_css_layer};
292 292 use makeover_layout::{
293 - Bevel, CellPart, Depth, Fill, Intent, RowPart, Selector, Sort, State, Token, Tone,
293 + Bevel, CellPart, Depth, Fill, Flow, Intent, RowPart, Selector, Sort, State, Token, Tone,
294 294 };
295 295 use makeover_touch::Affordance;
296 296 use std::fmt::Write as _;
@@ -953,6 +953,24 @@
953 953 // until pointed at. A table cell's actions were never hidden, so the
954 954 // two arrangements now agree.
955 955 }
956 +
957 + // A part that may take two lines. `Flow::Tight` gets no rule: one line is
958 + // what a run already does, and restating it here would put a declaration on
959 + // every part in every row to say nothing.
960 + //
961 + // This is the shape both webview apps had already written by hand and
962 + // commented -- Balanced Breakfast on a feed row's title, goingson on a
963 + // problem's body -- which is the whole argument for the description
964 + // carrying it. `-webkit-` prefixed and unprefixed together: the prefixed
965 + // trio is what every engine actually implements, and `line-clamp` is the
966 + // standard property landing behind it.
967 + let _ = writeln!(
968 + css,
969 + ".{} {{\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: {lines};\n line-clamp: {lines};\n overflow: hidden;\n}}",
970 + class("row-relaxed", opts),
971 + lines = Flow::Relaxed.lines()
972 + );
973 +
956 974 css
957 975 }
958 976
@@ -2309,6 +2327,25 @@
2309 2327 }
2310 2328 }
2311 2329
2330 + #[test]
2331 + fn a_relaxed_part_clamps_and_a_tight_one_says_nothing() {
2332 + let css = stylesheet(&Emit::default());
2333 + assert!(css.contains(".row-relaxed {"));
2334 + assert!(css.contains("-webkit-line-clamp: 2;"));
2335 + // The count is `Flow`'s, not this crate's. If the tier ever means three
2336 + // lines, this fails here rather than in an app.
2337 + assert!(css.contains(&format!("line-clamp: {};", Flow::Relaxed.lines())));
2338 + // Tight gets no rule at all: one line is what a run already does, and a
2339 + // class per part saying so is a declaration that changes nothing.
2340 + assert!(!css.contains("row-tight"));
2341 + assert_eq!(crate::list::flow_class(Flow::Relaxed), Some("row-relaxed"));
2342 + assert_eq!(crate::list::flow_class(Flow::Tight), None);
2343 + // Emitted, therefore checkable: an app's dead-vocabulary seal and the
2344 + // overlap check both read `vocabulary::names`, so a class the renderer
2345 + // can write and that list does not carry is invisible to both.
2346 + assert!(crate::vocabulary::names(&Emit::default()).contains("row-relaxed"));
2347 + }
2348 +
2312 2349 #[test]
2313 2350 fn the_whole_sheet_still_names_every_colour() {
2314 2351 // The crate's founding property, asserted over the component layer and
@@ -2424,6 +2461,26 @@
2424 2461 | "block"
2425 2462 | "cover"
2426 2463 | "contain"
2464 + // A relaxed part's three, and the third is the
2465 + // awkward one. `-webkit-box` and `vertical` are
2466 + // structure: they say the part is a box of lines
2467 + // stacked downward, which is the only way CSS lets
2468 + // anyone ask for a clamp at all.
2469 + //
2470 + // `2` is a count of lines, not a length. The
2471 + // distinction this crate holds is between naming a
2472 + // magnitude -- a padding, a height, a font size,
2473 + // all of which belong to makeover-geometry -- and
2474 + // naming how many of something there are. A line's
2475 + // height is still the app's, so two lines is
2476 + // whatever two of the app's lines come to, and
2477 + // nothing here decides how tall that is. It is also
2478 + // not a value picked here: it is `Flow::Relaxed`'s
2479 + // own answer arriving unchanged, the same way
2480 + // `cover` and `contain` are `Fit`'s.
2481 + | "-webkit-box"
2482 + | "vertical"
2483 + | "2"
2427 2484 ),
2428 2485 "unrecognised literal value: {line}"
2429 2486 );
M src/list.rs +25 -1
@@ -32,7 +32,7 @@
32 32
33 33 use crate::form::Markup;
34 34 use crate::{Emit, push_class};
35 - use makeover_layout::{CellPart, Column, Priority, RowPart, Width};
35 + use makeover_layout::{CellPart, Column, Flow, Priority, RowPart, Width};
36 36 use std::fmt::Write as _;
37 37
38 38 /// The lengths the description deferred.
@@ -359,6 +359,30 @@
359 359 "row-part",
360 360 ];
361 361
362 + /// Every class [`flow_class`] can return.
363 + ///
364 + /// `Flow::Tight` has no class: one line is what a run already does, so a rule
365 + /// for it would restate the default on every part in every row. Only the tier
366 + /// that departs from it is named, which is also why a renderer emitting nothing
367 + /// for an unknown flow is correct rather than lossy.
368 + pub const FLOW_CLASSES: &[&str] = &["row-relaxed"];
369 +
370 + /// The class for a part's flow, if it needs one.
371 + ///
372 + /// `None` for [`Flow::Tight`] and for any tier added upstream that this crate
373 + /// has not been taught, which lands as one line: the same trade
374 + /// [`part_class`]'s fallback makes, and the safe direction, since a part that
375 + /// grows without bound breaks the rows around it while a part that stays on one
376 + /// line only looks like the old rendering. Grep this when adopting a new
377 + /// makeover-layout.
378 + #[must_use]
379 + pub fn flow_class(flow: Flow) -> Option<&'static str> {
380 + match flow {
381 + Flow::Relaxed => Some("row-relaxed"),
382 + _ => None,
383 + }
384 + }
385 +
362 386 /// Every class [`cell_part_class`] can return, including the fallback.
363 387 ///
364 388 /// See [`ROW_PART_CLASSES`] for why it is written out.
@@ -34,7 +34,7 @@
34 34 //! from, and a class added to an emitter enters the vocabulary in the same
35 35 //! commit that adds it.
36 36
37 - use crate::list::{CELL_PART_CLASSES, ROW_PART_CLASSES};
37 + use crate::list::{CELL_PART_CLASSES, FLOW_CLASSES, ROW_PART_CLASSES};
38 38 use crate::{Emit, option_class};
39 39 use makeover_layout::Selector;
40 40 use std::collections::{BTreeMap, BTreeSet};
@@ -64,6 +64,7 @@
64 64 ROW_PART_CLASSES
65 65 .iter()
66 66 .chain(CELL_PART_CLASSES)
67 + .chain(FLOW_CLASSES)
67 68 .map(|name| crate::class(name, opts)),
68 69 );
69 70 all.extend(