From 339ca769fb2f0086657fe4ac9a6ac6a67baf78cf Mon Sep 17 00:00:00 2001 From: slhx agent Date: Tue, 2 Jun 2026 07:34:00 +0200 Subject: [PATCH] feat(build): accept domain ids on keyed targets Let generated string-keyed targets accept displayable domain ids directly, removing caller-side to_string noise from keyed partial append/replace flows. req: codegen/002 req: dx/006 --- REQUIREMENTS.md | 2 +- examples/v0/src/lib.rs | 2 +- slhx-build/src/lib.rs | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 2b223c7..b2e481a 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -226,7 +226,7 @@ a `data-*` handle param is statically known or runtime-extracted. 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 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. +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)`. String-keyed target objects accept displayable domain ids without caller-side `.to_string()` noise. 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/v0/src/lib.rs b/examples/v0/src/lib.rs index 73bd9c6..4826dd8 100644 --- a/examples/v0/src/lib.rs +++ b/examples/v0/src/lib.rs @@ -77,7 +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::targets::todo_row.append(todo.id.to_string(), &TodoRow { title: todo.title }) + todos::targets::todo_row.append(todo.id, &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 a261b82..ff7e346 100644 --- a/slhx-build/src/lib.rs +++ b/slhx-build/src/lib.rs @@ -426,10 +426,12 @@ impl Resources { 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")); + out.push_str(&format!("{inner}impl KeyedSlotTarget<::std::string::String, T> {{\n")); + out.push_str(&format!("{inner} pub fn append(self, key: impl ::std::string::ToString, view: &impl ::hemplate::Hemplate) -> impl ::slhx::IntoEffect {{ self.slot.append_html(key.to_string(), super::render(view)) }}\n")); + out.push_str(&format!("{inner} pub fn prepend(self, key: impl ::std::string::ToString, view: &impl ::hemplate::Hemplate) -> impl ::slhx::IntoEffect {{ self.slot.prepend_html(key.to_string(), super::render(view)) }}\n")); + out.push_str(&format!("{inner} pub fn replace(self, key: impl ::std::string::ToString, view: &impl ::hemplate::Hemplate) -> impl ::slhx::IntoEffect {{ self.slot.replace_html(key.to_string(), super::render(view)) }}\n")); + out.push_str(&format!("{inner} pub fn remove(self, key: impl ::std::string::ToString) -> impl ::slhx::IntoEffect {{ self.slot.remove(key.to_string()) }}\n")); out.push_str(&format!("{inner}}}\n")); for res in self.slots.values().filter(|res| component_matches(res, component)) { if res.keyed {