feat(examples): derive saas dashboard template

Replace the SaaS tutorial dashboard's hand-written Hemplate implementation with the real hemplate-derived dashboard template. This closes the tutorial skeleton shortcut and keeps the provider-light production app path on generated/derived rendering.

req: examples/001

req: canonical_authoring/001

req: view/001
This commit is contained in:
slhx agent
2026-06-05 09:22:52 +02:00
parent 38cfb263aa
commit d37480fb72
+2 -37
View File
@@ -7,7 +7,7 @@ use hemx_axum::{
interactions, Form, HandlerErrorContext, HandlerFailure, IntoHandlerFailure, Registry, State, interactions, Form, HandlerErrorContext, HandlerFailure, IntoHandlerFailure, Registry, State,
}; };
use std::convert::Infallible; use std::convert::Infallible;
use std::fmt::{Display, Write as _}; use std::fmt::Display;
use std::str::FromStr; use std::str::FromStr;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -174,6 +174,7 @@ impl IntoHandlerFailure for AppError {
} }
} }
#[derive(Hemplate)]
pub struct Dashboard { pub struct Dashboard {
csrf: CsrfToken, csrf: CsrfToken,
flash: String, flash: String,
@@ -203,42 +204,6 @@ pub struct ProjectRow {
owner: String, owner: String,
} }
impl Hemplate for Dashboard {
fn render_into(&self, buf: &mut String) -> Result<(), hemplate::error::HemplateError> {
buf.push_str("<section class=\"dashboard\" data-hemx-root=\"dashboard\" data-hemx-sse=\"/events\">\n");
buf.push_str(" <header class=\"hero\"><p class=\"eyebrow\">Production-shaped SaaS path</p><h1>Projects</h1><p class=\"lede\">Auth-gated mutations, CSRF checks, local persistence, typed forms, generated swaps, page swaps, live status, plain CSS, and one explicit island.</p></header>\n");
buf.push_str(" <nav class=\"tabs\" data-hemx-slot=\"nav\"><a href=\"/\" data-hemx-nav=\"\">Projects</a><button type=\"button\" data-hemx-handle=\"open_settings\">Settings</button></nav>\n");
buf.push_str(" <section class=\"panel\" data-hemx-slot=\"page_panel\">\n");
buf.push_str(" <form class=\"project-form\" data-hemx-handle=\"create_project\" data-hemx-form=\"new_project\" data-hemx-disable-while-pending>\n");
write!(
buf,
" <input type=\"hidden\" name=\"csrf\" value=\"{}\">\n",
hemplate::HtmlEscape(&self.csrf.to_string())
)?;
buf.push_str(" <label>Project name <input name=\"name\" required=\"required\" maxlength=\"64\" value=\"Launch checklist\"></label><button type=\"submit\">Create project</button><p class=\"field-error\" data-hemx-error-for=\"name\"></p>\n </form>\n");
write!(
buf,
" <p class=\"flash\" data-hemx-slot=\"flash\">{}</p>\n",
hemplate::HtmlEscape(&self.flash)
)?;
write!(
buf,
" <p class=\"summary\" data-hemx-slot=\"summary\">{}</p>\n",
hemplate::HtmlEscape(&self.summary)
)?;
buf.push_str(" <ul class=\"project-list\" data-hemx-slot=\"project_row\">\n");
for row in &self.rows {
buf.push_str(" ");
row.render_into(buf)?;
buf.push('\n');
}
buf.push_str(" </ul>\n </section>\n <section class=\"status-row\"><p data-hemx-slot=\"live_status\">Waiting for status…</p>");
write!(buf, "<article class=\"metrics-island\" data-hemx-island=\"metrics\" data-project-count=\"{}\"><h2>Metrics island</h2><canvas width=\"320\" height=\"140\" aria-label=\"Project metrics chart\"></canvas><p data-island-readout=\"\">Waiting for island script…</p></article>", self.project_count)?;
buf.push_str("</section>\n</section>\n");
Ok(())
}
}
impl From<ProjectRecord> for ProjectRow { impl From<ProjectRecord> for ProjectRow {
fn from(record: ProjectRecord) -> Self { fn from(record: ProjectRecord) -> Self {
Self { Self {