feat(api): streamline generated app authoring
Move the canonical examples toward generated component-root helpers, typed form decoding, async/state handler registration, and derive-driven app/component registry wiring. Tighten requirements and diagnostics for the server-first, selectorless authoring path. Verified with cargo run -p slhx-xtask -- test, cargo check --workspace, redgate list, redgate refs, redgate health --strict, and git diff --check. req: canonical/001 req: canonical/003 req: canonical/004 req: dx/002 req: derive_app/001 req: component/003 req: form/004 req: axum_integration/003
This commit is contained in:
+91
-17
@@ -3,18 +3,89 @@
|
||||
//! Most application code should depend on this crate, use the proc-macros from
|
||||
//! here, and import generated resources through `#[slhx::surface]`.
|
||||
|
||||
pub use slhx_core::*;
|
||||
use slhx_core::{Effect, KeyedSlot, SafeHtml, Slot};
|
||||
|
||||
pub use slhx_core::{
|
||||
navigate, push, redirect, replace, Atom, ComponentRef, CssClass, CssClasses, EventName, Form,
|
||||
FormContract, FormControlKind, FormError, FormField, FormModel, FormValue, FromForm,
|
||||
GeneratedTarget, Handle, IntoEffect, ParamName,
|
||||
};
|
||||
pub use slhx_derive::{app, component, form, handler, surface};
|
||||
|
||||
pub fn render(view: &impl hemplate::Hemplate) -> SafeHtml {
|
||||
let mut html = String::new();
|
||||
view.render_into(&mut html)
|
||||
.expect("hemplate view renders into slhx effect payload");
|
||||
SafeHtml::trusted(html)
|
||||
/// Advanced/raw slhx primitives used by generated code, integrations, and tests.
|
||||
///
|
||||
/// Beginner-facing application code should prefer generated targets, generated
|
||||
/// handles/forms/classes, `Html`, `IntoEffect`, and tuple composition. req: dx/001 req: public_api/005
|
||||
pub mod advanced {
|
||||
pub use slhx_core::*;
|
||||
}
|
||||
|
||||
/// Rendered, checked HTML produced by hemplate/slhx rendering helpers.
|
||||
///
|
||||
/// Raw trusted HTML construction remains an advanced boundary; beginner-facing
|
||||
/// code should receive `Html` values from generated render helpers. req: public_api/005
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Html(SafeHtml);
|
||||
|
||||
impl Html {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
self.0.into_string()
|
||||
}
|
||||
|
||||
pub fn join(fragments: impl IntoIterator<Item = Html>) -> Self {
|
||||
Self(SafeHtml::join(fragments.into_iter().map(Into::into)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Html> for SafeHtml {
|
||||
fn from(value: Html) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Html {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Html {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn render_html(view: &impl hemplate::Hemplate) -> SafeHtml {
|
||||
pub mod __private {
|
||||
use super::{Html, SafeHtml};
|
||||
|
||||
pub fn html_trusted(value: impl Into<String>) -> Html {
|
||||
Html(SafeHtml::trusted(value))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(view: &impl hemplate::Hemplate) -> Html {
|
||||
let mut html = String::new();
|
||||
view.render_into(&mut html)
|
||||
.expect("hemplate view renders into slhx effect payload");
|
||||
__private::html_trusted(html)
|
||||
}
|
||||
|
||||
/// A hemplate partial that carries the stable key for a generated keyed target.
|
||||
///
|
||||
/// Generated keyed target helpers use this to keep ordinary handler code at the
|
||||
/// level of `ui::row.replace(row)` instead of `ui::row.replace(row.id, &row)`.
|
||||
/// req: canonical_authoring/003 req: codegen/002
|
||||
pub trait KeyedPartial {
|
||||
fn slhx_key(&self) -> String;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn render_html(view: &impl hemplate::Hemplate) -> Html {
|
||||
render(view)
|
||||
}
|
||||
|
||||
@@ -42,7 +113,7 @@ impl<T> RenderSlotExt for Slot<T> {
|
||||
|
||||
/// Compatibility shim for raw keyed slot rendering.
|
||||
///
|
||||
/// Prefer generated target objects such as `targets::row.append(key, &view)` so
|
||||
/// Prefer generated target objects such as `targets::row.append(&view)` so
|
||||
/// generated resource lowering stays attached to the view boundary. req: dx/006
|
||||
#[doc(hidden)]
|
||||
pub trait RenderKeyedSlotExt<K> {
|
||||
@@ -90,12 +161,12 @@ where
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use slhx_core::{
|
||||
navigate, push, redirect, replace, Atom, BuildFingerprint, ComponentRef, CssClass,
|
||||
CssClasses, Effect, EventName, Form, FormModel, FormValue, Handle, IntoEffect, KeyedSlot,
|
||||
ParamName, SafeHtml, Slot,
|
||||
};
|
||||
pub use crate::render;
|
||||
pub use crate::Html;
|
||||
pub use slhx_core::{
|
||||
navigate, push, redirect, replace, Atom, ComponentRef, CssClass, CssClasses, EventName,
|
||||
Form, FormModel, FormValue, Handle, IntoEffect, ParamName,
|
||||
};
|
||||
pub use slhx_derive::{app, component, form, handler, surface};
|
||||
}
|
||||
|
||||
@@ -114,15 +185,18 @@ mod tests {
|
||||
fn render_is_the_short_safe_html_helper() {
|
||||
// req: dx/006 req: html_safety/002
|
||||
assert_eq!(crate::render(&InlineView).as_str(), "<strong>ok</strong>");
|
||||
assert_eq!(crate::render_html(&InlineView).as_str(), "<strong>ok</strong>");
|
||||
assert_eq!(
|
||||
crate::render_html(&InlineView).as_str(),
|
||||
"<strong>ok</strong>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prelude_exports_safe_html_for_page_composition() {
|
||||
// req: dx/006 req: html_safety/002
|
||||
fn prelude_exports_html_for_page_composition() {
|
||||
// req: dx/006 req: public_api/005 req: html_safety/002
|
||||
use crate::prelude::*;
|
||||
|
||||
let html = SafeHtml::join([render(&InlineView)]);
|
||||
let html = Html::join([render(&InlineView)]);
|
||||
|
||||
assert_eq!(html.as_str(), "<strong>ok</strong>");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user