feat(axum): add safe html page composition

Add a SafeHtml PageRequest path for shell/partial composition and migrate examples so rendered hemplate fragments stay typed through the transport boundary instead of round-tripping through unchecked strings.

req: axum_integration/001

req: html_safety/001

req: html_safety/002
This commit is contained in:
slhx agent
2026-06-01 23:45:52 +02:00
parent a287d87e38
commit 4e47b470a7
6 changed files with 96 additions and 49 deletions
+10 -13
View File
@@ -223,7 +223,7 @@ async fn main() {
async fn home(State(state): State<Arc<Shared>>, request: PageRequest) -> impl IntoResponse {
let demo = state.demo.lock().unwrap().clone();
request
.page(page_html(&demo), shell)
.page_html(page_html(&demo), shell)
.title("slhx Techdemo")
.fingerprint(ui::BUILD_FINGERPRINT)
}
@@ -238,7 +238,7 @@ async fn architecture(request: PageRequest) -> impl IntoResponse {
island_snapshot: "2|3|21|architecture route · same opaque island bridge".to_owned(),
});
request
.page(body, shell)
.page_html(body, shell)
.title("slhx Architecture")
.fingerprint(ui::BUILD_FINGERPRINT)
}
@@ -446,8 +446,7 @@ fn island_snapshot(demo: &DemoState) -> String {
)
}
fn page_html(demo: &DemoState) -> String {
// Explicit full-page composition boundary for already-rendered hemplate fragments.
fn page_html(demo: &DemoState) -> SafeHtml {
// req: html_safety/002 req: view/001
render_control_center(ControlCenter {
hero: render_hero(demo),
@@ -458,10 +457,9 @@ fn page_html(demo: &DemoState) -> String {
})
}
fn shell(body: String) -> String {
// Explicit full-page shell composition boundary for already-rendered hemplate fragments.
// req: html_safety/002 req: view/001 req: component/003
ui::app_shell::render(&AppShell { body: SafeHtml::trusted(body) }).into_string()
fn shell(body: SafeHtml) -> SafeHtml {
// req: html_safety/001 req: html_safety/002 req: axum_integration/001 req: component/003
ui::app_shell::render(&AppShell { body })
}
fn hero_view(demo: &DemoState) -> HeroMetrics {
@@ -580,12 +578,11 @@ fn architecture_activity() -> SafeHtml {
ui::render(&ArchitectureActivity)
}
fn render_control_center(page: ControlCenter) -> String {
fn render_control_center(page: ControlCenter) -> SafeHtml {
// req: html_safety/002 req: view/001 req: component/003
ui::render(&page).into_string()
ui::render(&page)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -599,7 +596,7 @@ mod tests {
#[test]
fn shell_is_composed_by_a_hemplate_view() {
let html = shell(page_html(&DemoState::default()));
let document = Html::parse_document(&html);
let document = Html::parse_document(html.as_str());
assert_eq!(
document
.select(&selector("title"))
@@ -624,7 +621,7 @@ mod tests {
assert!(!html.contains("__INSPECTOR__"));
assert!(!html.contains("__ACTIVITY__"));
let document = Html::parse_fragment(&html);
let document = Html::parse_fragment(html.as_str());
assert_eq!(document.select(&selector("[data-slhx-root=\"techdemo\"]")).count(), 1);
assert!(document.select(&selector("[data-sid]")).count() >= 7);
assert!(document.select(&selector("[data-hid]")).count() >= 7);