Skip to main content

max / makenotwork

Declare the team and policy documents Four shapes. The classes on the body, the disclosure's shape, the founder's row, every section heading, all seven other-policy rows and both contact addresses are what the tests already asserted, and none of them moved. `OTHER` gains named members. A description names what it draws and `.1` is not a name, which is the same reason `Section` had them already.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01MptwXZ8k65v19rFmdGAyki
Author: Max Johnson <me@maxj.phd> · 2026-09-03 17:36 UTC
Signed with PGP, not checked
Commit: 1e12d197c80c7ab83cf9506daba04baaebcd4298
Parent: d1c93ad
4 files changed, +136 insertions, -126 deletions
@@ -40,8 +40,6 @@
40 40 //! page's layout rather than about the control.
41 41
42 42 use quasi_declare::declare;
43 - use quasi_router::Node;
44 - use quasi_router::screen::Act;
45 43
46 44 declare! {
47 45 /// The route each export posts to, and the name the file is kept under.
@@ -39,10 +39,8 @@
39 39 //! against six others by hand.
40 40
41 41 use makeover_layout as layout;
42 - use quasi_router::screen::{Act, Row};
43 - use quasi_router::{
44 - Action, Document, Node, RegionKind, Request, Response, RouteError, Screen as Described, Slot,
45 - };
42 + use quasi_declare::declare;
43 + use quasi_router::{Document, Request, Response, RouteError};
46 44 use quasi_webview::Webview;
47 45
48 46 /// The address, registered whole. See [`super::public_document_mount`].
@@ -120,43 +118,53 @@
120 118 },
121 119 ];
122 120
121 + /// One of the other policy documents.
122 + ///
123 + /// Named members rather than a tuple, for the reason [`Section`] has them: a
124 + /// description names what it draws, and `.1` is not a name.
125 + struct Other {
126 + title: &'static str,
127 + covers: &'static str,
128 + route: &'static str,
129 + }
130 +
123 131 /// The other policy documents: a title, what it covers, and where it lives.
124 - const OTHER: &[(&str, &str, &str)] = &[
125 - (
126 - "Terms of Service",
127 - "What you agree to by using Makenotwork",
128 - "/docs/terms-of-service",
129 - ),
130 - (
131 - "Privacy Policy",
132 - "What we collect, why, and how to exercise your rights",
133 - "/docs/privacy-policy",
134 - ),
135 - (
136 - "Payments & Refunds",
137 - "Merchant-of-record model, refunds, chargebacks",
138 - "/docs/payments",
139 - ),
140 - (
141 - "Acceptable Use",
142 - "Specific behaviour that gets accounts suspended",
143 - "/docs/acceptable-use",
144 - ),
145 - (
146 - "Copyright & DMCA",
147 - "How takedowns and counter-notifications work",
148 - "/docs/copyright",
149 - ),
150 - (
151 - "Appeals",
152 - "How to challenge a moderation decision",
153 - "/docs/appeals",
154 - ),
155 - (
156 - "Mailing List Data Processing",
157 - "Who answers a subscriber's request about a project mailing list",
158 - "/docs/mailing-list-data-processing",
159 - ),
132 + const OTHER: &[Other] = &[
133 + Other {
134 + title: "Terms of Service",
135 + covers: "What you agree to by using Makenotwork",
136 + route: "/docs/terms-of-service",
137 + },
138 + Other {
139 + title: "Privacy Policy",
140 + covers: "What we collect, why, and how to exercise your rights",
141 + route: "/docs/privacy-policy",
142 + },
143 + Other {
144 + title: "Payments & Refunds",
145 + covers: "Merchant-of-record model, refunds, chargebacks",
146 + route: "/docs/payments",
147 + },
148 + Other {
149 + title: "Acceptable Use",
150 + covers: "Specific behaviour that gets accounts suspended",
151 + route: "/docs/acceptable-use",
152 + },
153 + Other {
154 + title: "Copyright & DMCA",
155 + covers: "How takedowns and counter-notifications work",
156 + route: "/docs/copyright",
157 + },
158 + Other {
159 + title: "Appeals",
160 + covers: "How to challenge a moderation decision",
161 + route: "/docs/appeals",
162 + },
163 + Other {
164 + title: "Mailing List Data Processing",
165 + covers: "Who answers a subscriber's request about a project mailing list",
166 + route: "/docs/mailing-list-data-processing",
167 + },
160 168 ];
161 169
162 170 /// The page. Reads nothing.
@@ -164,43 +172,46 @@
164 172 Ok(page_screen().into())
165 173 }
166 174
167 - /// The whole document: the title, the measure, the body.
168 - fn page_screen() -> Described {
169 - let mut page = Slot::new(PAGE_REGION, RegionKind::Pane)
170 - .with(Node::page("Content Policy"))
171 - .with(Node::text(
172 - "Makenotwork exists so creators can sell their work on fair terms. This policy \
173 - describes what belongs here, what doesn't, and how we handle problems.",
174 - ));
175 + declare! {
176 + /// The whole document: the title, the measure, the body.
177 + shape page_screen() -> Screen;
175 178
176 - for section in SECTIONS {
177 - page = page
178 - .with(Node::section(section.heading))
179 - .with(super::own_prose(section.body));
179 + screen single "Content Policy - Makenotwork" {
180 + measured MEASURE;
181 + documented Document::default().classed(crate::shell::body_class(MEASURE, &["policy-page"]));
182 + summarised "What belongs on Makenotwork, what doesn't, and how problems are handled.";
183 +
184 + region PAGE_REGION as Pane {
185 + page "Content Policy";
186 + text "Makenotwork exists so creators can sell their work on fair terms. This policy \
187 + describes what belongs here, what doesn't, and how we handle problems.";
188 +
189 + for part in SECTIONS {
190 + section part.heading;
191 + include super::own_prose(part.body);
192 + }
193 +
194 + section "Other Policies";
195 + list {
196 + for other in OTHER {
197 + row other.title {
198 + secondary other.covers;
199 + act "Read" to get other.route navigating;
200 + }
201 + }
202 + }
203 +
204 + section "Questions";
205 + include super::own_prose(QUESTIONS);
206 + }
180 207 }
181 -
182 - page = page
183 - .with(Node::section("Other Policies"))
184 - .with(Node::list(OTHER.iter().map(|(title, covers, route)| {
185 - Row::new(*title)
186 - .secondary(*covers)
187 - .act(Act::new("Read", Action::get(*route).navigating()))
188 - })));
189 -
190 - page = page.with(Node::section("Questions")).with(super::own_prose(
191 - "If something's unclear or you want to check before posting, reach out at \
192 - **policy@makenot.work**.",
193 - ));
194 -
195 - Described::single("Content Policy - Makenotwork")
196 - .measured(MEASURE)
197 - .documented(
198 - Document::default().classed(crate::shell::body_class(MEASURE, &["policy-page"])),
199 - )
200 - .summarised("What belongs on Makenotwork, what doesn't, and how problems are handled.")
201 - .with(page)
202 208 }
203 209
210 + /// The closing paragraph, a const so the declaration reads as one sentence
211 + /// rather than as a paragraph in an argument.
212 + const QUESTIONS: &str = "If something's unclear or you want to check before posting, reach out \
213 + at **policy@makenot.work**.";
214 +
204 215 /// The document this screen is drawn in. Same shape as the other public
205 216 /// documents: the skip link and the site header, whose user is optional here.
206 217 #[must_use]
@@ -273,10 +284,10 @@
273 284 fn every_other_policy_keeps_its_row_and_its_route() {
274 285 let html = html();
275 286
276 - for (title, _, route) in OTHER {
277 - let escaped = crate::helpers::escape_html(title);
278 - assert!(html.contains(&escaped), "{title} missing");
279 - assert!(html.contains(route), "{route} missing");
287 + for other in OTHER {
288 + let escaped = crate::helpers::escape_html(other.title);
289 + assert!(html.contains(&escaped), "{} missing", other.title);
290 + assert!(html.contains(other.route), "{} missing", other.route);
280 291 }
281 292 }
282 293
@@ -41,10 +41,8 @@
41 41 //! [`the_bio_is_a_disclosure_and_not_a_stepper`]: tests::the_bio_is_a_disclosure_and_not_a_stepper
42 42
43 43 use makeover_layout as layout;
44 - use quasi_router::screen::{Act, Row};
45 - use quasi_router::{
46 - Action, Document, Node, RegionKind, Request, Response, RouteError, Screen as Described, Slot,
47 - };
44 + use quasi_declare::declare;
45 + use quasi_router::{Document, Request, Response, RouteError};
48 46 use quasi_webview::Webview;
49 47
50 48 /// The address, registered whole. See [`super::public_document_mount`].
@@ -67,52 +65,56 @@
67 65 Ok(page_screen().into())
68 66 }
69 67
70 - /// The whole document: the title, the measure, the body.
71 - fn page_screen() -> Described {
72 - Described::single("Team - Makenotwork")
73 - .measured(MEASURE)
74 - .documented(Document::default().classed(crate::shell::body_class(MEASURE, &["team-page"])))
75 - .summarised("Small by design: who builds Makenotwork, and what they did before.")
76 - .with(
77 - Slot::new(PAGE_REGION, RegionKind::Pane)
78 - .with(Node::page("Team"))
79 - .with(Node::text(
80 - "Small by design. The base platform was designed by the founder, who intends \
81 - to stay as the technical lead through the project's growth.",
82 - ))
83 - .with(Node::section("Founder"))
84 - .with(Node::list([founder()]))
85 - .with(bio()),
86 - )
68 + declare! {
69 + /// The whole document: the title, the measure, the body.
70 + shape page_screen() -> Screen;
71 +
72 + screen single "Team - Makenotwork" {
73 + measured MEASURE;
74 + documented Document::default().classed(crate::shell::body_class(MEASURE, &["team-page"]));
75 + summarised "Small by design: who builds Makenotwork, and what they did before.";
76 +
77 + region PAGE_REGION as Pane {
78 + page "Team";
79 + text "Small by design. The base platform was designed by the founder, who intends \
80 + to stay as the technical lead through the project's growth.";
81 + section "Founder";
82 + list [founder()];
83 + include bio();
84 + }
85 + }
87 86 }
88 87
89 - /// The one card on the page.
90 - ///
91 - /// A row rather than a grid of one: `use-case-grid-2col` held a single article,
92 - /// so the grid was describing an intention rather than a layout. When there is a
93 - /// second person the list grows a second row and the design system decides how
94 - /// two rows sit, which is the arrangement question answered once for every
95 - /// screen instead of in this page's own CSS.
96 - fn founder() -> Row {
97 - Row::new("Max")
98 - .secondary("CEO & Tech Lead")
99 - .meta("Designed and built the platform.")
100 - .act(Act::new("@max", Action::get("/u/max").navigating()))
88 + declare! {
89 + /// The one card on the page.
90 + ///
91 + /// A row rather than a grid of one: `use-case-grid-2col` held a single article,
92 + /// so the grid was describing an intention rather than a layout. When there is a
93 + /// second person the list grows a second row and the design system decides how
94 + /// two rows sit, which is the arrangement question answered once for every
95 + /// screen instead of in this page's own CSS.
96 + shape founder() -> Row;
97 +
98 + row "Max" {
99 + secondary "CEO & Tech Lead";
100 + meta "Designed and built the platform.";
101 + act "@max" to get "/u/max" navigating;
102 + }
101 103 }
102 104
103 - /// The bio, closed until asked for.
104 - ///
105 - /// `showing_at_most_one(None)` is closed; `Some(0)` would be `<details open>`.
106 - fn bio() -> Node {
107 - Node::Region(
108 - Slot::new(BIO, RegionKind::Group)
109 - .with(Node::Region(
110 - Slot::new(BIO_BODY, RegionKind::Pane)
111 - .label("Bio")
112 - .with(Node::text(BIO_TEXT)),
113 - ))
114 - .showing_at_most_one(None),
115 - )
105 + declare! {
106 + /// The bio, closed until asked for.
107 + ///
108 + /// `showing_at_most_one None` is closed; `Some(0)` would be `<details open>`.
109 + shape bio() -> Node;
110 +
111 + region BIO as Group {
112 + showing_at_most_one None;
113 + region BIO_BODY as Pane {
114 + label "Bio";
115 + text BIO_TEXT;
116 + }
117 + }
116 118 }
117 119
118 120 /// Carried over verbatim from the template. Prose the founder wrote, so it is
@@ -39,7 +39,6 @@
39 39 //! whenever the files tab converts.
40 40
41 41 use quasi_declare::declare;
42 - use quasi_router::screen::Act;
43 42
44 43 declare! {
45 44 /// The delete button for one version's row.