mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
fix(live): variant outline accuracy and shader re-anchor after wrap
Two regressions surfaced in smoke testing, both traceable to state drift when live-wrap.mjs rewrites the source file and HMR swaps the DOM. 1. Variant outline on the wrong element. The skill rewrite lost the explicit "each variant must be a complete element replacement" rule and dropped the "full element replacement" comments from variants 2 and 3. Models started producing variants with loose sibling children, so live-browser's :first-child selector framed only the first sibling. Restored the rule, made all three comments consistent, and replaced :first-child with pickVariantContent — which skips non-visual tags (style/script/link/meta/template) and falls back to the variant div itself when a model still ships multiple visual children. 2. Loading shader freezes after wrap. The MutationObserver only woke up when new non-original variants arrived, so when the wrapper first appeared via HMR with just the original inside, selectedElement was left dangling on the now-detached pre-wrap node. Scroll-tracking read a zero rect on every frame and collapsed the shader canvas to 0x0. The observer now re-anchors selectedElement to the original's content the moment the wrapper shows up, keeping overlays positioned until real variants land. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
62e5b2bb92
commit
9ccb240dc7
@@ -147,16 +147,18 @@ Write CSS + all variants in ONE edit at the `insertLine` reported by `wrap`. Col
|
||||
@scope ([data-impeccable-variant="2"]) { ... }
|
||||
</style>
|
||||
<div data-impeccable-variant="1">
|
||||
<!-- variant 1: full element replacement -->
|
||||
<!-- variant 1: full element replacement (single top-level element) -->
|
||||
</div>
|
||||
<div data-impeccable-variant="2" style="display: none">
|
||||
<!-- variant 2 -->
|
||||
<!-- variant 2: full element replacement -->
|
||||
</div>
|
||||
<div data-impeccable-variant="3" style="display: none">
|
||||
<!-- variant 3 -->
|
||||
<!-- variant 3: full element replacement -->
|
||||
</div>
|
||||
```
|
||||
|
||||
**Each variant div contains exactly one top-level element — the full replacement for the original.** Use the same tag as the original (e.g. `<section>` if the user picked a `<section>`). Loose siblings (heading + paragraph + div as direct children of the variant div) break the outline tracking and the accept flow, which both assume one child.
|
||||
|
||||
The first variant has no `display: none` (visible by default). All others do. If variants use only inline styles and no scoped CSS, omit the `<style>` tag entirely. Use `@scope` for CSS isolation (Chrome 118+ / Firefox 128+ / Safari 17.4+).
|
||||
|
||||
One edit, all variants — the browser's MutationObserver picks everything up in one pass.
|
||||
|
||||
@@ -1248,8 +1248,7 @@
|
||||
showVariantInDOM(sessionId, 1);
|
||||
|
||||
// Update selectedElement to the visible variant's content
|
||||
const visEl = wrapper.querySelector('[data-impeccable-variant="1"] > :first-child');
|
||||
selectedElement = visEl || wrapper.parentElement;
|
||||
selectedElement = pickVariantContent(wrapper, 1) || wrapper.parentElement;
|
||||
|
||||
state = 'CYCLING';
|
||||
updateBarContent('cycling');
|
||||
@@ -1276,10 +1275,29 @@
|
||||
if (!currentSessionId) return;
|
||||
const wrapper = document.querySelector('[data-impeccable-variants="' + currentSessionId + '"]');
|
||||
if (!wrapper) return;
|
||||
const visEl = wrapper.querySelector('[data-impeccable-variant="' + visibleVariant + '"] > :first-child');
|
||||
const visEl = pickVariantContent(wrapper, visibleVariant);
|
||||
if (visEl) selectedElement = visEl;
|
||||
}
|
||||
|
||||
// Resolve the element that represents the variant's visible content.
|
||||
// Contract: each variant div should contain exactly one top-level element
|
||||
// (the full replacement). In practice a model may ship loose siblings or
|
||||
// lead with <style>/<script>. Be defensive: skip non-visual elements, and
|
||||
// if the variant has multiple element children, use the variant div itself
|
||||
// (it wraps all of them and gets correct bounds).
|
||||
function pickVariantContent(wrapper, index) {
|
||||
if (!wrapper) return null;
|
||||
const variantDiv = wrapper.querySelector('[data-impeccable-variant="' + index + '"]');
|
||||
if (!variantDiv) return null;
|
||||
const NON_VISUAL = new Set(['STYLE', 'SCRIPT', 'LINK', 'META', 'TEMPLATE']);
|
||||
const visual = [];
|
||||
for (const child of variantDiv.children) {
|
||||
if (!NON_VISUAL.has(child.tagName)) visual.push(child);
|
||||
}
|
||||
if (visual.length === 1) return visual[0];
|
||||
return variantDiv;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MutationObserver for progressive variant reveal
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1307,6 +1325,13 @@
|
||||
const wrapper = document.querySelector('[data-impeccable-variants="' + sessionId + '"]');
|
||||
if (!wrapper) return;
|
||||
|
||||
// Re-anchor selectedElement if it was detached by live-wrap's HMR swap.
|
||||
// Without this, the shader / highlight / bar track a zero-rect phantom
|
||||
// and the overlay appears frozen.
|
||||
if (selectedElement && !document.body.contains(selectedElement)) {
|
||||
selectedElement = pickVariantContent(wrapper, 'original') || wrapper;
|
||||
}
|
||||
|
||||
const variants = wrapper.querySelectorAll('[data-impeccable-variant]:not([data-impeccable-variant="original"])');
|
||||
const count = variants.length;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user