diff --git a/hemx-build/src/lib.rs b/hemx-build/src/lib.rs
index 6f92df3..e77d80a 100644
--- a/hemx-build/src/lib.rs
+++ b/hemx-build/src/lib.rs
@@ -1543,11 +1543,16 @@ fn reject_unkeyed_loop(
let Some(current) = surface.scopes.get(scope.0 as usize) else {
return Ok(());
};
- if matches!(current.kind, ScopeKind::For { key_expr: None, .. }) {
+ if let ScopeKind::For {
+ pattern,
+ expr,
+ key_expr: None,
+ } = ¤t.kind
+ {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
- "{}: data-hemx-{kind}=\"{name}\" is inside an h-for without h-key; add a stable h-key=\"item.id\" to the loop that owns this generated target",
+ "{}: data-hemx-{kind}=\"{name}\" is inside h-for=\"{pattern} in {expr}\" without h-key; add a stable h-key=\"{pattern}.id\" to that h-for so generated keyed helpers such as ui::{name}.replace(row) can target this partial. Dynamic +data-key on the child is rendered HTML, not the template fact hemx uses for generated targets.",
path.display()
),
));
@@ -2052,22 +2057,25 @@ fn main() {{
#[test]
fn rejects_hemx_resources_inside_unkeyed_for() {
- // req: scope/001
- for (case, template, resource) in [
+ // req: scope/001 req: diagnostics/002
+ for (case, template, resource, helper) in [
(
"slot",
r#"