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
This commit is contained in:
slhx agent
2026-06-12 07:18:38 +02:00
parent 176259afd6
commit 4a8442c5f9
5 changed files with 18 additions and 8 deletions
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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<impl IntoEffect, E>` mapping. Breaking changes require a major version
or an explicit migration note.
`#[hemx::handler]`, `#[hemx::form]`, generated page-boundary rendering, tuple
`IntoEffect`, and `Result<impl IntoEffect, E>` 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,
+2 -2
View File
@@ -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<impl IntoEffect, E>` handlers with `IntoHandlerFailure`
- generated form commands such as `clear`, `reset`, `error`, and `focus`
+2 -2
View File
@@ -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)]
+10
View File
@@ -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<T>(slot: ::hemx::advanced::Slot<T>, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect"));
assert!(generated.contains("\npub fn append<K, T>(slot: ::hemx::advanced::KeyedSlot<K, T>, 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")