//! Forward fence, the hand-written stylesheet ratchet. //! //! Every page ships both frontends. Measured 2026-08-14: 322KB of hand-written //! `style.css` plus 23KB of other hand-written sheets, against 10.7KB of //! `layout.css` and 3.3KB of `geometry.css` generated by makeover-build. The //! generated sheets are 4% of the CSS and the hand-written one has not shrunk, //! and that is currently the largest standing cost of the conversion, paid on //! every page load by every visitor including signed-out ones. //! //! # Parked, 2026-08-18 //! //! The ratchet below is `#[ignore]`d by Max's ruling until the conversion is //! finished and the duplicated CSS is gone. Everything this doc argues still //! holds and none of it is retracted; see the test's own doc comment for why the //! enforcement was suspended anyway, and for what turns it back on. //! `the_two_lists_agree` is unaffected and still runs. //! //! # Why this seal exists rather than a note saying the same thing //! //! The conversion's payoff is the hand-written sheet shrinking. Without a //! number, "we converted a screen" and "we removed its cost" are the same //! claim, and only the first one has ever actually happened: four conversion //! batches landed and this file's subject did not move. A seal makes the second //! claim checkable, the same way `frontend_globals` makes the script half //! checkable and `DEAD_VOCABULARY_HIGH_WATER` (in `build.rs`) makes the //! generated half checkable. This is the third of the three. //! //! # Kibibytes rather than bytes //! //! A byte-exact seal on a hand-edited file fails on whitespace, and a guard //! that fires on a reflow is a guard people learn to edit rather than read. A //! KiB is coarse enough that ordinary editing is free and fine enough that a //! deleted rule block shows up. //! //! # One-sided, and which side //! //! Growing fails. Under the feature freeze, adding to a hand-written sheet //! wants an argument, and the argument belongs in the commit that raises the //! number rather than nowhere. //! //! Shrinking fails too, asking for the seal to be lowered. That is the half //! that makes it a ratchet rather than a ceiling: a conversion batch that //! deletes a screen's CSS and leaves the seal where it was has left the next //! batch room to grow back into, which is exactly how 322KB happened. //! //! `check_vocabulary_use` deliberately only warns on the way down, and the //! reasoning does not carry here: dead vocabulary falls as a side effect of //! work aimed elsewhere, and this number falls only when somebody deleted CSS //! on purpose. Somebody who did that can lower a constant. use std::fs; /// Total hand-written CSS, in whole kibibytes, that this repo may ship. /// /// 337 on 2026-08-15, the first measurement: `style.css` 322,102 bytes, /// `wizard.css` 13,459, `media-player.css` 8,884, `no-js.css` 1,212, for /// 345,657 total. Nothing about that number is a target. It is where four /// conversion batches left it, recorded so the fifth has to move it. /// /// Lower it whenever it falls. Raising it is a decision, not a fix. const CSS_KIB_HIGH_WATER: usize = 337; /// The sheets this repo writes by hand. /// /// Kept in step with `HAND_WRITTEN_CSS` in `build.rs`, which is the list the /// breakpoint and vocabulary guards read. A sheet in one list and not the other /// is a sheet that can take bytes from a sealed neighbour and read as a /// deletion, so `the_two_lists_agree` below holds them together. /// /// `layout.css`, `geometry.css` and `embed-geometry.css` are excluded because /// they are generated: their size is makeover's answer, and shrinking them is /// not this repo's work to do. const SHEETS: [&str; 4] = [ "static/style.css", "static/wizard.css", "static/media-player.css", "static/no-js.css", ]; /// Total bytes across [`SHEETS`], and the per-sheet breakdown for the message. fn measure() -> (usize, Vec<(&'static str, usize)>) { let each: Vec<(&str, usize)> = SHEETS .iter() .map(|path| { let bytes = fs::read(path) .unwrap_or_else(|e| panic!("read {path}: {e}")) .len(); (*path, bytes) }) .collect(); (each.iter().map(|(_, bytes)| bytes).sum(), each) } /// Parked by Max on 2026-08-18, and the module doc above argues against parking /// it, so the argument gets answered rather than ignored. /// /// What the doc says: a guard people edit rather than read is not a guard, and /// without a number "we converted a screen" and "we removed its cost" are the /// same claim. Both points stand. What they do not survive is the assertion /// pair below: this test fails on any decrease as well as any increase, so mid /// conversion it fires on work going in the direction it exists to reward, and /// the number gets re-baselined every batch. A seal that is re-baselined on /// schedule measures nothing, and it teaches exactly the editing habit the doc /// warns about. /// /// So the number is kept and the enforcement is suspended, rather than the seal /// being quietly lowered batch by batch. `CSS_KIB_HIGH_WATER` stays at 337 /// because it is the record of where the conversion started, and it is what the /// finished conversion gets measured against. /// /// WHAT BRINGS IT BACK: the conversion complete and the duplicated CSS removed. /// At that point the number stops moving in both directions, and the decision /// deferred with it becomes worth taking: seal the minified size rather than raw /// bytes, which is what Max ruled earlier the same day. `lightningcss` is /// already a dependency of this repo, so that costs no new dependency. /// /// STATE AT PARKING, measured 2026-08-18: /// /// static/style.css 324,648 (322,102 at the 2026-08-15 baseline) /// static/wizard.css 13,459 /// static/media-player.css 8,953 /// static/no-js.css 1,212 /// total 348,272 = 340 KiB against the sealed 337 /// /// Red on the grow assertion by 3 KiB, which is what prompted the ruling. /// /// GoingsOn mnw-server `2d01900f`. #[test] #[ignore = "parked by Max 2026-08-18 until the conversion is finished and the duplicated CSS is gone: see this test's doc comment"] fn hand_written_css_does_not_grow() { let (total, each) = measure(); let kib = total / 1024; let breakdown = each .iter() .map(|(path, bytes)| format!(" {path}: {bytes}")) .collect::>() .join("\n"); assert!( kib <= CSS_KIB_HIGH_WATER, "hand-written CSS grew to {kib} KiB ({total} bytes), above the sealed \ {CSS_KIB_HIGH_WATER}:\n{breakdown}\n\n\ The conversion is supposed to move this number down. If a rule really \ belongs in a hand-written sheet rather than in makeover-webview, raise \ CSS_KIB_HIGH_WATER to {kib} and say why in the same commit." ); assert_eq!( kib, CSS_KIB_HIGH_WATER, "hand-written CSS fell to {kib} KiB ({total} bytes). Lower \ CSS_KIB_HIGH_WATER to {kib} so it cannot grow back:\n{breakdown}" ); } #[test] fn the_two_lists_agree() { // `build.rs` is not a module this test can import, so the list is compared // as text. Crude, and it is the only thing standing between the two lists // drifting apart, which would let bytes move from a weighed sheet to an // unweighed one and read as a deletion. let build = fs::read_to_string("build.rs").expect("read build.rs"); let (start, _) = build .split_once("const HAND_WRITTEN_CSS") .expect("build.rs declares HAND_WRITTEN_CSS"); // On `= [` rather than `[`, because the declaration's first bracket is the // type annotation (`: [&str; 4]`) and matching that reads the length back // as the list. let declared = &build[start.len()..]; let list = declared .split_once("= [") .and_then(|(_, rest)| rest.split_once(']')) .map(|(inner, _)| inner) .expect("HAND_WRITTEN_CSS is an array literal"); for sheet in SHEETS { assert!( list.contains(sheet), "{sheet} is weighed by this seal but is not in build.rs's \ HAND_WRITTEN_CSS, so it is not breakpoint- or vocabulary-checked." ); } let in_build = list.matches("static/").count(); assert_eq!( in_build, SHEETS.len(), "build.rs's HAND_WRITTEN_CSS lists {in_build} sheets and this seal \ weighs {}. A sheet in one list and not the other can take bytes from \ a sealed neighbour and read as a deletion.", SHEETS.len() ); }