Skip to main content

max / makenotwork

Serve /policy from a residual derived at build time The first screen on the seam (quasicoherent `793d99dd`), and the screen phase 2's sharp test was set against: **`/policy` compiles to a single `&'static str`**, 4879 bytes of it, asserted in `quasi::residuals::tests::the_policy_page_is_one_literal`. What made that reachable was two things landing together. The page's copy left Rust for `content/policy.toml`, so the five section bodies are literals rather than reads of a `const`. And `own_prose` is `#[constant]`, so each of the six prose includes is evaluated once while the residual is derived instead of building a `Node` and running docengine on every request. The closing paragraph went into the content file too: a `const` is a path and not a literal, so leaving it in Rust would have cost the fold for one sentence. `export-residuals` writes `src/quasi/residuals/compiled.rs`. A residual is produced by rendering, so this crate's own build script cannot make one; a binary that links the crate can. What it writes is Rust source the next build compiles. The staleness check is a test rather than a hook, because a residual goes stale for two reasons and a hook catches one. A screen changing shows up in the diff beside it. quasi-webview changing does not: under the tree's `[patch]` block a renderer edit in another repo leaves every committed residual a version behind with no file here modified. `cargo test` re-derives against the renderer actually linked, so it sees both. `/policy` mounts itself rather than going through the adapter, whose job is to call a handler that answers a `Screen` and render it. There is no `Screen` to answer with here, and an `Outcome` carrying markup would put an opaque string in the vocabulary for every screen to reach for. The other six public documents are mounted the old way, unchanged, which is the coexistence the transition said it would have.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01P8ostB2UmZJGj5WjSHRSot
Author: Max Johnson <me@maxj.phd> · 2026-09-07 20:10 UTC
Signed with PGP, not checked
Commit: a0465a157e73bf3eacf4efd0c2cab47edd664ba8
Parent: 3f76c93
8 files changed, +392 insertions, -69 deletions
M server/Cargo.lock +30 -30
@@ -2875,7 +2875,7 @@
2875 2875 checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
2876 2876 dependencies = [
2877 2877 "libc",
2878 - "windows-sys 0.61.2",
2878 + "windows-sys 0.52.0",
2879 2879 ]
2880 2880
2881 2881 [[package]]
@@ -3660,7 +3660,7 @@
3660 3660 checksum = "1a791e6620676a875f362f3156ed213e73ca099a09bf992c18812abe65cc37b1"
3661 3661 dependencies = [
3662 3662 "bstr",
3663 - "hashbrown 0.17.1",
3663 + "hashbrown 0.15.5",
3664 3664 ]
3665 3665
3666 3666 [[package]]
@@ -4811,7 +4811,7 @@
4811 4811 source = "registry+https://github.com/rust-lang/crates.io-index"
4812 4812 checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd"
4813 4813 dependencies = [
4814 - "windows-sys 0.61.2",
4814 + "windows-sys 0.59.0",
4815 4815 ]
4816 4816
4817 4817 [[package]]
@@ -5719,7 +5719,7 @@
5719 5719 source = "registry+https://github.com/rust-lang/crates.io-index"
5720 5720 checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
5721 5721 dependencies = [
5722 - "windows-sys 0.61.2",
5722 + "windows-sys 0.59.0",
5723 5723 ]
5724 5724
5725 5725 [[package]]
@@ -6633,7 +6633,7 @@
6633 6633
6634 6634 [[package]]
6635 6635 name = "quasi-axum"
6636 - version = "0.102.0"
6636 + version = "0.103.0"
6637 6637 dependencies = [
6638 6638 "axum",
6639 6639 "http 1.4.2",
@@ -6644,7 +6644,7 @@
6644 6644
6645 6645 [[package]]
6646 6646 name = "quasi-basics"
6647 - version = "0.102.0"
6647 + version = "0.103.0"
6648 6648 dependencies = [
6649 6649 "makeover-layout",
6650 6650 "quasi-declare",
@@ -6653,7 +6653,7 @@
6653 6653
6654 6654 [[package]]
6655 6655 name = "quasi-declare"
6656 - version = "0.1.10"
6656 + version = "0.1.11"
6657 6657 dependencies = [
6658 6658 "proc-macro2",
6659 6659 "quote",
@@ -6663,7 +6663,7 @@
6663 6663
6664 6664 [[package]]
6665 6665 name = "quasi-http"
6666 - version = "0.102.0"
6666 + version = "0.103.0"
6667 6667 dependencies = [
6668 6668 "form_urlencoded",
6669 6669 "http 1.4.2",
@@ -6672,7 +6672,7 @@
6672 6672
6673 6673 [[package]]
6674 6674 name = "quasi-router"
6675 - version = "0.102.0"
6675 + version = "0.103.0"
6676 6676 dependencies = [
6677 6677 "makeover-layout",
6678 6678 ]
@@ -6695,7 +6695,7 @@
6695 6695
6696 6696 [[package]]
6697 6697 name = "quasi-webview"
6698 - version = "0.102.0"
6698 + version = "0.103.0"
6699 6699 dependencies = [
6700 6700 "docengine",
6701 6701 "makeover-layout",
@@ -7347,7 +7347,7 @@
7347 7347 "errno",
7348 7348 "libc",
7349 7349 "linux-raw-sys 0.12.1",
7350 - "windows-sys 0.61.2",
7350 + "windows-sys 0.52.0",
7351 7351 ]
7352 7352
7353 7353 [[package]]
@@ -7427,7 +7427,7 @@
7427 7427 "security-framework 3.7.0",
7428 7428 "security-framework-sys",
7429 7429 "webpki-root-certs",
7430 - "windows-sys 0.61.2",
7430 + "windows-sys 0.52.0",
7431 7431 ]
7432 7432
7433 7433 [[package]]
@@ -8553,7 +8553,7 @@
8553 8553 "getrandom 0.4.3",
8554 8554 "once_cell",
8555 8555 "rustix 1.1.4",
8556 - "windows-sys 0.61.2",
8556 + "windows-sys 0.52.0",
8557 8557 ]
8558 8558
8559 8559 [[package]]
@@ -9979,7 +9979,7 @@
9979 9979 source = "registry+https://github.com/rust-lang/crates.io-index"
9980 9980 checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
9981 9981 dependencies = [
9982 - "windows-sys 0.61.2",
9982 + "windows-sys 0.48.0",
9983 9983 ]
9984 9984
9985 9985 [[package]]
@@ -10767,22 +10767,6 @@
10767 10767 "pkg-config",
10768 10768 ]
10769 10769
10770 - [[patch.unused]]
10771 - name = "quasi-immediate"
10772 - version = "0.102.0"
10773 -
10774 - [[patch.unused]]
10775 - name = "quasi-notifs"
10776 - version = "0.102.0"
10777 -
10778 - [[patch.unused]]
10779 - name = "quasi-store"
10780 - version = "0.1.0"
10781 -
10782 - [[patch.unused]]
10783 - name = "quasi-tauri"
10784 - version = "0.102.0"
10785 -
10786 10770 [[patch.unused]]
10787 10771 name = "kberg"
10788 10772 version = "0.1.0"
@@ -10802,3 +10786,19 @@
10802 10786 [[patch.unused]]
10803 10787 name = "synckit-config"
10804 10788 version = "0.2.0"
10789 +
10790 + [[patch.unused]]
10791 + name = "quasi-immediate"
10792 + version = "0.103.0"
10793 +
10794 + [[patch.unused]]
10795 + name = "quasi-notifs"
10796 + version = "0.103.0"
10797 +
10798 + [[patch.unused]]
10799 + name = "quasi-store"
10800 + version = "0.1.0"
10801 +
10802 + [[patch.unused]]
10803 + name = "quasi-tauri"
10804 + version = "0.103.0"
@@ -163,7 +163,7 @@
163 163 # through quasi beside its Askama equivalent, so the cost of describing a screen
164 164 # here is measured rather than argued. Not load-bearing for any shipped route.
165 165 # See wiki look-wave-2, tier G.
166 - quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.102" }
166 + quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.103" }
167 167 # The declared form. A screen written as `declare!` is compiled to the same
168 168 # `quasi_router` constructors the hand-written shape called, at build time, so
169 169 # nothing new reaches the binary and the macro is not in the request path.
@@ -183,13 +183,13 @@
183 183 # For the request head the per-viewer state factory reads. axum re-exports it,
184 184 # but the factory's signature is quasi-axum's and names `http::request::Parts`.
185 185 http = "1.3.1"
186 - quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.102" }
187 - quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.102" }
186 + quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.103" }
187 + quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.103" }
188 188 # The first-party widget set. The carousel was proved here against three pages
189 189 # and then moved there; what is left in `quasi/widgets/carousel.rs` is the
190 190 # Askama glue. Not `makeover-basics`: a widget is an assembly of `Node`s, so it
191 191 # sits above quasi-router rather than inside the description suite.
192 - quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.102" }
192 + quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.103" }
193 193
194 194 # Git source browser
195 195 gix = { version = "0.86", default-features = false, features = ["blame", "mailmap", "revision", "sha1", "max-performance-safe"] }
@@ -250,6 +250,13 @@
250 250 name = "export-openapi"
251 251 path = "src/bin/export-openapi.rs"
252 252
253 + # Writes every staged screen's residual to src/quasi/residuals.rs. A residual is
254 + # derived by rendering, so it cannot come from this crate's own build script:
255 + # a build script cannot link the crate it is building. A binary can.
256 + [[bin]]
257 + name = "export-residuals"
258 + path = "src/bin/export-residuals.rs"
259 +
253 260 [[bin]]
254 261 name = "export-operational-mail-doc"
255 262 path = "src/bin/export-operational-mail-doc.rs"
@@ -281,7 +288,7 @@
281 288 # Written from the crate rather than copied into the repo for the reason the
282 289 # constants exist: a stale copy in a static directory is a silently dead bulk
283 290 # bar, and the version has to be the one the emitter agrees with.
284 - quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.102" }
291 + quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.103" }
285 292
286 293 [dev-dependencies]
287 294 tower = { version = "0.5.3", features = ["util"] }
@@ -104,3 +104,11 @@
104 104 title = "Mailing List Data Processing"
105 105 covers = "Who answers a subscriber's request about a project mailing list"
106 106 route = "/docs/mailing-list-data-processing"
107 +
108 + # The closing paragraph. One entry rather than a scalar because the format has
109 + # one shape and a second would be a second thing to learn; it is read the same
110 + # way the sections are.
111 + [[closing]]
112 + body = '''
113 + If something's unclear or you want to check before posting, reach out at **policy@makenot.work**.
114 + '''
@@ -82,6 +82,7 @@
82 82 pub mod project_tabs;
83 83 pub mod promo_code_acts;
84 84 pub mod repo_acts;
85 + pub mod residuals;
85 86 pub mod rich_field;
86 87 pub mod schedule_field;
87 88 pub mod session_acts;
@@ -671,10 +672,7 @@
671 672 use_cases::PATH,
672 673 public_document_mount(app, use_cases::PATH, use_cases::screen, use_cases::renderer),
673 674 ),
674 - (
675 - policy::PATH,
676 - public_document_mount(app, policy::PATH, policy::screen, policy::renderer),
677 - ),
675 + (policy::PATH, policy::mount(app)),
678 676 (
679 677 fan_plus::PATH,
680 678 public_document_mount(app, fan_plus::PATH, fan_plus::screen, fan_plus::renderer),
@@ -839,6 +837,7 @@
839 837 /// cannot carry markup. A creator's description reaching a screen through
840 838 /// the database is `Node::rich` however well-behaved it has been.
841 839 #[must_use]
840 + #[constant]
842 841 pub shape own_prose(source: impl Into<String>) -> Node;
843 842
844 843 rich source {
@@ -1042,8 +1041,17 @@
1042 1041 .split_once("\n}")
1043 1042 .expect("public_document_mounts ends")
1044 1043 .0;
1044 + // Two mount styles, and both are public documents. Most screens go
1045 + // through the adapter, which calls a handler that answers a `Screen`
1046 + // and renders it. A screen on the residual seam has no `Screen` to
1047 + // answer with, so it brings its own mount; `policy` is the first
1048 + // (quasicoherent `793d99dd`). Counting only the first style would let a
1049 + // screen leave `PUBLIC_DOCUMENT_PATHS` unnoticed by moving onto the
1050 + // seam, which is the drift this test exists to catch.
1051 + let adapted = mounted.matches("public_document_mount(").count();
1052 + let own = mounted.matches("::mount(app)").count();
1045 1053 assert_eq!(
1046 - mounted.matches("public_document_mount(").count(),
1054 + adapted + own,
1047 1055 PUBLIC_DOCUMENT_PATHS.len(),
1048 1056 "public_document_mounts and PUBLIC_DOCUMENT_PATHS describe the same screens"
1049 1057 );
@@ -52,7 +52,11 @@
52 52 //! emitted, loop and all. Copy the macro can read is copy the macro can write
53 53 //! out, which is what leaves the page foldable to a literal later.
54 54 //!
55 - //! What did not move is [`QUESTIONS`], which is one sentence and not a table.
55 + //! The closing paragraph went too, and it was a `const` here until this pass.
56 + //! Keeping it in Rust would have cost the fold: a `const` is a path and not a
57 + //! literal, so an `include` of it opens a staged scope and the sentence gets
58 + //! built and rendered per request. The spelling device it existed for is worth
59 + //! less than that.
56 60
57 61 use makeover_layout as layout;
58 62 use quasi_declare::declare;
@@ -68,49 +72,113 @@
68 72 const MEASURE: layout::Measure = layout::Measure::Wide;
69 73
70 74 /// The page. Reads nothing.
75 + ///
76 + /// Kept beside the residual mount rather than replaced by it, and it is what
77 + /// the residual is checked against: `quasi::residuals` asserts that filling the
78 + /// compiled one gives back exactly what building and rendering this gives. A
79 + /// screen with no second way to produce its markup has nothing to check the
80 + /// first one with.
71 81 pub fn screen(_viewer: &super::Viewer, _request: Request) -> Result<Response, RouteError> {
72 82 Ok(page_screen().into())
73 83 }
74 84
85 + /// The address, served from the residual rather than from a tree.
86 + ///
87 + /// The first screen on the seam (quasicoherent `793d99dd`). Its regions were
88 + /// derived on a build machine and are a `&'static str` in this binary, so
89 + /// answering is writing the document around them: no `Node` is constructed and
90 + /// nothing is rendered.
91 + ///
92 + /// **Its own mount rather than [`super::public_document_mount`]**, because the
93 + /// adapter's whole job is to call a handler that answers a `Screen` and render
94 + /// it. There is no `Screen` here to answer with, and inventing an `Outcome`
95 + /// that carries markup would put an opaque string in the vocabulary for every
96 + /// screen to reach for. The two paths coexist by construction, which is what
97 + /// the transition said they would: every other public document is still mounted
98 + /// the old way on the line above this one.
99 + pub fn mount(app: &crate::AppState) -> axum::Router {
100 + let app = app.clone();
101 + axum::Router::new().route(
102 + PATH,
103 + axum::routing::get(move |parts: axum::http::request::Parts| {
104 + let app = app.clone();
105 + async move { document(&app, &parts).await }
106 + }),
107 + )
108 + }
109 +
110 + /// The document, around markup that is already written.
111 + async fn document(
112 + app: &crate::AppState,
113 + parts: &axum::http::request::Parts,
114 + ) -> axum::response::Response {
115 + use axum::response::IntoResponse as _;
116 +
117 + // `Audience::Anyone` never refuses a reader for being signed out, so the
118 + // error arm here is the session layer or the CSRF store failing, which is
119 + // this server being broken rather than this page being unreachable. Same
120 + // answer the adapter gives every other screen for the same failure.
121 + let Ok(viewer) = super::viewer_factory(app.clone(), super::Audience::Anyone)(parts).await
122 + else {
123 + return axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response();
124 + };
125 + let body = super::residuals::POLICY
126 + .settled()
127 + .expect("the policy page reads nothing, so its residual is one literal");
128 +
129 + axum::response::Html(renderer(&viewer).served(&page_screen(), body)).into_response()
130 + }
131 +
75 132 declare! {
76 133 /// The whole document: the title, the measure, the body.
77 - shape page_screen() -> Screen;
134 + pub(crate) shape page_screen() -> Screen;
78 135
79 136 screen single "Content Policy - Makenotwork" {
80 137 measured MEASURE;
81 138 documented Document::default().classed(crate::shell::body_class(MEASURE, &["policy-page"]));
82 139 summarised "What belongs on Makenotwork, what doesn't, and how problems are handled.";
83 140
84 - region PAGE_REGION as Pane {
85 - page "Content Policy";
86 - text "Makenotwork exists so creators can sell their work on fair terms. This policy \
87 - describes what belongs here, what doesn't, and how we handle problems.";
88 -
89 - for part in copy "content/policy.toml" as sections {
90 - section part.heading;
91 - include super::own_prose(part.body);
92 - }
93 -
94 - section "Other Policies";
95 - list {
96 - for other in copy "content/policy.toml" as others {
97 - row other.title {
98 - secondary other.covers;
99 - act "Read" to get other.route navigating;
100 - }
101 - }
102 - }
103 -
104 - section "Questions";
105 - include super::own_prose(QUESTIONS);
106 - }
141 + include page_region();
107 142 }
108 143 }
109 144
110 - /// The closing paragraph, a const so the declaration reads as one sentence
111 - /// rather than as a paragraph in an argument.
112 - const QUESTIONS: &str = "If something's unclear or you want to check before posting, reach out \
113 - at **policy@makenot.work**.";
145 + declare! {
146 + /// The page's one region, split out so it can be staged.
147 + ///
148 + /// `#[staged]` wants a shape whose residual is derived by rendering it, and
149 + /// the document around it is not derivable: the shell carries the site
150 + /// header, which says whether anybody is signed in. So the split is exactly
151 + /// where the request stops mattering. Everything below this line is the
152 + /// same for every reader on every request, and everything above it is not.
153 + #[staged]
154 + pub(crate) shape page_region() -> Slot;
155 +
156 + region PAGE_REGION as Pane {
157 + page "Content Policy";
158 + text "Makenotwork exists so creators can sell their work on fair terms. This policy \
159 + describes what belongs here, what doesn't, and how we handle problems.";
160 +
161 + for part in copy "content/policy.toml" as sections {
162 + section part.heading;
163 + include super::own_prose(part.body);
164 + }
165 +
166 + section "Other Policies";
167 + list {
168 + for other in copy "content/policy.toml" as others {
169 + row other.title {
170 + secondary other.covers;
171 + act "Read" to get other.route navigating;
172 + }
173 + }
174 + }
175 +
176 + section "Questions";
177 + for closing in copy "content/policy.toml" as closing {
178 + include super::own_prose(closing.body);
179 + }
180 + }
181 + }
114 182
115 183 /// The document this screen is drawn in. Same shape as the other public
116 184 /// documents: the skip link and the site header, whose user is optional here.
@@ -1,0 +1,50 @@
1 + //! Write every staged screen's residual to `src/quasi/residuals.rs`.
2 + //!
3 + //! The build-time half of the declaration transition (quasicoherent
4 + //! `793d99dd`). A residual is a screen's markup with the request taken out of
5 + //! it, and it is produced by *rendering*: `quasi_webview::stage::derive` calls
6 + //! the screen's staged twin and reads the spans off what the renderer emits. So
7 + //! it cannot be produced by this crate's own build script, which cannot link
8 + //! the crate it is building. It is produced here instead, by a binary that
9 + //! links the crate the ordinary way, and what it writes is Rust source the next
10 + //! build compiles.
11 + //!
12 + //! Rust source and not a serialization format. A format would need a writer, a
13 + //! reader, a version and a parse at startup, and the parse is exactly what the
14 + //! spike's template intermediate was rejected for. There is no format here: the
15 + //! artifact is code, `rustc` is its only reader, and what ships is literals in
16 + //! read-only data.
17 + //!
18 + //! `quasi::residuals::tests::every_committed_residual_matches_a_fresh_one`
19 + //! fails when this output is stale, so the regeneration step is:
20 + //!
21 + //! ```sh
22 + //! cargo run --bin export-residuals
23 + //! ```
24 + //!
25 + //! A test rather than only a pre-commit hook, because a residual goes stale for
26 + //! two different reasons and a hook catches one of them. A screen changing is
27 + //! visible in the diff; **quasi-webview changing is not**, and under the tree's
28 + //! `[patch]` block a renderer edit in another repo silently makes every
29 + //! committed residual one version behind. `cargo test` runs against the
30 + //! renderer actually linked, so it sees both.
31 +
32 + use std::io::Write as _;
33 +
34 + fn main() -> std::io::Result<()> {
35 + let source = makenotwork::quasi::residuals::generate();
36 +
37 + if std::env::args().any(|argument| argument == "--stdout") {
38 + std::io::stdout().write_all(source.as_bytes())?;
39 + return Ok(());
40 + }
41 +
42 + let path = concat!(
43 + env!("CARGO_MANIFEST_DIR"),
44 + "/src/quasi/residuals/compiled.rs"
45 + );
46 + let mut file = std::fs::File::create(path)?;
47 + file.write_all(source.as_bytes())?;
48 + println!("wrote {path}");
49 + Ok(())
50 + }
@@ -1,0 +1,170 @@
1 + //! Every staged screen's residual, derived at build time and compiled.
2 + //!
3 + //! <!-- wiki: quasi-overview -->
4 + //!
5 + //! A residual is a screen's markup with the request taken out of it: literals
6 + //! where the renderer decided, and a hole, a branch or a loop where a request
7 + //! does. Serving a screen from one is walking it and writing values into the
8 + //! gaps, so no `Node` is built and nothing is rendered.
9 + //!
10 + //! # Why this is generated rather than derived at startup
11 + //!
12 + //! A residual is produced by rendering, so a build script cannot make one: it
13 + //! would have to link the crate it is building. The two honest answers were to
14 + //! derive at boot into a `LazyLock`, or to generate. Generating won on three
15 + //! counts and Max ruled it (quasicoherent `793d99dd`, 2026-09-07):
16 + //!
17 + //! - **The artifact is readable and diffable.** A screen's markup is in the
18 + //! repo, in `residuals/compiled.rs`, where a change to it shows up in a diff
19 + //! rather than in a running process.
20 + //! - **Nothing happens at boot, and nothing can fail there.** `derive` panics
21 + //! loudly when a screen's branches do not nest, deliberately. At startup that
22 + //! is a boot failure; here it is a failed test.
23 + //! - **What ships is compiled.** The generated file is a `static` built from
24 + //! literals, so the strings are in read-only data and the tree that holds them
25 + //! is built by rustc. A `LazyLock` would still allocate on first use.
26 + //!
27 + //! # The staleness that is easy to miss
28 + //!
29 + //! A residual goes stale for two reasons and only one of them is visible. A
30 + //! screen changing shows up in the diff beside it. **quasi-webview changing
31 + //! does not**: the residual is that renderer's own output, and under the tree's
32 + //! `[patch]` block an edit in another repo silently leaves every committed
33 + //! residual one version behind, with no file here modified.
34 + //!
35 + //! So the check is a test rather than a pre-commit hook. [`generate`] is run by
36 + //! `cargo test` against the renderer actually linked, and its result is compared
37 + //! with what is committed. Regenerate with:
38 + //!
39 + //! ```sh
40 + //! cargo run --bin export-residuals
41 + //! ```
42 +
43 + mod compiled;
44 +
45 + pub use compiled::*;
46 +
47 + use quasi_router::Node;
48 + use quasi_router::stage::{Plan, Residual};
49 + use quasi_webview::Webview;
50 +
51 + /// One screen that serves from a residual: the `static` it gets, and the staged
52 + /// twin a derivation calls.
53 + type Staged = (&'static str, fn(&Plan) -> Node);
54 +
55 + /// Every screen on the seam.
56 + ///
57 + /// A list rather than a registry the screens add themselves to. A screen
58 + /// reaches the serving path by being named here, which is one place to read to
59 + /// know what is on the seam and what is still building a tree, and the
60 + /// alternative was a macro nobody can grep.
61 + fn roster() -> Vec<Staged> {
62 + vec![("POLICY", |plan| {
63 + Node::Region(super::policy::page_region_staged(plan))
64 + })]
65 + }
66 +
67 + /// The residual of one staged screen, derived by rendering it now.
68 + ///
69 + /// The reference the committed file is checked against, and what the generator
70 + /// writes. Both call this, so a generated residual and a freshly derived one
71 + /// cannot be produced two different ways.
72 + #[must_use]
73 + pub fn derive(shape: fn(&Plan) -> Node) -> Residual {
74 + quasi_webview::stage::derive(&Webview::new(), shape)
75 + }
76 +
77 + /// The whole generated module, as Rust source.
78 + #[must_use]
79 + pub fn generate() -> String {
80 + let mut out = String::from(
81 + "//! Generated by `cargo run --bin export-residuals`. Do not edit.\n\
82 + //!\n\
83 + //! Every staged screen's markup with the request taken out of it, as a\n\
84 + //! `static` the compiler builds. See the module above this one for why\n\
85 + //! this is generated rather than derived at startup, and for the test\n\
86 + //! that fails when it is stale.\n\n",
87 + );
88 + for (name, shape) in roster() {
89 + out.push_str(&derive(shape).as_rust(name));
90 + out.push('\n');
91 + }
92 + out
93 + }
94 +
95 + #[cfg(test)]
96 + mod tests {
97 + use super::*;
98 +
99 + /// The committed file says what this renderer says today.
100 + ///
101 + /// The whole staleness check, and it covers the case a diff cannot: a
102 + /// quasi-webview change in another repo alters what `derive` produces
103 + /// without touching a byte here.
104 + #[test]
105 + fn every_committed_residual_matches_a_fresh_one() {
106 + let committed = include_str!("residuals/compiled.rs");
107 +
108 + assert_eq!(
109 + committed,
110 + generate(),
111 + "the committed residuals are stale: run `cargo run --bin export-residuals`",
112 + );
113 + }
114 +
115 + /// `/policy` compiles to a single `&'static str`.
116 + ///
117 + /// Phase 2's sharp test (`60047dc0`), which moved here when it turned out
118 + /// to be a pipeline property rather than a vocabulary one. The screen's
119 + /// declaration holds no operator and now reaches no value a request brings:
120 + /// its copy is in `content/policy.toml` and read at macro time, and its six
121 + /// prose includes are `#[constant]`, so each is evaluated once while the
122 + /// residual is derived rather than once per request.
123 + ///
124 + /// One `Op::Lit` and nothing else is what that adds up to. A hole, a branch
125 + /// or a loop appearing here means something about the page started
126 + /// depending on the request, which is a real change and worth failing on.
127 + #[test]
128 + fn the_policy_page_is_one_literal() {
129 + let ops = POLICY.ops();
130 +
131 + assert_eq!(ops.len(), 1, "{ops:?}");
132 + assert!(matches!(ops[0], quasi_router::stage::Op::Lit(_)), "{ops:?}");
133 + }
134 +
135 + /// The residual is the document's own bytes, not a lookalike.
136 + ///
137 + /// The property the serving path rests on. A residual is derived from a
138 + /// *fragment* render of the region, and a document renders its regions
139 + /// through a different entry point; if the two disagreed by so much as an
140 + /// attribute, serving from the residual would quietly ship different markup
141 + /// than the page has always had. So the fragment has to appear in the
142 + /// document verbatim, and this is what says it does.
143 + #[test]
144 + fn what_the_residual_holds_appears_in_the_document_verbatim() {
145 + use quasi_axum::Serves as _;
146 +
147 + let document = Webview::new().screen(&crate::quasi::policy::page_screen());
148 + let body = POLICY.settled().expect("nothing in the page varies");
149 +
150 + assert!(document.contains(body), "{document}");
151 + }
152 +
153 + /// Filling the residual gives back what the renderer gives.
154 + ///
155 + /// The acceptance test the task asked for, and it is an equality rather
156 + /// than a diff: what the seam must not lose is the markup itself, so the
157 + /// check is that the two paths agree byte for byte on a screen that has
158 + /// nothing varying in it.
159 + #[test]
160 + fn the_residual_serves_what_the_renderer_serves() {
161 + use quasi_axum::Serves as _;
162 +
163 + let rendered = Webview::new().fragment(&Node::Region(crate::quasi::policy::page_region()));
164 +
165 + assert_eq!(
166 + POLICY.settled().expect("nothing in the page varies"),
167 + rendered,
168 + );
169 + }
170 + }
@@ -1,0 +1,12 @@
1 + //! Generated by `cargo run --bin export-residuals`. Do not edit.
2 + //!
3 + //! Every staged screen's markup with the request taken out of it, as a
4 + //! `static` the compiler builds. See the module above this one for why
5 + //! this is generated rather than derived at startup, and for the test
6 + //! that fails when it is stale.
7 +
8 + pub static POLICY: ::quasi_router::stage::Residual = ::quasi_router::stage::Residual::compiled(&[
9 + ::quasi_router::stage::Op::Lit(::std::borrow::Cow::Borrowed(
10 + "<div id=\"policy\" class=\"region pane\"><h1 class=\"heading\">Content Policy</h1><p class=\"text\">Makenotwork exists so creators can sell their work on fair terms. This policy describes what belongs here, what doesn&#39;t, and how we handle problems.</p><h2 class=\"heading\">What&#39;s Welcome</h2><div class=\"rich\" data-disable-scripting><p>Creative work across all supported types:</p>\n<ul>\n<li>Software, plugins, presets, and templates</li>\n<li>Audio: music, podcasts, samples, sound design</li>\n<li>Writing: articles, guides, courses, fiction</li>\n<li>Visual work: images, photography, design assets</li>\n<li>Video: tutorials, performances, documentaries</li>\n</ul>\n<p>If you made it and it's legal to distribute, it belongs here.</p>\n</div><h2 class=\"heading\">What&#39;s Not Allowed</h2><div class=\"rich\" data-disable-scripting><ul>\n<li>Content that violates applicable law</li>\n<li>Harassment, threats, or doxxing</li>\n<li>Spam, deceptive listings, or bait-and-switch pricing</li>\n<li>Malware, exploits, or tools designed to cause harm</li>\n<li>Impersonation of other creators or organizations</li>\n<li>Content you don't have the rights to distribute</li>\n</ul>\n</div><h2 class=\"heading\">How We Handle Issues</h2><div class=\"rich\" data-disable-scripting><p>During private alpha, every creator has a direct relationship with the admin. If something comes up, we talk about it. No automated takedowns, no faceless tickets.</p>\n<p>Post-alpha, we'll introduce a formal process with written notice of any policy violation, an opportunity to appeal, and continued access to data export throughout.</p>\n</div><h2 class=\"heading\">Your Rights</h2><div class=\"rich\" data-disable-scripting><ul>\n<li>Full data export is always available: your content, metadata, and transaction history</li>\n<li>If we ever moderate content or suspend an account, you'll get a clear explanation of what policy was violated</li>\n<li>You'll have the opportunity to appeal</li>\n<li>You can export your data even while suspended (excluding content we can't legally host)</li>\n</ul>\n<p>These commitments are part of our <a href=\"/docs/guarantees\" rel=\"noopener noreferrer\">creator guarantees</a>.</p>\n</div><h2 class=\"heading\">Software Downloads</h2><div class=\"rich\" data-disable-scripting><p>Makenotwork hosts downloadable software uploaded by creators. While we take steps to make sure downloads are safe:</p>\n<ul>\n<li>Creators are responsible for the safety and integrity of their uploads</li>\n<li>Users should verify downloads with antivirus software before running them</li>\n<li>We do not guarantee that any download is free of malware or other harmful content</li>\n<li>Report suspicious downloads to <strong>reports@makenot.work</strong></li>\n</ul>\n</div><h2 class=\"heading\">Other Policies</h2><ul class=\"list\"><li class=\"row\" data-row><span class=\"row-primary\">Terms of Service</span><span class=\"row-secondary\">What you agree to by using Makenotwork</span><span class=\"row-actions\"><a class=\"button\" data-act href=\"/docs/terms-of-service\">Read</a></span></li><li class=\"row\" data-row><span class=\"row-primary\">Privacy Policy</span><span class=\"row-secondary\">What we collect, why, and how to exercise your rights</span><span class=\"row-actions\"><a class=\"button\" data-act href=\"/docs/privacy-policy\">Read</a></span></li><li class=\"row\" data-row><span class=\"row-primary\">Payments &amp; Refunds</span><span class=\"row-secondary\">Merchant-of-record model, refunds, chargebacks</span><span class=\"row-actions\"><a class=\"button\" data-act href=\"/docs/payments\">Read</a></span></li><li class=\"row\" data-row><span class=\"row-primary\">Acceptable Use</span><span class=\"row-secondary\">Specific behaviour that gets accounts suspended</span><span class=\"row-actions\"><a class=\"button\" data-act href=\"/docs/acceptable-use\">Read</a></span></li><li class=\"row\" data-row><span class=\"row-primary\">Copyright &amp; DMCA</span><span class=\"row-secondary\">How takedowns and counter-notifications work</span><span class=\"row-actions\"><a class=\"button\" data-act href=\"/docs/copyright\">Read</a></span></li><li class=\"row\" data-row><span class=\"row-primary\">Appeals</span><span class=\"row-secondary\">How to challenge a moderation decision</span><span class=\"row-actions\"><a class=\"button\" data-act href=\"/docs/appeals\">Read</a></span></li><li class=\"row\" data-row><span class=\"row-primary\">Mailing List Data Processing</span><span class=\"row-secondary\">Who answers a subscriber&#39;s request about a project mailing list</span><span class=\"row-actions\"><a class=\"button\" data-act href=\"/docs/mailing-list-data-processing\">Read</a></span></li></ul><h2 class=\"heading\">Questions</h2><div class=\"rich\" data-disable-scripting><p>If something's unclear or you want to check before posting, reach out at <strong>policy@makenot.work</strong>.</p>\n</div><div class=\"anchored\" id=\"policy-anchored\" data-menu=\"anchored\" hidden></div></div>",
11 + )),
12 + ]);