Skip to main content

max / quasi

A host can put its own markup inside the shell The shell renders the document for a described screen and there was no way to reach it from anywhere else, so an app converting one screen at a time kept two heads: the renderer's for what it had described and its template's for what it had not. That is the divergence the stack exists to end, arriving through the conversion rather than through the design. Shell::document(title, body) is open + body + close. open and close stay private: the invariant they carry is that they are used as a pair, and a public open with no close is a half-written document waiting to happen.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-10 17:38 UTC
Signed with PGP, not checked
Commit: 751f49a9d94446ec4ab3b61f5d94833f61644c67
Parent: 8ab02e3
2 files changed, +60 insertions, -0 deletions
@@ -173,6 +173,25 @@
173 173 self.morph_src.is_some()
174 174 }
175 175
176 + /// A whole document with this body inside it. The body is markup, not
177 + /// escaped.
178 + ///
179 + /// For a host with markup of its own — a template it has not described yet,
180 + /// or one it never will — that still wants one head. The alternative was
181 + /// making [`open`](Self::open) and [`close`](Self::close) public, and the
182 + /// invariant those two carry is that they are used as a pair: a public
183 + /// `open` is a half-written document waiting to happen. What goes in is the
184 + /// inside of `<body>`; the `<body>` tag itself, its classes and the morph
185 + /// registration are the shell's, the same as on the described path.
186 + #[must_use]
187 + pub fn document(&self, title: &str, body: &str) -> String {
188 + let mut out = String::with_capacity(body.len() + 1024);
189 + self.open(title, &mut out);
190 + out.push_str(body);
191 + Self::close(&mut out);
192 + out
193 + }
194 +
176 195 /// Everything from `<!doctype>` to the open `<body>` tag.
177 196 pub(crate) fn open(&self, title: &str, out: &mut String) {
178 197 out.push_str("<!doctype html><html lang=\"");
@@ -1079,6 +1079,47 @@
1079 1079 assert!(html.find("name=\"a\"") < html.find("name=\"b\""));
1080 1080 }
1081 1081
1082 + #[test]
1083 + fn a_document_wraps_markup_the_renderer_did_not_write() {
1084 + // The path a host on hand-written templates takes: one head, emitted here,
1085 + // around a body it rendered itself.
1086 + let shell = Shell::default().layered(["base"]).styled("/style.css");
1087 + let html = shell.document("Console", "<main>hand-written</main>");
1088 +
1089 + assert!(html.starts_with("<!doctype html><html lang=\"en\">"));
1090 + assert!(html.contains("<title>Console</title>"));
1091 + assert!(html.contains("<main>hand-written</main>"));
1092 + assert!(html.ends_with("</body></html>"));
1093 + }
1094 +
1095 + #[test]
1096 + fn a_document_states_the_layers_before_the_sheets_like_a_screen_does() {
1097 + // The whole reason a template would take the shell. If these two paths
1098 + // disagree on layer order, the described and the Askama halves of one app
1099 + // cascade differently.
1100 + let shell = Shell::default().layered(["base"]).styled("/style.css");
1101 + let html = shell.document("Console", "<main>x</main>");
1102 +
1103 + let stmt = html.find("@layer makeover, base;").expect("the order is stated");
1104 + let sheet = html.find("/style.css").expect("the sheet is linked");
1105 + let body = html.find("<main>").expect("the body is placed");
1106 + assert!(stmt < sheet);
1107 + assert!(sheet < body);
1108 + }
1109 +
1110 + #[test]
1111 + fn a_documents_body_tag_is_the_shells_own() {
1112 + // The morph registration and the body classes are the shell's on both
1113 + // paths, so a template does not have to remember either.
1114 + let shell = Shell {
1115 + body_class: Some("console".into()),
1116 + ..Shell::default()
1117 + };
1118 + let html = shell.document("Console", "x");
1119 +
1120 + assert!(html.contains("<body hx-ext=\"morph\" class=\"console\">x</body>"));
1121 + }
1122 +
1082 1123 #[test]
1083 1124 fn a_nested_region_renders_inside_its_parent() {
1084 1125 let screen =