Skip to main content

max / makenotwork

9.4 KB · 236 lines History Blame Raw
1 //! The team page at `/team`, described.
2 //!
3 //! The first described document this server serves to a reader who may or may
4 //! not hold a session, which is the question `b5cbb646` left open and
5 //! [`super::Audience`] answers. It replaces `templates/pages/team.html`,
6 //! `TeamTemplate` and `landing::team_page`.
7 //!
8 //! # Why this is the screen that answers the signed-out question
9 //!
10 //! The gated documents (`/feed`, `/dashboard/export`) resolve a session and
11 //! refuse without one. `/pricing` resolves nothing and takes a state built once
12 //! at startup, which is why it carries no site header: with no per-request
13 //! session there is nobody for a header to greet.
14 //!
15 //! Every remaining `pages/` template is neither. They are public, they carry
16 //! the header, and the header reads differently once a reader signs in --
17 //! `crate::shell::site_header` puts Library, Dashboard and the cart behind
18 //! `Some(user)`. A screen like that needs a per-request viewer whose user is
19 //! optional, and `/team` is the smallest of them: static content, no writes, no
20 //! reads, so the only thing being proved is the mount.
21 //!
22 //! # One behaviour this changes, and it is a fix
23 //!
24 //! `team_page` resolved its user through `MaybeUserUnverified`, which reads the
25 //! session and short-circuits a legacy one but never validates the tracking row.
26 //! [`super::viewer_factory`] calls `crate::auth::authenticate`, which does:
27 //! a revoked or suspended session renders as signed out here where it would have
28 //! kept rendering as signed in. That is the posture `auth`'s own doc recommends
29 //! ("prefer this anywhere the identity actually gates"), and the cost is one
30 //! cached session touch on a page that made no database call at all before.
31 //!
32 //! # The card is a row, and the bio is a disclosure
33 //!
34 //! `<details class="team-card-bio">` is the exact disclosure shape the wiki note
35 //! records (`mnw-server-conversion-plan`, 2026-08-26): a selective region
36 //! showing at most one, holding exactly one labelled sub-region. The near miss
37 //! -- label on the outer slot -- renders as a carousel with Prev/Next buttons
38 //! and compiles fine, so [`the_bio_is_a_disclosure_and_not_a_stepper`] asserts
39 //! the rendering rather than the text.
40 //!
41 //! [`the_bio_is_a_disclosure_and_not_a_stepper`]: tests::the_bio_is_a_disclosure_and_not_a_stepper
42
43 use makeover_layout as layout;
44 use quasi_declare::declare;
45 use quasi_router::{Document, Request, Response, RouteError};
46 use quasi_webview::Webview;
47
48 /// The address, registered whole. See [`super::public_document_mount`].
49 pub const PATH: &str = "/team";
50
51 /// The page's own region, and what the skip link points at.
52 pub const PAGE_REGION: &str = "team";
53
54 /// The disclosure that holds the founder's bio.
55 const BIO: &str = "founder-bio";
56
57 /// The one labelled frame inside it, which is what makes it a disclosure rather
58 /// than a one-slide carousel.
59 const BIO_BODY: &str = "founder-bio-body";
60
61 const MEASURE: layout::Measure = layout::Measure::Wide;
62
63 /// The page, which reads nothing and so takes no round trip.
64 pub fn screen(_viewer: &super::Viewer, _request: Request) -> Result<Response, RouteError> {
65 Ok(page_screen().into())
66 }
67
68 declare! {
69 /// The whole document: the title, the measure, the body.
70 pub(crate) 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 include page_region();
78 }
79 }
80
81 declare! {
82 /// The page's one region, split out so it can be staged.
83 ///
84 /// The split is where the request stops mattering, which on this page is
85 /// immediately: nothing below this line reads anything. See
86 /// `policy::page_region` for why the document cannot come with it.
87 #[staged]
88 pub(crate) shape page_region() -> Slot;
89
90 region PAGE_REGION as Pane {
91 page "Team";
92 text "Small by design. The base platform was designed by the founder, who intends \
93 to stay as the technical lead through the project's growth.";
94 section "Founder";
95 // A row rather than a grid of one: `use-case-grid-2col` held a single
96 // article, so the grid was describing an intention rather than a
97 // layout. When there is a second person the list grows a second row and
98 // the design system decides how two rows sit, which is the arrangement
99 // question answered once for every screen instead of in this page's own
100 // CSS.
101 //
102 // Written here rather than in a `founder()` shape of its own, and that
103 // is a staging constraint rather than a preference. `list [founder()]`
104 // is a bracket list, which is the one place the grammar calls a shape
105 // without an `include`, and the `#[constant]` fold does not reach it: a
106 // bracket also holds plain constructors (`stats [Figure::new(..)]`), so
107 // folding every all-literal call in one would demand a
108 // `Figure::new_constant` that cannot exist. One row is not worth a
109 // production, so it moved.
110 list {
111 row "Max" {
112 secondary "CEO & Tech Lead";
113 meta "Designed and built the platform.";
114 act "@max" to get "/u/max" navigating;
115 }
116 }
117 include bio();
118 }
119 }
120
121 declare! {
122 /// The bio, closed until asked for.
123 ///
124 /// `showing_at_most_one None` is closed; `Some(0)` would be `<details open>`.
125 ///
126 /// `#[constant]`: it reads nothing, so a staged `include` of it folds to
127 /// the disclosure's markup instead of opening a scope per request. The
128 /// words are in `content/team.toml` and read at macro time, so the loop is
129 /// unrolled before any AST exists and nothing here survives to a request.
130 #[constant]
131 shape bio() -> Node;
132
133 region BIO as Group {
134 showing_at_most_one None;
135 region BIO_BODY as Pane {
136 label "Bio";
137 for part in copy "content/team.toml" as bio {
138 text part.body;
139 }
140 }
141 }
142 }
143
144 /// The document this screen is drawn in.
145 ///
146 /// The head, the tail and the token meta come off
147 /// [`super::Viewer::document_shell`]. What is added is what every public page on
148 /// this site opens with: the skip link, and the site header, which reads the
149 /// viewer's user through an `Option` because on this mount there may not be one.
150 #[must_use]
151 pub fn renderer(viewer: &super::Viewer) -> Webview {
152 Webview::new().with_shell(viewer.document_shell().with_body_first(format!(
153 "{}{}",
154 crate::shell::skip_link(PAGE_REGION),
155 crate::shell::site_header(viewer.user.as_ref()),
156 )))
157 }
158
159 #[cfg(test)]
160 mod tests {
161 use super::*;
162
163 fn html() -> String {
164 use quasi_axum::Serves as _;
165
166 Webview::new().screen(&page_screen())
167 }
168
169 /// `2790e5c4` for this screen. The template wrote the measure on the body
170 /// and `team-page` on a container div inside it; a described document has no
171 /// container, so both land on the body and the CSS measure moved onto
172 /// `.team-page main` the way `/feed`'s did. Same two classes, one element.
173 #[test]
174 fn the_document_carries_the_classes_the_template_carried() {
175 let screen = page_screen();
176
177 assert_eq!(
178 screen.document.body_class.as_deref(),
179 Some("padded-page team-page")
180 );
181 assert!(html().contains("padded-page team-page"), "{}", html());
182 }
183
184 /// The disclosure shape, asserted by what it renders rather than by what it
185 /// says: a label on the outer slot instead of the inner one is a legal
186 /// carousel, so the text is identical and the markup is not.
187 #[test]
188 fn the_bio_is_a_disclosure_and_not_a_stepper() {
189 let html = html();
190
191 assert!(html.contains(r#"aria-expanded="false""#), "{html}");
192 assert!(!html.contains(r#"data-shows="next""#), "{html}");
193 assert!(html.contains("University of Chicago"), "{html}");
194 }
195
196 /// The founder's row says who, what, and where to read more.
197 #[test]
198 fn the_founder_row_links_to_the_profile() {
199 let html = html();
200
201 assert!(html.contains("CEO &amp; Tech Lead"), "{html}");
202 assert!(html.contains(r#"href="/u/max""#), "{html}");
203 }
204
205 /// The bio the content file holds is the bio on the page.
206 ///
207 /// Reads `content/team.toml` rather than holding a second copy of the
208 /// prose, for `policy`'s reason: the test asks the content file what the
209 /// page should say, so an edit to the words is an edit and not a failure,
210 /// and words that stop being rendered are.
211 #[test]
212 fn the_bio_is_the_prose_the_content_file_holds() {
213 let copy: toml::Table = include_str!("../../content/team.toml")
214 .parse()
215 .expect("the team copy is TOML");
216 let bio = copy["bio"].as_array().expect("a list of one bio");
217 assert_eq!(bio.len(), 1, "the page shows one founder");
218
219 let body = bio[0]["body"].as_str().expect("a body");
220 assert!(
221 html().contains(&crate::helpers::escape_html(body)),
222 "{body}"
223 );
224 }
225
226 /// `736f45a5`: this screen's markup carries none of the four spellings.
227 #[test]
228 fn the_page_spells_no_spinner() {
229 let html = html();
230
231 for spelling in ["htmx-indicator", "spinner", "loading-text", "loading-state"] {
232 assert!(!html.contains(spelling), "{spelling} survives in {html}");
233 }
234 }
235 }
236