Skip to main content

max / makenotwork

6.1 KB · 151 lines History Blame Raw
1 //! Forward fence, the hand-written stylesheet ratchet.
2 //!
3 //! Every page ships both frontends. Measured 2026-08-14: 322KB of hand-written
4 //! `style.css` plus 23KB of other hand-written sheets, against 10.7KB of
5 //! `layout.css` and 3.3KB of `geometry.css` generated by makeover-build. The
6 //! generated sheets are 4% of the CSS and the hand-written one has not shrunk,
7 //! and that is currently the largest standing cost of the conversion, paid on
8 //! every page load by every visitor including signed-out ones.
9 //!
10 //! # Why this seal exists rather than a note saying the same thing
11 //!
12 //! The conversion's payoff is the hand-written sheet shrinking. Without a
13 //! number, "we converted a screen" and "we removed its cost" are the same
14 //! claim, and only the first one has ever actually happened: four conversion
15 //! batches landed and this file's subject did not move. A seal makes the second
16 //! claim checkable, the same way `frontend_globals` makes the script half
17 //! checkable and `DEAD_VOCABULARY_HIGH_WATER` (in `build.rs`) makes the
18 //! generated half checkable. This is the third of the three.
19 //!
20 //! # Kibibytes rather than bytes
21 //!
22 //! A byte-exact seal on a hand-edited file fails on whitespace, and a guard
23 //! that fires on a reflow is a guard people learn to edit rather than read. A
24 //! KiB is coarse enough that ordinary editing is free and fine enough that a
25 //! deleted rule block shows up.
26 //!
27 //! # One-sided, and which side
28 //!
29 //! Growing fails. Under the feature freeze, adding to a hand-written sheet
30 //! wants an argument, and the argument belongs in the commit that raises the
31 //! number rather than nowhere.
32 //!
33 //! Shrinking fails too, asking for the seal to be lowered. That is the half
34 //! that makes it a ratchet rather than a ceiling: a conversion batch that
35 //! deletes a screen's CSS and leaves the seal where it was has left the next
36 //! batch room to grow back into, which is exactly how 322KB happened.
37 //!
38 //! `check_vocabulary_use` deliberately only warns on the way down, and the
39 //! reasoning does not carry here: dead vocabulary falls as a side effect of
40 //! work aimed elsewhere, and this number falls only when somebody deleted CSS
41 //! on purpose. Somebody who did that can lower a constant.
42
43 use std::fs;
44
45 /// Total hand-written CSS, in whole kibibytes, that this repo may ship.
46 ///
47 /// 337 on 2026-08-15, the first measurement: `style.css` 322,102 bytes,
48 /// `wizard.css` 13,459, `media-player.css` 8,884, `no-js.css` 1,212, for
49 /// 345,657 total. Nothing about that number is a target. It is where four
50 /// conversion batches left it, recorded so the fifth has to move it.
51 ///
52 /// Lower it whenever it falls. Raising it is a decision, not a fix.
53 const CSS_KIB_HIGH_WATER: usize = 337;
54
55 /// The sheets this repo writes by hand.
56 ///
57 /// Kept in step with `HAND_WRITTEN_CSS` in `build.rs`, which is the list the
58 /// breakpoint and vocabulary guards read. A sheet in one list and not the other
59 /// is a sheet that can take bytes from a sealed neighbour and read as a
60 /// deletion, so `the_two_lists_agree` below holds them together.
61 ///
62 /// `layout.css`, `geometry.css` and `embed-geometry.css` are excluded because
63 /// they are generated: their size is makeover's answer, and shrinking them is
64 /// not this repo's work to do.
65 const SHEETS: [&str; 4] = [
66 "static/style.css",
67 "static/wizard.css",
68 "static/media-player.css",
69 "static/no-js.css",
70 ];
71
72 /// Total bytes across [`SHEETS`], and the per-sheet breakdown for the message.
73 fn measure() -> (usize, Vec<(&'static str, usize)>) {
74 let each: Vec<(&str, usize)> = SHEETS
75 .iter()
76 .map(|path| {
77 let bytes = fs::read(path)
78 .unwrap_or_else(|e| panic!("read {path}: {e}"))
79 .len();
80 (*path, bytes)
81 })
82 .collect();
83 (each.iter().map(|(_, bytes)| bytes).sum(), each)
84 }
85
86 #[test]
87 fn hand_written_css_does_not_grow() {
88 let (total, each) = measure();
89 let kib = total / 1024;
90
91 let breakdown = each
92 .iter()
93 .map(|(path, bytes)| format!(" {path}: {bytes}"))
94 .collect::<Vec<_>>()
95 .join("\n");
96
97 assert!(
98 kib <= CSS_KIB_HIGH_WATER,
99 "hand-written CSS grew to {kib} KiB ({total} bytes), above the sealed \
100 {CSS_KIB_HIGH_WATER}:\n{breakdown}\n\n\
101 The conversion is supposed to move this number down. If a rule really \
102 belongs in a hand-written sheet rather than in makeover-webview, raise \
103 CSS_KIB_HIGH_WATER to {kib} and say why in the same commit."
104 );
105
106 assert_eq!(
107 kib, CSS_KIB_HIGH_WATER,
108 "hand-written CSS fell to {kib} KiB ({total} bytes). Lower \
109 CSS_KIB_HIGH_WATER to {kib} so it cannot grow back:\n{breakdown}"
110 );
111 }
112
113 #[test]
114 fn the_two_lists_agree() {
115 // `build.rs` is not a module this test can import, so the list is compared
116 // as text. Crude, and it is the only thing standing between the two lists
117 // drifting apart, which would let bytes move from a weighed sheet to an
118 // unweighed one and read as a deletion.
119 let build = fs::read_to_string("build.rs").expect("read build.rs");
120 let (start, _) = build
121 .split_once("const HAND_WRITTEN_CSS")
122 .expect("build.rs declares HAND_WRITTEN_CSS");
123 // On `= [` rather than `[`, because the declaration's first bracket is the
124 // type annotation (`: [&str; 4]`) and matching that reads the length back
125 // as the list.
126 let declared = &build[start.len()..];
127 let list = declared
128 .split_once("= [")
129 .and_then(|(_, rest)| rest.split_once(']'))
130 .map(|(inner, _)| inner)
131 .expect("HAND_WRITTEN_CSS is an array literal");
132
133 for sheet in SHEETS {
134 assert!(
135 list.contains(sheet),
136 "{sheet} is weighed by this seal but is not in build.rs's \
137 HAND_WRITTEN_CSS, so it is not breakpoint- or vocabulary-checked."
138 );
139 }
140
141 let in_build = list.matches("static/").count();
142 assert_eq!(
143 in_build,
144 SHEETS.len(),
145 "build.rs's HAND_WRITTEN_CSS lists {in_build} sheets and this seal \
146 weighs {}. A sheet in one list and not the other can take bytes from \
147 a sealed neighbour and read as a deletion.",
148 SHEETS.len()
149 );
150 }
151