From a138f92b33d730ad97127fb9612e8462c93025bf Mon Sep 17 00:00:00 2001 From: slhx agent Date: Tue, 2 Jun 2026 07:21:23 +0200 Subject: [PATCH] feat(build): generate slot target objects Add generated targets:: wrapper objects so partial swaps read as target.put/append/replace while preserving existing lower-aware commands and IntoEffect wiring. req: dx/006 req: codegen/001 req: codegen/002 req: component/003 --- REQUIREMENTS.md | 6 +-- examples/kanban.md | 12 +++--- examples/kanban/README.md | 2 +- examples/kanban/src/lib.rs | 2 +- examples/kanban/src/main.rs | 8 ++-- examples/techdemo/src/lib.rs | 4 +- examples/techdemo/src/main.rs | 18 ++++----- examples/v0/README.md | 2 +- examples/v0/src/lib.rs | 8 +--- slhx-build/src/lib.rs | 56 ++++++++++++++++++++++++++-- slhx-test/tests/examples_contract.rs | 3 ++ 11 files changed, 84 insertions(+), 37 deletions(-) diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 5798e43..fc09da9 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -76,7 +76,7 @@ slhx competes with React by making frontend frameworks unnecessary for most apps 005 Error messages must explain fixes in author language, not internal language. Say “add `h-key="todo.id"` to this `h-for`”, not “missing ScopeKey for ResourceRef”. ### req: dx/006 -006 Generated resource commands are the preferred authoring API: `ui::put(slots::todo_list, &view)`, `ui::append(slots::card, key, &view)`, `slots::count.text(42)`, `atoms::user.set(user)`. The public facade exposes `render(view)` for trusted hemplate-to-`SafeHtml` page and fragment composition; generated view modules expose lower-aware `render(view)`, `put(slot, view)`, `append/prepend/replace(keyed_slot, key, view)`, `static_fragment(include_str!(...))` for prototype/static `.heml` fragments that need generated resource lowering as `SafeHtml`, plus `lower(html)` for callers that need the lowered string. `render_html(view)` and `lower_html(html)` remain doc-hidden compatibility aliases and are not beginner-prelude exports. The beginner prelude should not expose lower-level slot render shortcuts that bypass generated lowering. These return `impl IntoEffect`, `SafeHtml`, or lowered HTML at the boundary. Raw `Effect` constructors, opcodes, and `EffectWriter` remain low-level. [north_star] +006 Generated target objects are the preferred authoring API: `targets::todo_list.put(&view)`, `targets::card.append(key, &view)`, `targets::count.text(42)`, `atoms::user.set(user)`. The public facade exposes `render(view)` for trusted hemplate-to-`SafeHtml` page and fragment composition; generated view modules expose lower-aware target methods plus compatibility functions `render(view)`, `put(slot, view)`, `append/prepend/replace(keyed_slot, key, view)`, `static_fragment(include_str!(...))` for prototype/static `.heml` fragments that need generated resource lowering as `SafeHtml`, and `lower(html)` for callers that need the lowered string. `render_html(view)` and `lower_html(html)` remain doc-hidden compatibility aliases and are not beginner-prelude exports. The beginner prelude should not expose lower-level slot render shortcuts that bypass generated lowering. These return `impl IntoEffect`, `SafeHtml`, or lowered HTML at the boundary. Raw `Effect` constructors, opcodes, and `EffectWriter` remain low-level. [north_star] ### req: dx/007 007 Tuple composition of `IntoEffect` is the canonical batch syntax: `(a, b, c)` implements `IntoEffect` up to arity 12. `Effect::batch((...))` is available but not required for the happy path. @@ -223,10 +223,10 @@ a `data-*` handle param is statically known or runtime-extracted. ## codegen ### req: codegen/001 -001 `slhx_build` generates three artifacts from the generic Surface IR: (a) `slhx.generated.rs` containing ergonomic resource modules (`slots`, `handles`, `forms`, `atoms`), (b) `slhx.syms` for proc-macro validation, (c) runtime id-lowering tables. `slhx_build` interprets tool-specific conventions (`data-slhx-*`, `h-for`, `h-key`, form controls) from the Surface. [north_star] +001 `slhx_build` generates three artifacts from the generic Surface IR: (a) `slhx.generated.rs` containing ergonomic resource modules (`slots`, `targets`, `handles`, `forms`, `atoms`), (b) `slhx.syms` for proc-macro validation, (c) runtime id-lowering tables. `slhx_build` interprets tool-specific conventions (`data-slhx-*`, `h-for`, `h-key`, form controls) from the Surface. [north_star] ### req: codegen/002 -002 Generated view modules expose ergonomic resource commands: `put(slot, value)`, `append(keyed_slot, key, value)`, `prepend(keyed_slot, key, value)`, `replace(keyed_slot, key, value)`, plus typed resource constants for direct text/remove operations. Commands return `impl IntoEffect` and preserve generated lowering. +002 Generated view modules expose ergonomic target objects and resource commands: `targets::list.put(value)`, `targets::row.append(key, value)`, `targets::row.replace(key, value)`, plus compatibility commands `put(slot, value)`, `append(keyed_slot, key, value)`, `prepend(keyed_slot, key, value)`, and `replace(keyed_slot, key, value)`. Commands return `impl IntoEffect` and preserve generated lowering. ### req: codegen/003 003 Generated module `handles` exports typed constants: `Handle` where `I` is `Form`, a param type, or `()`. Users rarely reference handles directly; they are consumed by `#[slhx::handler]` for validation. diff --git a/examples/kanban.md b/examples/kanban.md index f58c6f0..dc0911a 100644 --- a/examples/kanban.md +++ b/examples/kanban.md @@ -90,9 +90,9 @@ FormSurface { slhx reads this from hemplate Surface facts and generates scoped typed resources: ```rust -use ui::board::{forms, slots}; +use ui::board::{forms, targets}; -ui::board::replace(slots::card, card.id, &CardView::from(card)); +targets::card.replace(card.id, &CardView::from(card)); forms::create_card.clear("title"); ui::render(&BoardView::from(board)); ``` @@ -135,7 +135,7 @@ pub fn create_card( app.board.update(|board| board.insert_card(form.column, card.clone())); ( - ui::board::append(slots::card, card.id, &CardView::from(card)), + targets::card.append(card.id, &CardView::from(card)), forms::create_card.clear("title"), // slhx-sync: queue atomic board state diff for sync SyncEffect::send_patch(atoms::board, Patch::insert_card(form.column, card)), @@ -233,14 +233,14 @@ pub fn apply_board_patch( Effect::broadcast( Channel::Board(app.board.id()), Effect::batch(changed_cards.into_iter().map(|c| - ui::board::replace(slots::card, c.id, &CardView::from(c)) + targets::card.replace(c.id, &CardView::from(c)) )), ), )), PatchResult::Conflict { canonical_board } => Effect::batch(( Effect::set(atoms::BOARD, canonical_board.clone()), - ui::put(slots::board, &BoardView::from(canonical_board)), + targets::board.put(&BoardView::from(canonical_board)), )), } } @@ -255,7 +255,7 @@ Server-authoritative on conflict. No Redux sagas. No React Query cache fades. ```rust #[slhx_sync::presence] pub fn user_joined(user: UserPresence) -> impl IntoEffect { - ui::board::append(slots::presence_user, user.id, &PresenceBadge::from(user)) + targets::presence_user.append(user.id, &PresenceBadge::from(user)) } ``` diff --git a/examples/kanban/README.md b/examples/kanban/README.md index 81501cd..78271b7 100644 --- a/examples/kanban/README.md +++ b/examples/kanban/README.md @@ -11,7 +11,7 @@ The example is a server-first Kanban board with: - add-card form - move-left / move-right card controls - delete-card controls -- generated resource commands for checked slot updates +- generated target objects for checked slot updates - tuple-composed `IntoEffect` / `application/slhx` responses - SSE presence updates diff --git a/examples/kanban/src/lib.rs b/examples/kanban/src/lib.rs index e250d60..63e34da 100644 --- a/examples/kanban/src/lib.rs +++ b/examples/kanban/src/lib.rs @@ -27,7 +27,7 @@ mod tests { #[test] fn kanban_board_updates_generated_slot() { fn render_board() -> impl IntoEffect { - super::ui::put(board::slots::board, &empty_board()) + board::targets::board.put(&empty_board()) } let effect = inspect(render_board()); diff --git a/examples/kanban/src/main.rs b/examples/kanban/src/main.rs index c3add90..2855f1f 100644 --- a/examples/kanban/src/main.rs +++ b/examples/kanban/src/main.rs @@ -10,7 +10,7 @@ use slhx_axum::{ InteractionRequest, PageRequest, }; use slhx_kanban_example::ui::{self, board as board_ui}; -use slhx_kanban_example::ui::board::{forms, handles, slots}; +use slhx_kanban_example::ui::board::{forms, handles, slots, targets}; use slhx_kanban_example::ui::board_card::handles as card_handles; use std::collections::BTreeMap; use std::convert::Infallible; @@ -138,13 +138,13 @@ async fn interact( // req: push/001 req: push/003 req: examples/001 async fn events(Query(params): Query>) -> impl IntoResponse { if params.contains_key("once") { - let effect = ui::put(slots::presence, &Presence { count: 1 }); + let effect = targets::presence.put(&Presence { count: 1 }); return sse(stream::iter([Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT))]).boxed()); } let batches = stream::unfold(1_u64, |count| async move { tokio::time::sleep(Duration::from_secs(4)).await; - let effect = ui::put(slots::presence, &Presence { count }); + let effect = targets::presence.put(&Presence { count }); Some((Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT)), count + 1)) }) .boxed(); @@ -206,7 +206,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { fn board_effects(board: &BoardState, notice: &'static str) -> impl IntoEffect { ( - ui::put(slots::board, &board_view(board)), + targets::board.put(&board_view(board)), slots::notice.text(notice), forms::create_card.clear("title"), ) diff --git a/examples/techdemo/src/lib.rs b/examples/techdemo/src/lib.rs index 9f573db..d0b981d 100644 --- a/examples/techdemo/src/lib.rs +++ b/examples/techdemo/src/lib.rs @@ -3,7 +3,7 @@ pub mod ui {} #[cfg(test)] mod tests { - use super::ui::control_center::{forms, handles, slots}; + use super::ui::control_center::{forms, handles, slots, targets}; use super::ui::issue_card::handles as card_handles; use super::ui::issue_lane::events as lane_events; use hemplate::Hemplate; @@ -30,7 +30,7 @@ mod tests { fn techdemo_uses_generated_slots_for_multi_target_updates() { fn update() -> impl IntoEffect { ( - super::ui::put(slots::hero_metrics, &FastMetric { label: "fast" }), + targets::hero_metrics.put(&FastMetric { label: "fast" }), slots::notice.text("typed"), ) } diff --git a/examples/techdemo/src/main.rs b/examples/techdemo/src/main.rs index 607d58a..922e97c 100644 --- a/examples/techdemo/src/main.rs +++ b/examples/techdemo/src/main.rs @@ -10,7 +10,7 @@ use slhx_axum::{ InteractionRequest, PageRequest, }; use slhx_techdemo::ui; -use slhx_techdemo::ui::control_center::{classes, forms, handles, slots}; +use slhx_techdemo::ui::control_center::{classes, forms, handles, slots, targets}; use slhx_techdemo::ui::issue_card::handles as card_handles; use slhx_techdemo::ui::issue_lane::handles as lane_handles; use std::collections::{BTreeMap, VecDeque}; @@ -269,13 +269,13 @@ async fn interact( // req: push/001 req: push/003 req: examples/001 async fn events(Query(params): Query>) -> impl IntoResponse { if params.contains_key("once") { - let effect = ui::put(slots::live_feed, &LiveFeed { tick: 1 }); + let effect = targets::live_feed.put(&LiveFeed { tick: 1 }); return sse(stream::iter([Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT))]).boxed()); } let batches = stream::unfold(1_u64, |tick| async move { tokio::time::sleep(Duration::from_secs(4)).await; - let effect = ui::put(slots::live_feed, &LiveFeed { tick }); + let effect = targets::live_feed.put(&LiveFeed { tick }); Some((Ok::<_, Infallible>(effect.into_batch(ui::BUILD_FINGERPRINT)), tick + 1)) }) .boxed(); @@ -384,10 +384,10 @@ fn registry(shared: Arc) -> impl DispatchRegistry { let mut demo = shared.demo.lock().unwrap(); demo.log("Simulated push event produced the same generated update shape"); ( - ui::put(slots::live_feed, &LiveFeed { + targets::live_feed.put(&LiveFeed { tick: demo.activity.len() as u64, }), - ui::put(slots::activity, &activity_view(&demo)), + targets::activity.put(&activity_view(&demo)), slots::notice.text("Push simulated · no client app code"), slhx::event("slhx:island-orbit", island_snapshot(&demo)), ) @@ -414,10 +414,10 @@ fn update_work(demo: &mut DemoState, id: Option, update: impl FnOnce(&mut W fn demo_effects(demo: &DemoState, notice: &'static str) -> impl IntoEffect { ( - ui::put(slots::hero_metrics, &hero_view(demo)), - ui::put(slots::board, &board_view(demo)), - ui::put(slots::activity, &activity_view(demo)), - ui::put(slots::inspector, &inspector_view(demo)), + targets::hero_metrics.put(&hero_view(demo)), + targets::board.put(&board_view(demo)), + targets::activity.put(&activity_view(demo)), + targets::inspector.put(&inspector_view(demo)), slots::notice.text(notice), forms::launch_work.clear("title"), slhx::event("slhx:island-orbit", island_snapshot(demo)), diff --git a/examples/v0/README.md b/examples/v0/README.md index e9cee40..22d0291 100644 --- a/examples/v0/README.md +++ b/examples/v0/README.md @@ -8,7 +8,7 @@ cargo run -p slhx-v0-examples Open . -The page includes working examples for generated-resource, server-first UI commands: +The page includes working examples for generated target objects and server-first UI commands: - counter updates - todo form submission diff --git a/examples/v0/src/lib.rs b/examples/v0/src/lib.rs index 7a4f3a0..73bd9c6 100644 --- a/examples/v0/src/lib.rs +++ b/examples/v0/src/lib.rs @@ -49,7 +49,7 @@ mod tests { #[test] fn counter_updates_a_generated_slot() { fn increment(count: u64) -> impl IntoEffect { - counter::slots::counter_value.text(count + 1) + counter::targets::counter_value.text(count + 1) } let effect = inspect(increment(1)); @@ -77,11 +77,7 @@ mod tests { fn todos_append_keyed_rows_from_form_input() { fn add_todo(input: TodoInput) -> impl IntoEffect { let todo = Todo { id: 7, title: input.title }; - todos::append( - todos::slots::todo_row, - todo.id.to_string(), - &TodoRow { title: todo.title }, - ) + todos::targets::todo_row.append(todo.id.to_string(), &TodoRow { title: todo.title }) } let effect = inspect(add_todo(TodoInput { title: "Ship v0".into() })); diff --git a/slhx-build/src/lib.rs b/slhx-build/src/lib.rs index 9676ae4..a261b82 100644 --- a/slhx-build/src/lib.rs +++ b/slhx-build/src/lib.rs @@ -407,6 +407,45 @@ impl Resources { } out.push_str(&format!("{pad}}}\n\n")); + out.push_str(&format!("{pad}#[allow(non_upper_case_globals)]\n{pad}pub mod targets {{\n")); + out.push_str(&format!("{inner}#[derive(Clone, Copy)]\n")); + out.push_str(&format!("{inner}pub struct SlotTarget {{ slot: ::slhx::Slot }}\n")); + out.push_str(&format!("{inner}impl SlotTarget {{\n")); + out.push_str(&format!("{inner} pub const fn new(slot: ::slhx::Slot) -> Self {{ Self {{ slot }} }}\n")); + out.push_str(&format!("{inner} pub const fn slot(self) -> ::slhx::Slot {{ self.slot }}\n")); + out.push_str(&format!("{inner} pub const fn id(self) -> ::slhx::ResourceId {{ self.slot.id() }}\n")); + out.push_str(&format!("{inner} pub fn put(self, view: &impl ::hemplate::Hemplate) -> impl ::slhx::IntoEffect {{ super::put(self.slot, view) }}\n")); + out.push_str(&format!("{inner} pub fn text(self, value: impl ::std::string::ToString) -> impl ::slhx::IntoEffect {{ self.slot.text(value) }}\n")); + out.push_str(&format!("{inner}}}\n")); + out.push_str(&format!("{inner}#[derive(Clone, Copy)]\n")); + out.push_str(&format!("{inner}pub struct KeyedSlotTarget {{ slot: ::slhx::KeyedSlot }}\n")); + out.push_str(&format!("{inner}impl KeyedSlotTarget\n")); + out.push_str(&format!("{inner}where\n")); + out.push_str(&format!("{inner} K: ::std::string::ToString,\n")); + out.push_str(&format!("{inner}{{\n")); + out.push_str(&format!("{inner} pub const fn new(slot: ::slhx::KeyedSlot) -> Self {{ Self {{ slot }} }}\n")); + out.push_str(&format!("{inner} pub const fn slot(self) -> ::slhx::KeyedSlot {{ self.slot }}\n")); + out.push_str(&format!("{inner} pub const fn id(self) -> ::slhx::ResourceId {{ self.slot.id() }}\n")); + out.push_str(&format!("{inner} pub fn append(self, key: K, view: &impl ::hemplate::Hemplate) -> impl ::slhx::IntoEffect {{ super::append(self.slot, key, view) }}\n")); + out.push_str(&format!("{inner} pub fn prepend(self, key: K, view: &impl ::hemplate::Hemplate) -> impl ::slhx::IntoEffect {{ super::prepend(self.slot, key, view) }}\n")); + out.push_str(&format!("{inner} pub fn replace(self, key: K, view: &impl ::hemplate::Hemplate) -> impl ::slhx::IntoEffect {{ super::replace(self.slot, key, view) }}\n")); + out.push_str(&format!("{inner} pub fn remove(self, key: K) -> impl ::slhx::IntoEffect {{ self.slot.remove(key) }}\n")); + out.push_str(&format!("{inner}}}\n")); + for res in self.slots.values().filter(|res| component_matches(res, component)) { + if res.keyed { + out.push_str(&format!( + "{inner}pub const {}: KeyedSlotTarget<::std::string::String, ::std::string::String> = KeyedSlotTarget::new(super::slots::{});\n", + res.ident, res.ident + )); + } else { + out.push_str(&format!( + "{inner}pub const {}: SlotTarget<::std::string::String> = SlotTarget::new(super::slots::{});\n", + res.ident, res.ident + )); + } + } + out.push_str(&format!("{pad}}}\n\n")); + out.push_str(&format!("{pad}#[allow(non_upper_case_globals)]\n{pad}pub mod handles {{\n")); for res in self.handles.values().filter(|res| component_matches(res, component)) { handle_ids.push(res.id); @@ -1312,6 +1351,10 @@ mod tests { assert!(generated.contains("pub const todo: ::slhx::ComponentRef = ::slhx::ComponentRef::new(\"todo\")")); assert!(generated.contains("pub mod slots")); assert!(generated.contains("pub const todos")); + assert!(generated.contains("pub mod targets")); + assert!(generated.contains("pub struct SlotTarget")); + assert!(generated.contains("pub const todos: SlotTarget<::std::string::String> = SlotTarget::new(super::slots::todos);")); + assert!(generated.contains("pub fn put(self, view: &impl ::hemplate::Hemplate) -> impl ::slhx::IntoEffect")); assert!(generated.contains("pub mod handles")); assert!(generated.contains("pub const create: ::slhx::Handle<::slhx::Form<::std::string::String>>")); assert!(generated.contains("pub mod params")); @@ -1374,6 +1417,7 @@ mod tests { assert!(generated.contains("pub mod todo")); assert!(generated.contains("pub const create: ::slhx::Handle<()> = ::slhx::Handle::new(")); assert!(generated.contains("pub const todos: ::slhx::Slot<::std::string::String> = ::slhx::Slot::new(")); + assert!(generated.contains("pub const todos: SlotTarget<::std::string::String> = SlotTarget::new(super::slots::todos);")); assert!(generated.contains("pub const click: ::slhx::EventName = ::slhx::EventName::new(\"click\")")); let _ = std::fs::remove_dir_all(&base); @@ -1408,6 +1452,8 @@ mod tests { assert!(generated.contains("pub mod todo")); assert!(generated.contains(" pub const COMPONENT: ::slhx::ComponentRef = ::slhx::ComponentRef::new(\"todo\")")); assert!(generated.contains(" pub mod slots")); + assert!(generated.contains(" pub mod targets")); + assert!(generated.contains(" pub const todos: SlotTarget<::std::string::String> = SlotTarget::new(super::slots::todos);")); assert!(generated.contains(" #[doc(hidden)]\n pub fn lower_html")); assert!(generated.contains(" #[doc(hidden)]\n pub fn render_html(view: &impl ::hemplate::Hemplate) -> ::slhx::SafeHtml")); assert!(generated.contains(" pub fn static_fragment(html: &'static str) -> ::slhx::SafeHtml")); @@ -1514,12 +1560,13 @@ mod slhx {{ #[derive(Clone, Copy)] pub struct BuildFingerprint; impl BuildFingerprint {{ pub const fn from_parts(_: &[u32]) -> Self {{ Self }} }} pub struct Effect; + #[derive(Clone, Copy)] pub struct ResourceId; pub trait IntoEffect {{}} impl IntoEffect for Effect {{}} #[derive(Clone, Copy)] pub struct Slot(::std::marker::PhantomData); - impl Slot {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} pub fn html(self, _: SafeHtml) -> Effect {{ Effect }} }} + impl Slot {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} pub const fn id(self) -> ResourceId {{ ResourceId }} pub fn html(self, _: SafeHtml) -> Effect {{ Effect }} pub fn text(self, _: impl ::std::string::ToString) -> Effect {{ Effect }} }} #[derive(Clone, Copy)] pub struct KeyedSlot(::std::marker::PhantomData<(K, T)>); - impl KeyedSlot {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} pub fn append_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn prepend_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn replace_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} }} + impl KeyedSlot {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} pub const fn id(self) -> ResourceId {{ ResourceId }} pub fn append_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn prepend_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn replace_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn remove(self, _: K) -> Effect {{ Effect }} }} #[derive(Clone, Copy)] pub struct Handle(::std::marker::PhantomData); impl Handle {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} }} #[derive(Clone, Copy)] pub struct Atom(::std::marker::PhantomData); @@ -1791,12 +1838,13 @@ mod slhx {{ #[derive(Clone, Copy)] pub struct BuildFingerprint; impl BuildFingerprint {{ pub const fn from_parts(_: &[u32]) -> Self {{ Self }} }} pub struct Effect; + #[derive(Clone, Copy)] pub struct ResourceId; pub trait IntoEffect {{}} impl IntoEffect for Effect {{}} #[derive(Clone, Copy)] pub struct Slot(::std::marker::PhantomData); - impl Slot {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} pub fn html(self, _: SafeHtml) -> Effect {{ Effect }} }} + impl Slot {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} pub const fn id(self) -> ResourceId {{ ResourceId }} pub fn html(self, _: SafeHtml) -> Effect {{ Effect }} pub fn text(self, _: impl ::std::string::ToString) -> Effect {{ Effect }} }} #[derive(Clone, Copy)] pub struct KeyedSlot(::std::marker::PhantomData<(K, T)>); - impl KeyedSlot {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} pub fn append_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn prepend_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn replace_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} }} + impl KeyedSlot {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} pub const fn id(self) -> ResourceId {{ ResourceId }} pub fn append_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn prepend_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn replace_html(self, _: K, _: SafeHtml) -> Effect {{ Effect }} pub fn remove(self, _: K) -> Effect {{ Effect }} }} #[derive(Clone, Copy)] pub struct Handle(::std::marker::PhantomData); impl Handle {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} }} #[derive(Clone, Copy)] pub struct Atom(::std::marker::PhantomData); diff --git a/slhx-test/tests/examples_contract.rs b/slhx-test/tests/examples_contract.rs index 8aeb581..970823a 100644 --- a/slhx-test/tests/examples_contract.rs +++ b/slhx-test/tests/examples_contract.rs @@ -72,6 +72,9 @@ fn canonical_examples_do_not_author_low_level_resource_plumbing() { ".html(ui::render", ".html(slhx::render", ".html(super::ui::render", + "ui::put(", + "ui::append(", + "ui::replace(", "__h=", "name=\"__h\"", "name='__h'",