fix: hydrate attribute-bound each values and guard style directives

Addresses two cursor review findings:

- {#each} bodies whose bound values appear in attributes (href={link.href},
  src={item.img}) now record attr slots; the browser hydrates them from the
  rendered attribute so component previews no longer mount with empty links.
  Single-expression attributes hydrate exactly; mixed values stay unhydrated
  as before. A new slot classifier also refuses shapes that would crash a
  shallow hydration item (deep paths, method calls, bare item renders) and
  routes them to source-preview mode instead.
- Style directives now run the mixed loop/outer identifier check before the
  free-identifier param check, so style:width={base + r.pct} falls back
  instead of minting a broken param.

Tests: attr-slot analysis units, crashy/lossy fallback units, an attribute-
bound anchor in the stateful SvelteKit fixture asserted through accept, and
a mountedDomProbe e2e hook that reads the hydrated href off the mounted
variant DOM (verified to fail when hydration is disabled).

AI-assisted (Claude Code).

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-28 15:33:16 -07:00
co-authored by Claude Code
parent 6997e4bdb5
commit 39f233ac24
6 changed files with 320 additions and 43 deletions
+26
View File
@@ -512,6 +512,32 @@ for (const { name, fixture } of fixtures) {
);
};
await assertVisibleVariantStyle(visible);
// Optional fixture hook: assert attribute/text values inside the
// MOUNTED variant DOM. Component previews hydrate collection items
// from the rendered page (text slots and attribute slots); a probe
// here catches a preview that mounts but with empty hydrated values,
// which every other assertion (style, counter, accept) misses.
if (Array.isArray(fixture.runtime.mountedDomProbe)) {
for (const probe of fixture.runtime.mountedDomProbe) {
const actual = await evaluatePageWithTimeout(
page,
({ sel, attr }) => {
const query = window.__impeccableLiveQuery || ((s) => document.querySelector(s));
const el = query(sel) || document.querySelector(sel);
if (!el) return null;
return attr ? el.getAttribute(attr) : (el.textContent || '').trim();
},
{ sel: probe.selector, attr: probe.attr || null },
5_000,
'mounted DOM probe',
);
assert.equal(
actual,
probe.expect,
`mounted variant DOM: ${probe.selector}${probe.attr ? ` [${probe.attr}]` : ' text'}`,
);
}
}
for (const targetVariant of cycleSequence) {
t.diagnostic(`Cycling to variant ${targetVariant}`);
visible = await cycleToVariant(page, targetVariant, expectedCount, {