Skip to main content

max / makenotwork

4.3 KB · 98 lines History Blame Raw
1 //! The Askama entry point for a described "delete this blog post" button.
2 //!
3 //! Shape 8's first conversion that did not have to wait for a screen
4 //! (`b279b9eb`), and the second thing the glue-module ruling bought
5 //! (`27d5e5b8`, reasoning in [`super::widgets`]). The same button was written
6 //! twice, in `templates/partials/tabs/project_content.html` and
7 //! `templates/partials/tabs/project_blog.html`, over the same route with the
8 //! same prompt, in two tabs of the same dashboard. Described once, called from
9 //! both.
10 //!
11 //! **One caller now.** The blog tab was dead — a route no markup reached, kept
12 //! only because it held the second copy of this table — and it was deleted at
13 //! the `project_content` flip (`6077d0d9`, 2026-08-26). So the duplication this
14 //! module was written to end is gone by subtraction as well as by description.
15 //! That is not a reason to inline it back: `project_content.html`'s own copy
16 //! becomes a described element in its turn, and this is where it goes.
17 //!
18 //! # What the description says that the markup did not
19 //!
20 //! That the act is destructive, and what to ask before doing it.
21 //! [`Act::confirm`] is that, and `hx-confirm` was it: one of the 50 sites the
22 //! shape exists to move. The difference is where the sentence lives. A prompt
23 //! in an attribute is a fact about one template; a prompt on the act is a fact
24 //! about the call, so a terminal asks y/n and a touch host raises an action
25 //! sheet without either being told about this button.
26 //!
27 //! `Tone::Danger` for the same reason, and it is worth knowing it only started
28 //! working on 2026-08-21: until `35756077` landed the app's unlayered
29 //! `button { }` rule took the tone off every described act, so this would have
30 //! rendered as an ordinary button that claimed to be dangerous. The templates
31 //! said it with `class="danger-text"`, which is the same claim in a spelling no
32 //! other renderer can read.
33 //!
34 //! # Why the row id is a parameter
35 //!
36 //! The answer replaces the row it was pressed in, and `/api/blog/{id}` is a
37 //! plain API route the description layer does not serve — it answers an
38 //! `HX-Trigger` toast and an empty body — so it cannot name what it changed and
39 //! the control has to. See [`Action::replaces`]: this is exactly the case that
40 //! member exists for, rather than the misuse it warns about.
41 //!
42 //! Both templates already give the row a real id (`post-row-{id}`), which is
43 //! what makes these two describable when their neighbours are not. The Remove
44 //! buttons in `partials/link_row.html` and `partials/tag.html` carry
45 //! `hx-target="closest .link-row"` and `hx-target="closest .tag"`, and a
46 //! relative selector is not something [`Action::replaces`] can say; the tag one
47 //! additionally reads `×` and gets its name from `aria-label`, which is
48 //! quasicoherent `aad33ecc`. Both are left hand-written and filed rather than
49 //! approximated.
50
51 use makeover_layout::Tone;
52 use quasi_router::screen::Act;
53 use quasi_router::{Action, Node};
54
55 /// The delete button for one post's row.
56 ///
57 /// `id` is the blog post's, and it names both the route and the row: the two
58 /// are the same fact, so the caller states it once.
59 #[must_use]
60 pub fn html(id: &str) -> String {
61 use quasi_axum::Serves as _;
62
63 let act = Act::new(
64 "Delete",
65 Action::delete(format!("/api/blog/{id}")).replacing(format!("post-row-{id}")),
66 )
67 .tone(Tone::Danger)
68 .confirm("Delete this blog post?");
69
70 quasi_webview::Webview::new().fragment(&Node::Act(act))
71 }
72
73 #[cfg(test)]
74 mod tests {
75 /// The three things the two templates said between them, said once: where
76 /// it goes, what it replaces, and that it is destructive.
77 #[test]
78 fn a_delete_names_its_route_its_row_and_its_prompt() {
79 let html = super::html("42");
80
81 assert!(html.contains(r#"hx-delete="/api/blog/42""#), "{html}");
82 assert!(html.contains(r##"hx-target="#post-row-42""##), "{html}");
83 assert!(
84 html.contains(r#"hx-confirm="Delete this blog post?""#),
85 "{html}"
86 );
87 }
88
89 /// The tone, which is the half that could not survive before `35756077`.
90 /// `danger-text` in a class attribute said this to the stylesheet only.
91 #[test]
92 fn a_delete_says_it_is_dangerous_in_a_word_every_renderer_reads() {
93 let html = super::html("42");
94
95 assert!(html.contains(r#"data-tone="danger""#), "{html}");
96 }
97 }
98