From 4a8442c5f9c830e25d4aef27c43c59ba96cae2e9 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Fri, 12 Jun 2026 07:18:38 +0200 Subject: [PATCH] feat(build): generate page-boundary helper Expose generated ui::page as the canonical page-boundary rendering helper and move the Workout golden path and docs off beginner-visible ui::render calls. req: public_api/002 req: codegen/002 req: canonical_authoring/006 --- README.md | 2 +- docs/recipes/deploy-versioning.md | 6 +++--- docs/versioning.md | 4 ++-- examples/workout/src/lib.rs | 4 ++-- hemx-build/src/lib.rs | 10 ++++++++++ 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e0c16fb..1e84036 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ For beginner and production-shaped app code, stay on this path. req: public_api/ ## Mental model: render → slot/key → effect → runtime - **Render:** hemplate renders Rust view structs into checked HTML. App code - normally reaches rendering through generated helpers or `hemx::page(...)` at a + normally reaches rendering through generated `ui::page(...)` helpers at a server page boundary, not raw HTML construction. req: html_safety/002 req: view/001 - **Slot/key:** a generated slot names the target, and a generated keyed slot also carries the stable row key. A list target inside `h-for` must have a diff --git a/docs/recipes/deploy-versioning.md b/docs/recipes/deploy-versioning.md index 3276f15..4425289 100644 --- a/docs/recipes/deploy-versioning.md +++ b/docs/recipes/deploy-versioning.md @@ -99,9 +99,9 @@ persistence or cross-version addressing contract. req: abi/005 For v1, document changes in three buckets: - **Beginner API:** generated helpers, `#[hemx::app]`, `#[hemx::component]`, - `#[hemx::handler]`, `#[hemx::form]`, `hemx::page`, tuple `IntoEffect`, and - `Result` mapping. Breaking changes require a major version - or an explicit migration note. + `#[hemx::handler]`, `#[hemx::form]`, generated page-boundary rendering, tuple + `IntoEffect`, and `Result` mapping. Breaking changes + require a major version or an explicit migration note. - **Wire/runtime ABI:** EffectBatch schema, runtime ABI version, and fingerprint inputs. Incompatible changes must bump ABI versions and fail closed at runtime. - **Advanced escape hatches:** raw effects, manual registries, low-level ids, diff --git a/docs/versioning.md b/docs/versioning.md index 098fa4a..cb1fb24 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -15,8 +15,8 @@ changes: - `#[hemx::surface]`, `#[hemx::app]`, `#[hemx::component]`, `#[hemx::handler]`, and `#[hemx::form]` - generated component helpers for slots, keyed partials, forms, handles, page - targets, class tokens, and events -- `hemx::page(...)` for page-boundary rendering + targets, page-boundary rendering, class tokens, and events +- `hemx::page(...)` only at explicit server shell boundaries - tuple `IntoEffect` composition - `Result` handlers with `IntoHandlerFailure` - generated form commands such as `clear`, `reset`, `error`, and `focus` diff --git a/examples/workout/src/lib.rs b/examples/workout/src/lib.rs index 95f830a..0d70000 100644 --- a/examples/workout/src/lib.rs +++ b/examples/workout/src/lib.rs @@ -627,7 +627,7 @@ pub struct AppShell { } pub fn page(runtime_src: &'static str, state: &WorkoutState) -> Html { - ui::render(&AppShell { + ui::page(&AppShell { runtime_src, body: render(state), }) @@ -683,7 +683,7 @@ pub fn view(state: &WorkoutState) -> Workout { } pub fn render(state: &WorkoutState) -> Html { - ui::render(&view(state)) + ui::page(&view(state)) } #[derive(Clone, Debug)] diff --git a/hemx-build/src/lib.rs b/hemx-build/src/lib.rs index 8964496..626fc73 100644 --- a/hemx-build/src/lib.rs +++ b/hemx-build/src/lib.rs @@ -743,6 +743,9 @@ impl Resources { "{pad} ::hemx::__private::html_trusted(lower(html))\n" )); out.push_str(&format!("{pad}}}\n\n")); + out.push_str(&format!( + "{pad}pub fn page(view: &impl ::hemplate::Hemplate) -> ::hemx::Html {{ render(view) }}\n\n" + )); out.push_str(&format!("{pad}#[doc(hidden)]\n")); out.push_str(&format!("{pad}pub fn render_html(view: &impl ::hemplate::Hemplate) -> ::hemx::Html {{ render(view) }}\n\n")); out.push_str(&format!("{pad}#[doc(hidden)]\n")); @@ -1733,6 +1736,7 @@ mod tests { assert!( generated.contains("pub fn render(view: &impl ::hemplate::Hemplate) -> ::hemx::Html") ); + assert!(generated.contains("pub fn page(view: &impl ::hemplate::Hemplate) -> ::hemx::Html")); assert!(generated.contains( "#[doc(hidden)]\npub fn render_html(view: &impl ::hemplate::Hemplate) -> ::hemx::Html" )); @@ -1845,6 +1849,9 @@ mod tests { assert!( generated.contains("\npub fn render(view: &impl ::hemplate::Hemplate) -> ::hemx::Html") ); + assert!( + generated.contains("\npub fn page(view: &impl ::hemplate::Hemplate) -> ::hemx::Html") + ); assert!(generated.contains("\n#[doc(hidden)]\npub fn render_html(view: &impl ::hemplate::Hemplate) -> ::hemx::Html")); assert!(generated.contains("\npub fn put(slot: ::hemx::advanced::Slot, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect")); assert!(generated.contains("\npub fn append(slot: ::hemx::advanced::KeyedSlot, key: K, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect")); @@ -1858,6 +1865,9 @@ mod tests { assert!(generated.contains(" pub const todos: SlotTarget<::std::string::String> = SlotTarget::new(super::advanced::slots::todos);")); assert!(generated.contains(" pub use self::targets::todos;")); assert!(generated.contains(" #[doc(hidden)]\n pub fn lower_html")); + assert!( + generated.contains(" pub fn page(view: &impl ::hemplate::Hemplate) -> ::hemx::Html") + ); assert!(generated.contains(" #[doc(hidden)]\n pub fn render_html(view: &impl ::hemplate::Hemplate) -> ::hemx::Html")); assert!( generated.contains(" pub fn static_fragment(html: &'static str) -> ::hemx::Html")