Skip to main content

max / makenotwork

8.1 KB · 197 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_router::screen::{Act, Row};
45 use quasi_router::{
46 Action, Document, Node, RegionKind, Request, Response, RouteError, Screen as Described, Slot,
47 };
48 use quasi_webview::Webview;
49
50 /// The address, registered whole. See [`super::public_document_mount`].
51 pub const PATH: &str = "/team";
52
53 /// The page's own region, and what the skip link points at.
54 pub const PAGE_REGION: &str = "team";
55
56 /// The disclosure that holds the founder's bio.
57 const BIO: &str = "founder-bio";
58
59 /// The one labelled frame inside it, which is what makes it a disclosure rather
60 /// than a one-slide carousel.
61 const BIO_BODY: &str = "founder-bio-body";
62
63 const MEASURE: layout::Measure = layout::Measure::Wide;
64
65 /// The page, which reads nothing and so takes no round trip.
66 pub fn screen(_viewer: &super::Viewer, _request: Request) -> Result<Response, RouteError> {
67 Ok(page_screen().into())
68 }
69
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 )
87 }
88
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()))
101 }
102
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 )
116 }
117
118 /// Carried over verbatim from the template. Prose the founder wrote, so it is
119 /// content rather than copy and nothing here rewrites it.
120 const BIO_TEXT: &str = "I started in the creator space as a young kid making backgrounds and \
121 montages for CoD MW2 players. I picked up programming around the same time, helping Minecraft \
122 server owners with whatever they needed. I spent the years after that focused on my \
123 education: a BA in mathematics at the University of Chicago, then a PhD in mathematics at UC \
124 San Diego. Afterward I decided to start my own business, and here we are.";
125
126 /// The document this screen is drawn in.
127 ///
128 /// The head, the tail and the token meta come off
129 /// [`super::Viewer::document_shell`]. What is added is what every public page on
130 /// this site opens with: the skip link, and the site header, which reads the
131 /// viewer's user through an `Option` because on this mount there may not be one.
132 #[must_use]
133 pub fn renderer(viewer: &super::Viewer) -> Webview {
134 Webview::new().with_shell(viewer.document_shell().with_body_first(format!(
135 "{}{}",
136 crate::shell::skip_link(PAGE_REGION),
137 crate::shell::site_header(viewer.user.as_ref()),
138 )))
139 }
140
141 #[cfg(test)]
142 mod tests {
143 use super::*;
144
145 fn html() -> String {
146 use quasi_axum::Serves as _;
147
148 Webview::new().screen(&page_screen())
149 }
150
151 /// `2790e5c4` for this screen. The template wrote the measure on the body
152 /// and `team-page` on a container div inside it; a described document has no
153 /// container, so both land on the body and the CSS measure moved onto
154 /// `.team-page main` the way `/feed`'s did. Same two classes, one element.
155 #[test]
156 fn the_document_carries_the_classes_the_template_carried() {
157 let screen = page_screen();
158
159 assert_eq!(
160 screen.document.body_class.as_deref(),
161 Some("padded-page team-page")
162 );
163 assert!(html().contains("padded-page team-page"), "{}", html());
164 }
165
166 /// The disclosure shape, asserted by what it renders rather than by what it
167 /// says: a label on the outer slot instead of the inner one is a legal
168 /// carousel, so the text is identical and the markup is not.
169 #[test]
170 fn the_bio_is_a_disclosure_and_not_a_stepper() {
171 let html = html();
172
173 assert!(html.contains(r#"aria-expanded="false""#), "{html}");
174 assert!(!html.contains(r#"data-shows="next""#), "{html}");
175 assert!(html.contains("University of Chicago"), "{html}");
176 }
177
178 /// The founder's row says who, what, and where to read more.
179 #[test]
180 fn the_founder_row_links_to_the_profile() {
181 let html = html();
182
183 assert!(html.contains("CEO &amp; Tech Lead"), "{html}");
184 assert!(html.contains(r#"href="/u/max""#), "{html}");
185 }
186
187 /// `736f45a5`: this screen's markup carries none of the four spellings.
188 #[test]
189 fn the_page_spells_no_spinner() {
190 let html = html();
191
192 for spelling in ["htmx-indicator", "spinner", "loading-text", "loading-state"] {
193 assert!(!html.contains(spelling), "{spelling} survives in {html}");
194 }
195 }
196 }
197