max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
72 files changed,
+165 insertions,
-71 deletions
| @@ -5326,7 +5326,7 @@ | |||
| 5326 | 5326 | ||
| 5327 | 5327 | [[package]] | |
| 5328 | 5328 | name = "makeover-layout" | |
| 5329 | - | version = "0.17.0" | |
| 5329 | + | version = "0.18.0" | |
| 5330 | 5330 | ||
| 5331 | 5331 | [[package]] | |
| 5332 | 5332 | name = "makeover-touch" | |
| @@ -10694,6 +10694,10 @@ | |||
| 10694 | 10694 | name = "kberg" | |
| 10695 | 10695 | version = "0.1.0" | |
| 10696 | 10696 | ||
| 10697 | + | [[patch.unused]] | |
| 10698 | + | name = "ops-status" | |
| 10699 | + | version = "0.1.0" | |
| 10700 | + | ||
| 10697 | 10701 | [[patch.unused]] | |
| 10698 | 10702 | name = "painhours" | |
| 10699 | 10703 | version = "0.1.0" |
| @@ -159,7 +159,7 @@ | |||
| 159 | 159 | # rather than reached through quasi-router's re-export because a described | |
| 160 | 160 | # screen names FieldKind and Tone directly; it has to track what quasi-router | |
| 161 | 161 | # resolves or the two `layout::` paths are different crates. | |
| 162 | - | makeover-layout = "0.17.0" | |
| 162 | + | makeover-layout = "0.18.0" | |
| 163 | 163 | # For the request head the per-viewer state factory reads. axum re-exports it, | |
| 164 | 164 | # but the factory's signature is quasi-axum's and names `http::request::Parts`. | |
| 165 | 165 | http = "1.3.1" |
| @@ -87,6 +87,37 @@ | |||
| 87 | 87 | &parts().body_attrs | |
| 88 | 88 | } | |
| 89 | 89 | ||
| 90 | + | pub use makeover_layout::Measure; | |
| 91 | + | ||
| 92 | + | /// The body class for a screen's measure. | |
| 93 | + | /// | |
| 94 | + | /// `0eccff0d`. 69 of 72 templates carried one of these three strings as a | |
| 95 | + | /// literal, which made how wide a page runs a fact about the template rather | |
| 96 | + | /// than about the screen. The strings are unchanged and the rules in | |
| 97 | + | /// `style.css` are untouched: what moved is where the choice is written down, | |
| 98 | + | /// from a class name in markup to a described property with a name in every | |
| 99 | + | /// renderer's vocabulary. | |
| 100 | + | /// | |
| 101 | + | /// The old names stay on the left-hand side of the rules because they are what | |
| 102 | + | /// `style.css` matches, and renaming them is a separate change with no | |
| 103 | + | /// description in it. `padded-page` is [`Measure::Wide`] because a padded page | |
| 104 | + | /// is the full width with gutters, which is what the class always meant. | |
| 105 | + | /// | |
| 106 | + | /// The four standalone tokens -- `health-page`, `purchase-page`, `buy-page`, | |
| 107 | + | /// `stripe-disclaimer-page` -- are screen identity rather than measure, and are | |
| 108 | + | /// deliberately not here. | |
| 109 | + | #[must_use] | |
| 110 | + | pub const fn measure(measure: Measure) -> &'static str { | |
| 111 | + | match measure { | |
| 112 | + | Measure::Contained => "centered-page", | |
| 113 | + | Measure::Reading => "article-page", | |
| 114 | + | // The default, and the arm a member added upstream lands in. A measure | |
| 115 | + | // this server has not learned yet should render at the width every | |
| 116 | + | // other page does rather than unstyled. | |
| 117 | + | _ => "padded-page", | |
| 118 | + | } | |
| 119 | + | } | |
| 120 | + | ||
| 90 | 121 | #[cfg(test)] | |
| 91 | 122 | mod tests { | |
| 92 | 123 | use super::*; | |
| @@ -133,6 +164,65 @@ | |||
| 133 | 164 | assert!(!attrs.contains("class=")); | |
| 134 | 165 | } | |
| 135 | 166 | ||
| 167 | + | #[test] | |
| 168 | + | fn every_measure_keeps_the_class_the_templates_used_to_write() { | |
| 169 | + | // `0eccff0d` moved where the choice is written down and changed no | |
| 170 | + | // rule in `style.css`, so the three strings have to come out exactly as | |
| 171 | + | // the 69 templates spelled them. A typo here renders 53 pages unstyled. | |
| 172 | + | assert_eq!(measure(Measure::Wide), "padded-page"); | |
| 173 | + | assert_eq!(measure(Measure::Contained), "centered-page"); | |
| 174 | + | assert_eq!(measure(Measure::Reading), "article-page"); | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | #[test] | |
| 178 | + | fn no_template_still_writes_a_layout_class_by_hand() { | |
| 179 | + | // The done-condition, checked rather than remembered: the layout axis | |
| 180 | + | // is derived from the described property. A new template pasted from an | |
| 181 | + | // old one fails here instead of quietly reintroducing the literal. | |
| 182 | + | // | |
| 183 | + | // The four standalone tokens are screen identity rather than measure | |
| 184 | + | // and are deliberately left alone, so they are not looked for. | |
| 185 | + | let mut offenders = Vec::new(); | |
| 186 | + | for entry in walk("templates") { | |
| 187 | + | let source = std::fs::read_to_string(&entry).expect("a template reads"); | |
| 188 | + | for (at, line) in source.lines().enumerate() { | |
| 189 | + | if !line.contains("block body_attrs") { | |
| 190 | + | continue; | |
| 191 | + | } | |
| 192 | + | // The literal, as distinct from the call that produces it: the | |
| 193 | + | // rendered class still says `padded-page`, and should. | |
| 194 | + | let derived = line.contains("crate::shell::measure("); | |
| 195 | + | let literal = ["padded-page", "centered-page", "article-page"] | |
| 196 | + | .iter() | |
| 197 | + | .any(|name| line.contains(name)); | |
| 198 | + | if literal && !derived { | |
| 199 | + | offenders.push(format!("{}:{}", entry.display(), at + 1)); | |
| 200 | + | } | |
| 201 | + | } | |
| 202 | + | } | |
| 203 | + | assert!(offenders.is_empty(), "{offenders:?}"); | |
| 204 | + | } | |
| 205 | + | ||
| 206 | + | /// Every `.html` under a directory. | |
| 207 | + | fn walk(root: &str) -> Vec<std::path::PathBuf> { | |
| 208 | + | let mut found = Vec::new(); | |
| 209 | + | let mut stack = vec![std::path::PathBuf::from(root)]; | |
| 210 | + | while let Some(at) = stack.pop() { | |
| 211 | + | let Ok(entries) = std::fs::read_dir(&at) else { | |
| 212 | + | continue; | |
| 213 | + | }; | |
| 214 | + | for entry in entries.flatten() { | |
| 215 | + | let path = entry.path(); | |
| 216 | + | if path.is_dir() { | |
| 217 | + | stack.push(path); | |
| 218 | + | } else if path.extension().is_some_and(|ext| ext == "html") { | |
| 219 | + | found.push(path); | |
| 220 | + | } | |
| 221 | + | } | |
| 222 | + | } | |
| 223 | + | found | |
| 224 | + | } | |
| 225 | + | ||
| 136 | 226 | #[test] | |
| 137 | 227 | fn morph_is_loaded_wherever_a_swap_can_ask_for_it() { | |
| 138 | 228 | // The shell writes `hx-ext="morph"` onto every page's body, so the |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Admin: Appeals - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page admin-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} admin-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block head %} | |
| 7 | 7 | {% endblock %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Admin: Comp codes - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page admin-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} admin-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block head %} | |
| 7 | 7 | {% endblock %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Admin: Metrics - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page admin-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} admin-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block head %} | |
| 7 | 7 | {% endblock %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Admin: Reports - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page admin-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} admin-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block content %} | |
| 7 | 7 | {% include "partials/site_header.html" %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Admin: Scan Audit Log - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page admin-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} admin-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block content %} | |
| 7 | 7 | {% include "partials/site_header.html" %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Admin: Signups - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page admin-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} admin-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block content %} | |
| 7 | 7 | {% include "partials/site_header.html" %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Admin: Scan Pipeline - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page admin-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} admin-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block content %} | |
| 7 | 7 | {% include "partials/site_header.html" %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Admin: Users - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page admin-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} admin-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block head %} | |
| 7 | 7 | {% endblock %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Admin: Waitlist - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page admin-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} admin-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block head %} | |
| 7 | 7 | {% endblock %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}{% if editing %}Edit Post{% else %}New Post{% endif %} - {{ project_slug }} - Dashboard{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page blog-editor"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} blog-editor"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block content %} | |
| 7 | 7 | {% include "partials/site_header.html" %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | {% extends "base.html" %} | |
| 2 | 2 | ||
| 3 | 3 | {% block title %}Delete Account - Makenotwork{% endblock %} | |
| 4 | - | {% block body_attrs %} class="padded-page delete-account-page"{% endblock %} | |
| 4 | + | {% block body_attrs %} class="{{ crate::shell::measure(crate::shell::Measure::Wide) }} delete-account-page"{% endblock %} | |
| 5 | 5 | ||
| 6 | 6 | {% block head %} | |
| 7 | 7 | {% endblock %} |