//! Forward fence, the frontend-globals ratchet. //! //! The legacy `static/*.js` files register behavior as `window. = fn` //! globals (the pre-module `data-action` dispatcher pattern). The frontend //! restructure (`_private/docs/mnw/frontend/`) migrates these into typed ES //! modules under `frontend/src`. This seal keeps the count monotonically //! non-increasing: adding a new `window.*` global fails the build, and every //! migrated file must lower `HIGH_WATER`. Same idea as the migration //! HIGH_WATER seal. //! //! When you remove globals, lower `HIGH_WATER` to the new count (the failure //! message reports it). Never raise it. use std::fs; /// Ratchets down only, never up. 115 at the start of the restructure /// (2026-07-07); 113 after Phase 1 retired `mnw.js` (its 2 `window.*` globals /// moved into the typed core module's bridge). const HIGH_WATER: usize = 113; /// Count `window. =` assignments (not `==`/`===` comparisons) across the /// legacy `static/*.js` files. fn count_window_globals() -> usize { // `=[^=]` matches an assignment `=` while excluding the first `=` of a // comparison operator. The regex crate has no lookahead, so this is the // portable form. let re = regex::Regex::new(r"window\.[A-Za-z_][A-Za-z0-9_]*\s*=[^=]").unwrap(); let static_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/static"); let mut total = 0; for entry in fs::read_dir(static_dir).expect("read static/ dir") { let path = entry.expect("dir entry").path(); if path.extension().and_then(|e| e.to_str()) == Some("js") { let src = fs::read_to_string(&path).unwrap_or_default(); total += re.find_iter(&src).count(); } } total } #[test] fn frontend_globals_do_not_grow() { let count = count_window_globals(); assert!( count <= HIGH_WATER, "window.* global assignments in static/*.js rose to {count} (HIGH_WATER {HIGH_WATER}). \ New frontend code must be a typed ES module in frontend/src registered through the \ dispatcher, not a `window.*` global. If you REMOVED globals, lower HIGH_WATER to {count}." ); }