Keep a loop item's property from being read as a prop of the same name

A component that renders a list, such as FeatureGrid, passes each item
into a child: title={item.title || item.label || "Feature"}. The
compiler decided whether that attribute needed a client binding by
searching the expression for each prop name, so `item.label` matched the
grid's own `label` prop and a binding was emitted.

On hydration the binding is re-evaluated where the loop item is out of
scope, so the expression fell through to its default and replaced the
server's value: every card's title became "Feature". A name after a dot
belongs to whatever precedes it, so it no longer counts as a reference.

Timeline headings follow the same idea: its items were always h4, which
skipped a level and broke the heading order of a page that gives the
timeline no title of its own. They now take h3 in that case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ClintchizandClaude Opus 5 committed 2026-09-18 12:09:28 +05:30
1 parent a4a31032b9
commit a154e6c26e
3 files changed
+57 -10

No files matched your search

+32
View File
@@ -1174,6 +1174,38 @@ test("nested component props retain parent-owned reactive bindings", () => {
expect(output).toContain("[&quot;value&quot;,&quot;{count}&quot;]");
expect(output).toContain('lowerName.startsWith("data-wrn-prop-bind-")');
});
test("a loop item's property is not mistaken for a prop of the same name", () => {
// `item.label` reads the loop item, not this component's `label` prop. A
// client binding here would be re-evaluated with the item out of scope, so
// the card would fall back to its default and lose the rendered value.
const output = generate(
parse(`component Grid {
props { items = [] label = "Features" }
view {
{#each items as item}
<Card title={item.title || item.label || "Feature"} />
{/each}
}
}`),
);
// The helper boilerplate mentions the prefix, so match an emitted binding.
expect(output).not.toContain("data-wrn-prop-bind-0");
});
test("a prop read directly still gets a parent-owned binding inside a loop", () => {
const output = generate(
parse(`component Grid {
props { items = [] label = "Features" }
view {
{#each items as item}
<Card title={label} />
{/each}
}
}`),
);
expect(output).toContain("data-wrn-prop-bind-0");
});
test("component functions are available during server rendering", () => {
const output = generate(
parse(`