//! The Askama entry point for a described "delete this blog post" button. //! //! Shape 8's first conversion that did not have to wait for a screen //! (`b279b9eb`), and the second thing the glue-module ruling bought //! (`27d5e5b8`, reasoning in [`super::widgets`]). The same button was written //! twice, in `templates/partials/tabs/project_content.html` and //! `templates/partials/tabs/project_blog.html`, over the same route with the //! same prompt, in two tabs of the same dashboard. Described once, called from //! both. //! //! **One caller now.** The blog tab was dead — a route no markup reached, kept //! only because it held the second copy of this table — and it was deleted at //! the `project_content` flip (`6077d0d9`, 2026-08-26). So the duplication this //! module was written to end is gone by subtraction as well as by description. //! That is not a reason to inline it back: `project_content.html`'s own copy //! becomes a described element in its turn, and this is where it goes. //! //! # What the description says that the markup did not //! //! That the act is destructive, and what to ask before doing it. //! [`Act::confirm`] is that, and `hx-confirm` was it: one of the 50 sites the //! shape exists to move. The difference is where the sentence lives. A prompt //! in an attribute is a fact about one template; a prompt on the act is a fact //! about the call, so a terminal asks y/n and a touch host raises an action //! sheet without either being told about this button. //! //! `Tone::Danger` for the same reason, and it is worth knowing it only started //! working on 2026-08-21: until `35756077` landed the app's unlayered //! `button { }` rule took the tone off every described act, so this would have //! rendered as an ordinary button that claimed to be dangerous. The templates //! said it with `class="danger-text"`, which is the same claim in a spelling no //! other renderer can read. //! //! # Why the row id is a parameter //! //! The answer replaces the row it was pressed in, and `/api/blog/{id}` is a //! plain API route the description layer does not serve — it answers an //! `HX-Trigger` toast and an empty body — so it cannot name what it changed and //! the control has to. See [`Action::replaces`]: this is exactly the case that //! member exists for, rather than the misuse it warns about. //! //! Both templates already give the row a real id (`post-row-{id}`), which is //! what makes these two describable when their neighbours are not. The Remove //! buttons in `partials/link_row.html` and `partials/tag.html` carry //! `hx-target="closest .link-row"` and `hx-target="closest .tag"`, and a //! relative selector is not something [`Action::replaces`] can say; the tag one //! additionally reads `×` and gets its name from `aria-label`, which is //! quasicoherent `aad33ecc`. Both are left hand-written and filed rather than //! approximated. use makeover_layout::Tone; use quasi_router::screen::Act; use quasi_router::{Action, Node}; /// The delete button for one post's row. /// /// `id` is the blog post's, and it names both the route and the row: the two /// are the same fact, so the caller states it once. #[must_use] pub fn html(id: &str) -> String { use quasi_axum::Serves as _; let act = Act::new( "Delete", Action::delete(format!("/api/blog/{id}")).replacing(format!("post-row-{id}")), ) .tone(Tone::Danger) .confirm("Delete this blog post?"); quasi_webview::Webview::new().fragment(&Node::Act(act)) } #[cfg(test)] mod tests { /// The three things the two templates said between them, said once: where /// it goes, what it replaces, and that it is destructive. #[test] fn a_delete_names_its_route_its_row_and_its_prompt() { let html = super::html("42"); assert!(html.contains(r#"hx-delete="/api/blog/42""#), "{html}"); assert!(html.contains(r##"hx-target="#post-row-42""##), "{html}"); assert!( html.contains(r#"hx-confirm="Delete this blog post?""#), "{html}" ); } /// The tone, which is the half that could not survive before `35756077`. /// `danger-text` in a class attribute said this to the stylesheet only. #[test] fn a_delete_says_it_is_dangerous_in_a_word_every_renderer_reads() { let html = super::html("42"); assert!(html.contains(r#"data-tone="danger""#), "{html}"); } }