From 43103524234f5d0ef1390b358a4965dd72555c90 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Sat, 25 Apr 2026 01:05:28 -0700 Subject: [PATCH] fix(live): variant observer detects wrappers added as descendants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit startVariantObserver's "dominated" check only matched when the variant wrapper was added directly as a mutation's addedNode. SvelteKit (and any framework whose HMR replaces a whole subtree on edit) adds the wrapper as a descendant of an added
or similar — the observer ignored those mutations and the session stayed in GENERATING forever even with all 3 variants present in the DOM. Surfaced by the LLM-agent E2E run on vite8-sveltekit. The fake-agent path masked the issue because its splice timing happened before Vite's reload finalized; the slower LLM call shifted timing into the failure window. Co-Authored-By: Claude Opus 4.7 (1M context) --- .agents/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .claude/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .cursor/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .gemini/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .github/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .kiro/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .opencode/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .pi/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .rovodev/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .trae-cn/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- .trae/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- source/skills/impeccable/scripts/live-browser.js | 11 ++++++++++- 12 files changed, 120 insertions(+), 12 deletions(-) diff --git a/.agents/skills/impeccable/scripts/live-browser.js b/.agents/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.agents/skills/impeccable/scripts/live-browser.js +++ b/.agents/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.claude/skills/impeccable/scripts/live-browser.js b/.claude/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.claude/skills/impeccable/scripts/live-browser.js +++ b/.claude/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.cursor/skills/impeccable/scripts/live-browser.js b/.cursor/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.cursor/skills/impeccable/scripts/live-browser.js +++ b/.cursor/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.gemini/skills/impeccable/scripts/live-browser.js b/.gemini/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.gemini/skills/impeccable/scripts/live-browser.js +++ b/.gemini/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.github/skills/impeccable/scripts/live-browser.js b/.github/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.github/skills/impeccable/scripts/live-browser.js +++ b/.github/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.kiro/skills/impeccable/scripts/live-browser.js b/.kiro/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.kiro/skills/impeccable/scripts/live-browser.js +++ b/.kiro/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.opencode/skills/impeccable/scripts/live-browser.js b/.opencode/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.opencode/skills/impeccable/scripts/live-browser.js +++ b/.opencode/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.pi/skills/impeccable/scripts/live-browser.js b/.pi/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.pi/skills/impeccable/scripts/live-browser.js +++ b/.pi/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.rovodev/skills/impeccable/scripts/live-browser.js b/.rovodev/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.rovodev/skills/impeccable/scripts/live-browser.js +++ b/.rovodev/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.trae-cn/skills/impeccable/scripts/live-browser.js b/.trae-cn/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.trae-cn/skills/impeccable/scripts/live-browser.js +++ b/.trae-cn/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/.trae/skills/impeccable/scripts/live-browser.js b/.trae/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/.trae/skills/impeccable/scripts/live-browser.js +++ b/.trae/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } } diff --git a/source/skills/impeccable/scripts/live-browser.js b/source/skills/impeccable/scripts/live-browser.js index 78b58180e..82afa6b61 100644 --- a/source/skills/impeccable/scripts/live-browser.js +++ b/source/skills/impeccable/scripts/live-browser.js @@ -2011,7 +2011,16 @@ for (const m of mutations) { if (m.target.closest?.('[data-impeccable-variants]')) { dominated = true; break; } for (const n of m.addedNodes) { - if (n.nodeType === 1 && (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant)) { + if (n.nodeType !== 1) continue; + // Direct hit: the added node itself is the wrapper or a variant. + if (n.dataset?.impeccableVariants || n.dataset?.impeccableVariant) { + dominated = true; break; + } + // Subtree hit: framework HMR (notably SvelteKit) sometimes replaces + // a whole subtree where the wrapper is a descendant of the added + // node. Without this check, the observer ignores those mutations + // and the session stays in GENERATING forever. + if (n.querySelector?.('[data-impeccable-variants],[data-impeccable-variant]')) { dominated = true; break; } }