@accelerated-agency/visual-editor 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4960,7 +4960,7 @@ function PlatformVisualEditorV2({
4960
4960
  window.addEventListener("message", listener);
4961
4961
  return () => window.removeEventListener("message", listener);
4962
4962
  }, [handleMessage]);
4963
- const onTabClick = useCallback(
4963
+ useCallback(
4964
4964
  (tab) => onTabChange?.(tab),
4965
4965
  [onTabChange]
4966
4966
  );
@@ -4972,59 +4972,16 @@ function PlatformVisualEditorV2({
4972
4972
  /* @__PURE__ */ jsx("p", { className: "text-xs text-red-400", children: error })
4973
4973
  ] }) });
4974
4974
  }
4975
- return /* @__PURE__ */ jsxs("div", { className, children: [
4976
- showHeader ? renderHeader?.({
4977
- title: title ?? experiment?.name,
4978
- status: status ?? experiment?.status,
4979
- tabs,
4980
- activeTab,
4981
- onTabClick,
4982
- onClose
4983
- }) ?? /* ── Figma-style top bar ── */
4984
- /* @__PURE__ */ jsxs("div", { className: "h-12 border-b border-slate-200 bg-white flex items-center justify-between px-4 shadow-[0_1px_3px_rgba(0,0,0,0.04)] shrink-0", children: [
4985
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
4986
- /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold tracking-wide px-1.5 py-0.5 rounded bg-indigo-50 text-indigo-600 border border-indigo-100 shrink-0", children: "V2" }),
4987
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-slate-800 truncate", children: title ?? experiment?.name ?? "Visual Editor" }),
4988
- status ? /* @__PURE__ */ jsx("span", { className: "hidden sm:inline-flex text-[10px] px-2 py-0.5 rounded-full border border-slate-200 text-slate-500 font-medium bg-slate-50 capitalize", children: status }) : null,
4989
- dirty ? /* @__PURE__ */ jsx(
4990
- "span",
4991
- {
4992
- className: "w-1.5 h-1.5 rounded-full bg-amber-400 shrink-0",
4993
- title: "Unsaved changes"
4994
- }
4995
- ) : null
4996
- ] }),
4997
- tabs.length > 0 ? /* @__PURE__ */ jsx("div", { className: "hidden md:flex items-center gap-1 absolute left-1/2 -translate-x-1/2", children: tabs.map((tab) => /* @__PURE__ */ jsx(
4998
- "button",
4999
- {
5000
- type: "button",
5001
- onClick: () => onTabClick(tab),
5002
- className: `text-[11px] font-semibold px-3 py-1 rounded-full border transition-all ${tab.label === activeTab ? "bg-indigo-600 text-white border-indigo-600 shadow-sm" : "bg-white text-slate-500 border-slate-200 hover:border-slate-300 hover:text-slate-700"}`,
5003
- children: tab.label
5004
- },
5005
- tab.hash
5006
- )) }) : null,
5007
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: showCloseButton ? /* @__PURE__ */ jsx(
5008
- "button",
5009
- {
5010
- type: "button",
5011
- className: "text-[11px] font-medium px-3 py-1.5 rounded-md border border-slate-200 text-slate-600 hover:bg-slate-50 hover:border-slate-300 transition-all",
5012
- onClick: () => sendToVvveb("close-editor", {}),
5013
- children: closeLabel
5014
- }
5015
- ) : null })
5016
- ] }) : null,
5017
- /* @__PURE__ */ jsx("div", { className: editorClassName, children: /* @__PURE__ */ jsx(
5018
- "iframe",
5019
- {
5020
- ref: iframeRef,
5021
- src: editorSrc,
5022
- className: "w-full h-full border-0",
5023
- title: "Vvveb Visual Editor",
5024
- allow: "same-origin"
5025
- }
5026
- ) })
5027
- ] });
4975
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx("div", { className: editorClassName, children: /* @__PURE__ */ jsx(
4976
+ "iframe",
4977
+ {
4978
+ ref: iframeRef,
4979
+ src: editorSrc,
4980
+ className: "w-full h-full border-0",
4981
+ title: "Vvveb Visual Editor",
4982
+ allow: "same-origin"
4983
+ }
4984
+ ) }) });
5028
4985
  }
5029
4986
  var AI_EDITOR_CHANNEL = "ve-ai-editor";
5030
4987
  var AI_VIEWPORT_WIDTH = {
package/dist/vite.cjs CHANGED
@@ -11,8 +11,20 @@ var fs__default = /*#__PURE__*/_interopDefault(fs);
11
11
  var path__default = /*#__PURE__*/_interopDefault(path);
12
12
 
13
13
  // src/visualEditorProxyPlugin.ts
14
- var popupHideCss = `<style id="__ce_popup_hide">#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#onetrust-consent-sdk,#onetrust-banner-sdk,.cc-window,.cc-banner,.cc-overlay,#cookie-notice,#cookie-banner,#cookie-consent,.cookie-notice,.cookie-banner,.cookie-consent,.cookie-popup,.cookie-bar,.cookie-message,.cookie-alert,.gdpr-banner,.gdpr-consent,.gdpr-popup,.gdpr-overlay,#gdpr-consent,#gdpr-banner,.consent-banner,.consent-popup,.consent-overlay,#consent-banner,#consent-popup,[class*="cookie-consent"],[class*="cookie-banner"],[class*="cookie-notice"],[class*="CookieConsent"],[class*="CookieBanner"],[id*="cookie-consent"],[id*="cookie-banner"],[id*="cookie-notice"],[aria-label*="cookie" i],[aria-label*="consent" i],.klaro,.klaro .cookie-modal,#usercentrics-root,.trustarc-banner,#truste-consent-track,#hs-eu-cookie-confirmation,.osano-cm-window,.osano-cm-dialog,.evidon-banner,#_evidon_banner,.js-cookie-consent,.cookie-disclaimer,.shopify-section-cookies,#shopify-section-cookies,#shopify-pc__banner,#shopify-pc__modal,.privacy-banner,.privacy-popup,[data-testid="cookie-banner"],[data-testid="consent-banner"],.amgdprcookie-bar-container,[data-amcookie-js="bar"],.amgdprjs-bar-template,.amgdprcookie-modal-container,.amgdprcookie-modal-overlay,#cmplz-cookiebanner-container,.cmplz-cookiebanner,#iubenda-cs-banner,.iubenda-cs-container,#qc-cmp2-container,.qc-cmp2-consent-info,#didomi-host,.didomi-popup-container,.didomi-notice,#termly-code-snippet-support,[class*="termly"],[class*="gdprcookie"],[class*="amgdpr"],[id*="gdpr-cookie"],[class*="cookie-modal"],[id*="cookie-modal"],[class*="cookieConsent"],[id*="cookieConsent"],.klaviyo-form,.klaviyo-modal,.klaviyo-popup,[class*="klaviyo"],.privy-popup,.privy-flyout,[id*="privy"],#PopupSignupForm,.popup-signup,.newsletter-popup,.newsletter-modal,[class*="newsletter-popup"],[class*="newsletter-modal"],[id*="newsletter-popup"],.email-popup,.email-modal,[class*="email-popup"],[class*="email-modal"],.signup-popup,.signup-modal,[class*="signup-popup"],[class*="signup-modal"],.subscribe-popup,.subscribe-modal,[class*="subscribe-popup"],#mc_embed_signup,.mc-modal,.mc-banner,.mc-closeModal,.omniconvert-popup,[class*="omniconvert"],.optinmonster-popup,[id*="om-"][class*="campaign"],.sumo-overlay,.sumome-overlay,[class*="sumome"],.hustle-modal,.hustle-popup,[class*="hustle-"],.popup-overlay,.popup-modal,.modal-overlay,[class*="exit-intent"],[class*="exitintent"],.wheelio-popup,[class*="wheelio"],.spin-wheel-popup,.justuno-popup,[class*="justuno"],.wisepops,.wisepops-overlay,[class*="wisepops"],.elegantmodal,.elegant-popup,#zipify-popup,[class*="zipify"],.age-gate,.age-verification,.age-popup,[class*="age-gate"],[class*="age-verif"],[class*="popup-overlay"],[class*="modal-overlay"],[class*="popup-backdrop"]{display:none!important;visibility:hidden!important;opacity:0!important;pointer-events:none!important;height:0!important;overflow:hidden!important;}body.klaviyo-open,body.modal-open,body.popup-open,body.no-scroll,body.noscroll{overflow:auto!important;position:static!important;}</style>`;
15
- var consentAllowCss = `<style id="__ce_consent_allow">#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#onetrust-consent-sdk,#onetrust-banner-sdk,.cc-window,.cc-banner,.cc-overlay,#cookie-notice,#cookie-banner,#cookie-consent,.cookie-notice,.cookie-banner,.cookie-consent,.cookie-popup,.cookie-bar,.cookie-message,.cookie-alert,.gdpr-banner,.gdpr-consent,.gdpr-popup,.gdpr-overlay,#gdpr-consent,#gdpr-banner,.consent-banner,.consent-popup,.consent-overlay,#consent-banner,#consent-popup,[class*="cookie-consent"],[class*="cookie-banner"],[class*="cookie-notice"],[class*="CookieConsent"],[class*="CookieBanner"],[id*="cookie-consent"],[id*="cookie-banner"],[id*="cookie-notice"],[aria-label*="cookie" i],[aria-label*="consent" i],.klaro,.klaro .cookie-modal,#usercentrics-root,.trustarc-banner,#truste-consent-track,#hs-eu-cookie-confirmation,.osano-cm-window,.osano-cm-dialog,.evidon-banner,#_evidon_banner,.js-cookie-consent,.cookie-disclaimer,.shopify-section-cookies,#shopify-section-cookies,#shopify-pc__banner,#shopify-pc__modal,.privacy-banner,.privacy-popup,[data-testid="cookie-banner"],[data-testid="consent-banner"],.amgdprcookie-bar-container,[data-amcookie-js="bar"],.amgdprjs-bar-template,.amgdprcookie-modal-container,.amgdprcookie-modal-overlay,#cmplz-cookiebanner-container,.cmplz-cookiebanner,#iubenda-cs-banner,.iubenda-cs-container,#qc-cmp2-container,.qc-cmp2-consent-info,#didomi-host,.didomi-popup-container,.didomi-notice,#termly-code-snippet-support,[class*="termly"],[class*="gdprcookie"],[class*="amgdpr"],[id*="gdpr-cookie"],[class*="cookie-modal"],[id*="cookie-modal"],[class*="cookieConsent"],[id*="cookieConsent"]{display:revert!important;visibility:visible!important;opacity:1!important;pointer-events:auto!important;height:auto!important;overflow:visible!important;}</style>`;
14
+ var iframeAlwaysShowCss = `<style id="__ce_force_show">#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#onetrust-consent-sdk,#onetrust-banner-sdk,.cc-window,.cc-banner,.cc-overlay,#cookie-notice,#cookie-banner,#cookie-consent,.cookie-notice,.cookie-banner,.cookie-consent,.cookie-popup,.cookie-bar,.cookie-message,.cookie-alert,.gdpr-banner,.gdpr-consent,.gdpr-popup,.gdpr-overlay,#gdpr-consent,#gdpr-banner,.consent-banner,.consent-popup,.consent-overlay,#consent-banner,#consent-popup,[class*="cookie-consent"],[class*="cookie-banner"],[class*="cookie-notice"],[class*="CookieConsent"],[class*="CookieBanner"],[id*="cookie-consent"],[id*="cookie-banner"],[id*="cookie-notice"],[aria-label*="cookie" i],[aria-label*="consent" i],.klaro,.klaro .cookie-modal,#usercentrics-root,.trustarc-banner,#truste-consent-track,#hs-eu-cookie-confirmation,.osano-cm-window,.osano-cm-dialog,.evidon-banner,#_evidon_banner,.js-cookie-consent,.cookie-disclaimer,.shopify-section-cookies,#shopify-section-cookies,#shopify-pc__banner,#shopify-pc__modal,.privacy-banner,.privacy-popup,[data-testid="cookie-banner"],[data-testid="consent-banner"],.amgdprcookie-bar-container,[data-amcookie-js="bar"],.amgdprjs-bar-template,.amgdprcookie-modal-container,.amgdprcookie-modal-overlay,#cmplz-cookiebanner-container,.cmplz-cookiebanner,#iubenda-cs-banner,.iubenda-cs-container,#qc-cmp2-container,.qc-cmp2-consent-info,#didomi-host,.didomi-popup-container,.didomi-notice,#termly-code-snippet-support,[class*="termly"],[class*="gdprcookie"],[class*="amgdpr"],[id*="gdpr-cookie"],[class*="cookie-modal"],[id*="cookie-modal"],[class*="cookieConsent"],[id*="cookieConsent"]{display:revert!important;visibility:visible!important;opacity:1!important;pointer-events:auto!important;height:auto!important;max-height:none!important;overflow:visible!important;}</style>`;
15
+ var iframeAlwaysShowCssGuardScript = `<script id="__ce_force_show_guard">(function(){try{
16
+ function ensureForceShowStyleLast(){
17
+ var style=document.getElementById("__ce_force_show");
18
+ if(!style||!document.head)return;
19
+ if(document.head.lastElementChild!==style){
20
+ document.head.appendChild(style);
21
+ }
22
+ }
23
+ ensureForceShowStyleLast();
24
+ var mo=new MutationObserver(function(){ensureForceShowStyleLast();});
25
+ if(document.head){mo.observe(document.head,{childList:true});}
26
+ window.addEventListener("beforeunload",function(){try{mo.disconnect();}catch(_){}});}
27
+ catch(_){}})();</script>`;
16
28
  var AI_SYSTEM_PROMPT = `You are a CRO expert. Return JSON only with: hypothesis, mutations, summary. Keep 2-6 precise mutations and only valid selectors from snapshot.`;
17
29
  var BRIDGE_SCRIPT = `(function(){if(window.__CONVERSION_BRIDGE_LOADED__)return;window.__CONVERSION_BRIDGE_LOADED__=true;var CHANNEL='conversion-editor';var highlightEl=null;function send(payload){window.parent.postMessage({channel:CHANNEL,payload:payload},'*');}function qs(selector){try{return document.querySelector(selector);}catch(_){return null;}}function getSelector(el){if(!el||el.nodeType!==1)return'';if(el.id)return'#'+CSS.escape(el.id);var parts=[];var current=el;var depth=0;while(current&&current.nodeType===1&&depth<5){var part=current.tagName.toLowerCase();if(current.classList&&current.classList.length){part+='.'+Array.from(current.classList).slice(0,2).map(function(c){return CSS.escape(c);}).join('.');}var parent=current.parentElement;if(parent){var siblings=Array.from(parent.children).filter(function(s){return s.tagName===current.tagName;});if(siblings.length>1)part+=':nth-of-type('+(siblings.indexOf(current)+1)+')';}parts.unshift(part);current=parent;depth+=1;}return parts.join(' > ');}function payloadOf(el){var rect=el.getBoundingClientRect();var style=window.getComputedStyle(el);return{selector:getSelector(el),tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,500),computedStyles:{color:style.color,backgroundColor:style.backgroundColor,fontSize:style.fontSize,fontWeight:style.fontWeight,lineHeight:style.lineHeight,display:style.display},rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}function applyMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.value;break;case'setText':el.textContent=m.value;break;case'setHTML':el.innerHTML=m.value;break;case'setAttribute':if(m.property)el.setAttribute(m.property,m.value);break;case'hide':el.style.display='none';break;case'show':el.style.display='';break;case'insertHTML':if(m.position)el.insertAdjacentHTML(m.position,m.value||'');break;case'insertSection':if(m.position)el.insertAdjacentHTML(m.position,m.sectionHtml||m.value||'');break;case'reorderElement':if(!m.targetSelector)break;var target=qs(m.targetSelector);if(!target||!target.parentNode||!el.parentNode)break;if(m.insertPosition==='before')target.parentNode.insertBefore(el,target);else target.parentNode.insertBefore(el,target.nextSibling);break;default:break;}}function revertMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.previous||'';break;case'setText':el.textContent=m.previous||'';break;case'setHTML':el.innerHTML=m.previous||'';break;case'setAttribute':if(m.property){if(m.previous!=null)el.setAttribute(m.property,m.previous);else el.removeAttribute(m.property);}break;case'hide':case'show':el.style.display=m.previous||'';break;default:break;}}function captureSnapshot(){var elements=Array.from(document.querySelectorAll('h1,h2,h3,p,button,a,input,select,img,section,div')).slice(0,700).map(function(el){var rect=el.getBoundingClientRect();var cs=window.getComputedStyle(el);return{selector:getSelector(el),parentSelector:el.parentElement?getSelector(el.parentElement):null,tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,300),attributes:Array.from(el.attributes||[]).reduce(function(acc,a){acc[a.name]=a.value;return acc;},{}),computedStyles:{color:cs.color,backgroundColor:cs.backgroundColor,fontSize:cs.fontSize,fontWeight:cs.fontWeight,display:cs.display},childrenCount:el.children?el.children.length:0,aboveFold:rect.top<window.innerHeight,depth:(function(){var d=0,p=el.parentElement;while(p&&d<25){d++;p=p.parentElement;}return d;})()};});send({type:'snapshotCaptured',snapshot:{url:window.location.href,title:document.title,viewport:{width:window.innerWidth,height:window.innerHeight},elements:elements}});}function captureTree(){function toNode(el,depth){var rect=el.getBoundingClientRect();var tag=el.tagName.toLowerCase();var type='generic';if(tag==='section')type='section';else if(tag==='img')type='image';else if(tag==='a')type='link';else if(tag==='button')type='button';else if(tag==='form')type='form';else if(['p','h1','h2','h3','h4','h5','h6','span'].indexOf(tag)>=0)type='text';else if(['div','main','article','aside','header','footer','nav'].indexOf(tag)>=0)type='container';var children=Array.from(el.children||[]).slice(0,50).map(function(c){return toNode(c,depth+1);});return{id:getSelector(el),selector:getSelector(el),tagName:tag,label:(el.getAttribute('aria-label')||el.getAttribute('id')||el.className||tag).toString().slice(0,80),type:type,children:children,depth:depth,isAboveFold:rect.top<window.innerHeight,rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}send({type:'pageTreeCaptured',tree:[toNode(document.body,0)]});}window.addEventListener('message',function(e){var msg=e.data;if(!msg||msg.channel!==CHANNEL||!msg.payload)return;var p=msg.payload;switch(p.type){case'ping':send({type:'pong'});break;case'applyMutation':applyMutation(p.mutation);break;case'applyMutationBatch':(p.mutations||[]).forEach(applyMutation);break;case'revert':revertMutation(p.mutation);break;case'clearAllMutations':window.location.reload();break;case'captureSnapshot':captureSnapshot();break;case'validateSelectors':send({type:'selectorsValidated',results:(p.selectors||[]).map(function(s){var el=qs(s);return{selector:s,found:!!el,tagName:el?el.tagName.toLowerCase():null};})});break;case'scrollToElement':case'selectElement':var target=qs(p.selector);if(target){target.scrollIntoView({behavior:'smooth',block:'center'});send({type:'elementSelected',element:payloadOf(target)});}break;case'hoverElement':var h=qs(p.selector);if(h){if(highlightEl)highlightEl.style.outline='';h.style.outline='2px solid #3b82f6';highlightEl=h;}break;case'capturePageTree':captureTree();break;default:break;}});document.addEventListener('click',function(e){var el=e.target;if(!(el instanceof Element))return;send({type:'elementSelected',element:payloadOf(el)});},true);send({type:'bridgeReady'});})();`;
18
30
  function buildVvvebEditorHtml() {
@@ -254,7 +266,7 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
254
266
  /* \u2500\u2500 DOM tree (Elements tab) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
255
267
  .dt-tree{font-size:11px;padding:0px 0 0px 20px;user-select:none}
256
268
  .dt-row{
257
- display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
269
+ width:fit-content;display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
258
270
  cursor:pointer;color:var(--text-2);border-radius:4px;margin:0 4px
259
271
  }
260
272
  .dt-row:hover{background:var(--bg-hover);color:var(--text)}
@@ -680,10 +692,10 @@ select.pr-inp{cursor:pointer;background:#fff}
680
692
  <button class="tb-dk-btn" id="btn-mode-nav" onclick="setMode('navigate')" title="Navigate mode \u2014 interact with page normally"><i class="bi bi-magic"></i></button>
681
693
  <button class="tb-dk-btn" title="Comments"><i class="bi bi-chat-dots"></i></button>
682
694
  </div>
683
- <!-- btn-close: hidden visually, kept for JS event listener -->
684
- <button id="btn-close" style="display:none" title="Close editor"></button>
685
695
  <button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
686
- <button class="tb-fin-btn" id="btn-save">Finalize</button>
696
+ <button class="tb-fin-btn" id="btn-save">Save Changes</button>
697
+ <!-- btn-close: kept for JS event listener -->
698
+ <button class="tb-fin-btn" id="btn-close">Close</button>
687
699
  </div>
688
700
 
689
701
  <!-- url-bar: hidden, kept for JS compatibility -->
@@ -1102,6 +1114,8 @@ var iframeContentNavGen = 0;
1102
1114
  var iframeContentApplyTimer = null;
1103
1115
  var iframeEarlyGranularPrimedForGen = null;
1104
1116
  var iframeEarlySyncPrimedForGen = null;
1117
+ var iframeEarlyDomSignature = '';
1118
+ var iframeEarlyDomSignatureNavGen = 0;
1105
1119
  /** insert/reorder entries are applied from early granular + full apply \u2014 skip exact duplicates per iframe nav */
1106
1120
  var appliedStructuralChangesetKeys = {};
1107
1121
  var isDirty = false;
@@ -1113,6 +1127,7 @@ var selectedEl = null;
1113
1127
  var selectedElFingerprint = '';
1114
1128
  var selectedElRecoverMisses = 0;
1115
1129
  var MAX_SELECTED_RECOVER_MISSES = 12;
1130
+ var hoveredTreeEl = null;
1116
1131
  var isDeselectingSelection = false;
1117
1132
  var suppressClickUntil = 0;
1118
1133
  var dragAttachDoc = null;
@@ -1126,6 +1141,7 @@ var iframeSyncAttempts = 0;
1126
1141
  var selectionScrollWin = null;
1127
1142
  var selectionResizeBound = false;
1128
1143
  var clickAttachDoc = null;
1144
+ var hoverAttachDoc = null;
1129
1145
  var changeObserver = null;
1130
1146
  var changeObserverDoc = null;
1131
1147
  /** Incremented while applying changesets / selection chrome so MutationObserver does not mark dirty */
@@ -1338,6 +1354,7 @@ function setMode(mode) {
1338
1354
  document.getElementById('btn-mode-editor').classList.toggle('active', mode === 'editor');
1339
1355
  document.getElementById('btn-mode-nav').classList.toggle('active', mode === 'navigate');
1340
1356
  if (mode === 'navigate') {
1357
+ clearTreeHoverHighlight();
1341
1358
  setDragHandleActive(false);
1342
1359
  deselectElement();
1343
1360
  } else if (mode === 'editor') {
@@ -1721,7 +1738,7 @@ function softReloadEditorIframe() {
1721
1738
  resetIframeBindings();
1722
1739
  setIframePageLoadingUi(true);
1723
1740
  iframe.src = appendIframeReloadBust(src);
1724
- startIframeContentApplyWatcher(navGen);
1741
+ startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
1725
1742
  scheduleDomTreeRefresh();
1726
1743
  }
1727
1744
 
@@ -2200,6 +2217,27 @@ function bodyHasFirstPaintChild(body) {
2200
2217
  return false;
2201
2218
  }
2202
2219
 
2220
+ function computeIframeDomSignature(doc) {
2221
+ if (!doc || !doc.body) return '';
2222
+ var body = doc.body;
2223
+ var tags = [];
2224
+ var walker = null;
2225
+ try {
2226
+ walker = doc.createTreeWalker(body, NodeFilter.SHOW_ELEMENT, null);
2227
+ } catch(_) {
2228
+ walker = null;
2229
+ }
2230
+ var count = 0;
2231
+ if (walker) {
2232
+ while (walker.nextNode() && count < 400) {
2233
+ var node = walker.currentNode;
2234
+ tags.push((node && node.tagName) ? String(node.tagName).toLowerCase() : '');
2235
+ count += 1;
2236
+ }
2237
+ }
2238
+ return String(body.children ? body.children.length : 0) + '|' + String(count) + '|' + tags.join(',');
2239
+ }
2240
+
2203
2241
  /** True when at least one granular changeset selector already matches (nested content painted). */
2204
2242
  function granularAnySelectorMatches(doc, cs) {
2205
2243
  if (!doc || !cs || !cs.length) return false;
@@ -2354,6 +2392,7 @@ function resetIframeBindings() {
2354
2392
  appliedStructuralChangesetKeys = {};
2355
2393
  clickAttachDoc = null;
2356
2394
  dragAttachDoc = null;
2395
+ hoverAttachDoc = null;
2357
2396
  changeObserverDoc = null;
2358
2397
  clearPendingGranularChangesets();
2359
2398
  if (changeObserver) {
@@ -2382,7 +2421,7 @@ function loadPage(proxyUrl) {
2382
2421
  iframe.style.display = 'block';
2383
2422
  setIframePageLoadingUi(true);
2384
2423
  iframe.src = appendIframeReloadBust(proxyUrl);
2385
- startIframeContentApplyWatcher(navGen);
2424
+ startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
2386
2425
  scheduleDomTreeRefresh();
2387
2426
  }
2388
2427
 
@@ -2454,7 +2493,7 @@ function switchVariation(varId) {
2454
2493
  iframe.src = appendIframeReloadBust(src);
2455
2494
  // Do not sync here: the document is still the previous navigation until the
2456
2495
  // iframe load event; an eager sync attached observers / DOM tree to the wrong document.
2457
- startIframeContentApplyWatcher(navGen);
2496
+ startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
2458
2497
  scheduleDomTreeRefresh();
2459
2498
  }
2460
2499
  } catch(_) {}
@@ -2971,10 +3010,12 @@ function reapplyActiveVariationGranular(iframeDoc) {
2971
3010
  }
2972
3011
 
2973
3012
  /** Poll iframe document during navigation; apply granular edits as soon as DOM can match selectors. */
2974
- function startIframeContentApplyWatcher(navGen) {
3013
+ function startIframeContentApplyWatcher(navGen, prevDocRef) {
2975
3014
  stopIframeContentApplyWatcher();
2976
3015
  iframeEarlyGranularPrimedForGen = null;
2977
3016
  iframeEarlySyncPrimedForGen = null;
3017
+ iframeEarlyDomSignature = '';
3018
+ iframeEarlyDomSignatureNavGen = navGen;
2978
3019
  var iframe = document.getElementById('iframeId');
2979
3020
  iframeContentApplyTimer = setInterval(function() {
2980
3021
  if (navGen !== iframeContentNavGen) {
@@ -2984,6 +3025,7 @@ function startIframeContentApplyWatcher(navGen) {
2984
3025
  try {
2985
3026
  var doc = iframe.contentDocument;
2986
3027
  if (!doc || !doc.body) return;
3028
+ if (prevDocRef && doc === prevDocRef) return;
2987
3029
  var docUrl = '';
2988
3030
  try { docUrl = String(doc.URL || ''); } catch(_) {}
2989
3031
  if (docUrl === 'about:blank') return;
@@ -3018,6 +3060,7 @@ function startIframeContentApplyWatcher(navGen) {
3018
3060
  }
3019
3061
  if (iframeEarlySyncPrimedForGen !== navGen) {
3020
3062
  iframeEarlySyncPrimedForGen = navGen;
3063
+ iframeEarlyDomSignature = computeIframeDomSignature(doc);
3021
3064
  syncIframeInteractions('iframe-early-paint');
3022
3065
  }
3023
3066
  }
@@ -3087,6 +3130,8 @@ function injectIframeSelectionStyles(doc) {
3087
3130
  st.textContent =
3088
3131
  '.vve-selected{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
3089
3132
  'box-shadow:0 0 0 2px rgba(99,102,241,.28),inset 0 0 0 1px rgba(99,102,241,.18)!important;}' +
3133
+ '.vve-tree-hover{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
3134
+ 'box-shadow:0 0 0 2px rgba(99,102,241,.22),inset 0 0 0 1px rgba(99,102,241,.12)!important;}' +
3090
3135
  'html.vve-drag-armed .vve-selected{cursor:grab!important;}' +
3091
3136
  '.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;' +
3092
3137
  'outline-offset:2px!important;cursor:grabbing!important;box-shadow:none!important;}';
@@ -3098,6 +3143,47 @@ function injectIframeSelectionStyles(doc) {
3098
3143
  }
3099
3144
  }
3100
3145
 
3146
+ function clearTreeHoverHighlight() {
3147
+ if (!hoveredTreeEl) return;
3148
+ beginSuppressIframeMutationDirty();
3149
+ try {
3150
+ if (hoveredTreeEl.classList) hoveredTreeEl.classList.remove('vve-tree-hover');
3151
+ } catch(_) {
3152
+ } finally {
3153
+ endSuppressIframeMutationDirty();
3154
+ hoveredTreeEl = null;
3155
+ }
3156
+ }
3157
+
3158
+ function setTreeHoverHighlight(el) {
3159
+ if (!el || el.nodeType !== 1) {
3160
+ clearTreeHoverHighlight();
3161
+ return;
3162
+ }
3163
+ if (hoveredTreeEl === el) return;
3164
+ clearTreeHoverHighlight();
3165
+ beginSuppressIframeMutationDirty();
3166
+ try {
3167
+ if (el.classList) el.classList.add('vve-tree-hover');
3168
+ hoveredTreeEl = el;
3169
+ } catch(_) {
3170
+ hoveredTreeEl = null;
3171
+ } finally {
3172
+ endSuppressIframeMutationDirty();
3173
+ }
3174
+ }
3175
+
3176
+ function isTreeHoverOnlyClassMutation(mutation) {
3177
+ if (!mutation || mutation.type !== 'attributes' || mutation.attributeName !== 'class') return false;
3178
+ var oldClass = String(mutation.oldValue || '');
3179
+ var target = mutation.target;
3180
+ var nextClass = '';
3181
+ try {
3182
+ nextClass = target && typeof target.className === 'string' ? target.className : '';
3183
+ } catch(_) {}
3184
+ return oldClass.indexOf('vve-tree-hover') >= 0 || String(nextClass).indexOf('vve-tree-hover') >= 0;
3185
+ }
3186
+
3101
3187
  function setDragHandleActive(on) {
3102
3188
  dragHandleActive = !!on;
3103
3189
  var b = document.getElementById('sf-drag');
@@ -3418,6 +3504,9 @@ function renderDomTree(filterRaw) {
3418
3504
  if (e.target.closest && e.target.closest('.dt-chev')) return;
3419
3505
  selectElementFromTree(el);
3420
3506
  };
3507
+ row.onmouseenter = function() {
3508
+ setTreeHoverHighlight(el);
3509
+ };
3421
3510
  root.appendChild(row);
3422
3511
 
3423
3512
  if (!hasKids || collapsed) return;
@@ -3436,6 +3525,9 @@ function renderDomTree(filterRaw) {
3436
3525
  ? '<div class="dt-muted">No elements match your search.</div>'
3437
3526
  : '<div class="dt-muted">No visible elements yet.</div>';
3438
3527
  }
3528
+ root.onmouseleave = function() {
3529
+ clearTreeHoverHighlight();
3530
+ };
3439
3531
  }
3440
3532
 
3441
3533
  // \u2500\u2500 Utility helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -3855,12 +3947,25 @@ function renderRightPanel(el) {
3855
3947
  var sel = buildSelector(el);
3856
3948
  bindings.forEach(function(b){
3857
3949
  var inp = document.getElementById(b[0]);
3858
- if (inp) inp.addEventListener('input', function(){
3950
+ if (inp) {
3951
+ var onValueChange = function() {
3859
3952
  // Read the original value BEFORE applying the change so we can revert later
3860
3953
  var orig = getOriginalValue(b[0], el);
3861
3954
  b[1](inp.value);
3862
- logChange(sel, b[0], inp.value, el, orig);
3863
- });
3955
+ var valueToLog = inp.value;
3956
+ try {
3957
+ console.log('[V2] input changed', {
3958
+ inputId: b[0],
3959
+ rawValue: inp.value,
3960
+ appliedValue: valueToLog,
3961
+ selector: sel,
3962
+ });
3963
+ } catch(_) {}
3964
+ logChange(sel, b[0], valueToLog, el, orig);
3965
+ };
3966
+ inp.addEventListener('input', onValueChange);
3967
+ inp.addEventListener('change', onValueChange);
3968
+ }
3864
3969
  });
3865
3970
  }
3866
3971
 
@@ -4123,6 +4228,32 @@ function attachClickHandler() {
4123
4228
  } catch(_) {}
4124
4229
  }
4125
4230
 
4231
+ function attachIframeHoverHandler() {
4232
+ try {
4233
+ var iframe = document.getElementById('iframeId');
4234
+ var doc = iframe && iframe.contentDocument;
4235
+ if (!doc || !doc.body) return;
4236
+ if (hoverAttachDoc === doc) return;
4237
+ hoverAttachDoc = doc;
4238
+ doc.addEventListener('mousemove', function(e) {
4239
+ if (currentMode !== 'editor') {
4240
+ clearTreeHoverHighlight();
4241
+ return;
4242
+ }
4243
+ var target = e.target;
4244
+ if (!target || target === doc.body || target === doc.documentElement) {
4245
+ clearTreeHoverHighlight();
4246
+ return;
4247
+ }
4248
+ setTreeHoverHighlight(target);
4249
+ }, true);
4250
+ doc.addEventListener('mouseout', function(e) {
4251
+ if (e.relatedTarget) return;
4252
+ clearTreeHoverHighlight();
4253
+ }, true);
4254
+ } catch(_) {}
4255
+ }
4256
+
4126
4257
  function attachChangeObserver() {
4127
4258
  try {
4128
4259
  var iframe = document.getElementById('iframeId');
@@ -4135,6 +4266,14 @@ function attachChangeObserver() {
4135
4266
  changeObserverDoc = null;
4136
4267
  }
4137
4268
  changeObserver = new MutationObserver(function(mutations) {
4269
+ var hasMeaningfulMutation = false;
4270
+ for (var mi = 0; mi < mutations.length; mi++) {
4271
+ if (!isTreeHoverOnlyClassMutation(mutations[mi])) {
4272
+ hasMeaningfulMutation = true;
4273
+ break;
4274
+ }
4275
+ }
4276
+ if (!hasMeaningfulMutation) return;
4138
4277
  // Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
4139
4278
  // Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
4140
4279
  // Keep selection sticky by re-resolving from fingerprint.
@@ -4145,7 +4284,7 @@ function attachChangeObserver() {
4145
4284
  updateSelectionToolbar();
4146
4285
  });
4147
4286
  changeObserver.observe(doc.body, {
4148
- childList: true, subtree: true, attributes: true, characterData: true
4287
+ childList: true, subtree: true, attributes: true, characterData: true, attributeOldValue: true
4149
4288
  });
4150
4289
  changeObserverDoc = doc;
4151
4290
  } catch(_) {}
@@ -4171,6 +4310,7 @@ function syncIframeInteractions(reason) {
4171
4310
  injectIframeSelectionStyles(doc);
4172
4311
  refreshPersistentChangesetStyleTagForActiveVariation();
4173
4312
  attachClickHandler();
4313
+ attachIframeHoverHandler();
4174
4314
  attachDragReposition();
4175
4315
  attachChangeObserver();
4176
4316
  startConsistencyWatchdog(doc);
@@ -4529,10 +4669,9 @@ window.addEventListener('load', function() {
4529
4669
  var iframe = document.getElementById('iframeId');
4530
4670
  iframe.addEventListener('load', function() {
4531
4671
  if (!iframe.src || iframe.src === 'about:blank' || iframe.src === window.location.href) return;
4532
- // New iframe navigation: always drop bindings tied to the previous document.
4533
- resetIframeBindings();
4534
4672
  var doc = iframe.contentDocument;
4535
4673
  if (!doc) {
4674
+ resetIframeBindings();
4536
4675
  syncIframeInteractions('iframe-load-no-doc');
4537
4676
  return;
4538
4677
  }
@@ -4541,14 +4680,32 @@ window.addEventListener('load', function() {
4541
4680
  // Stale events: src may already be the proxy URL while the document is still
4542
4681
  // about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
4543
4682
  if (docUrl === 'about:blank') {
4683
+ resetIframeBindings();
4544
4684
  syncIframeInteractions('iframe-load-about-blank');
4545
4685
  return;
4546
4686
  }
4687
+ // If early-paint and final load DOM signatures match, avoid a second full apply/reset
4688
+ // that steals focus from sidebar controls while the page is still stabilizing.
4689
+ var shouldRefreshOnFinalLoad = true;
4690
+ if (
4691
+ iframeEarlyDomSignatureNavGen === iframeContentNavGen &&
4692
+ iframeEarlySyncPrimedForGen === iframeContentNavGen &&
4693
+ iframeEarlyDomSignature
4694
+ ) {
4695
+ var finalDomSignature = computeIframeDomSignature(doc);
4696
+ if (finalDomSignature && finalDomSignature === iframeEarlyDomSignature) {
4697
+ shouldRefreshOnFinalLoad = false;
4698
+ }
4699
+ }
4700
+ if (clickAttachDoc !== doc || dragAttachDoc !== doc || changeObserverDoc !== doc || hoverAttachDoc !== doc) {
4701
+ resetIframeBindings();
4702
+ }
4547
4703
  attachIframeLoadingUntilComplete(iframe);
4548
4704
  if (doc.body && iframeDocMatchesNavigatedSrc(iframe, doc)) {
4549
4705
  stopIframeContentApplyWatcher();
4550
- deselectElement();
4551
- applyActiveVariationHtml();
4706
+ if (shouldRefreshOnFinalLoad) {
4707
+ applyActiveVariationHtml();
4708
+ }
4552
4709
  }
4553
4710
  // Always attempt sync; it has its own readiness checks + retry loop.
4554
4711
  syncIframeInteractions('iframe-load');
@@ -4890,9 +5047,12 @@ function createVisualEditorMiddleware(options) {
4890
5047
  }
4891
5048
  );
4892
5049
  if (html.includes("</head>")) {
4893
- html = html.replace("</head>", `${popupHideCss}
4894
- ${consentAllowCss}
4895
- </head>`);
5050
+ html = html.replace(
5051
+ "</head>",
5052
+ `${iframeAlwaysShowCss}
5053
+ ${iframeAlwaysShowCssGuardScript}
5054
+ </head>`
5055
+ );
4896
5056
  }
4897
5057
  html = html.replace(
4898
5058
  /<meta[^>]+http-equiv=["']?\s*(x-frame-options|content-security-policy)\s*["']?[^>]*>/gi,
package/dist/vite.js CHANGED
@@ -3,8 +3,20 @@ import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
 
5
5
  // src/visualEditorProxyPlugin.ts
6
- var popupHideCss = `<style id="__ce_popup_hide">#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#onetrust-consent-sdk,#onetrust-banner-sdk,.cc-window,.cc-banner,.cc-overlay,#cookie-notice,#cookie-banner,#cookie-consent,.cookie-notice,.cookie-banner,.cookie-consent,.cookie-popup,.cookie-bar,.cookie-message,.cookie-alert,.gdpr-banner,.gdpr-consent,.gdpr-popup,.gdpr-overlay,#gdpr-consent,#gdpr-banner,.consent-banner,.consent-popup,.consent-overlay,#consent-banner,#consent-popup,[class*="cookie-consent"],[class*="cookie-banner"],[class*="cookie-notice"],[class*="CookieConsent"],[class*="CookieBanner"],[id*="cookie-consent"],[id*="cookie-banner"],[id*="cookie-notice"],[aria-label*="cookie" i],[aria-label*="consent" i],.klaro,.klaro .cookie-modal,#usercentrics-root,.trustarc-banner,#truste-consent-track,#hs-eu-cookie-confirmation,.osano-cm-window,.osano-cm-dialog,.evidon-banner,#_evidon_banner,.js-cookie-consent,.cookie-disclaimer,.shopify-section-cookies,#shopify-section-cookies,#shopify-pc__banner,#shopify-pc__modal,.privacy-banner,.privacy-popup,[data-testid="cookie-banner"],[data-testid="consent-banner"],.amgdprcookie-bar-container,[data-amcookie-js="bar"],.amgdprjs-bar-template,.amgdprcookie-modal-container,.amgdprcookie-modal-overlay,#cmplz-cookiebanner-container,.cmplz-cookiebanner,#iubenda-cs-banner,.iubenda-cs-container,#qc-cmp2-container,.qc-cmp2-consent-info,#didomi-host,.didomi-popup-container,.didomi-notice,#termly-code-snippet-support,[class*="termly"],[class*="gdprcookie"],[class*="amgdpr"],[id*="gdpr-cookie"],[class*="cookie-modal"],[id*="cookie-modal"],[class*="cookieConsent"],[id*="cookieConsent"],.klaviyo-form,.klaviyo-modal,.klaviyo-popup,[class*="klaviyo"],.privy-popup,.privy-flyout,[id*="privy"],#PopupSignupForm,.popup-signup,.newsletter-popup,.newsletter-modal,[class*="newsletter-popup"],[class*="newsletter-modal"],[id*="newsletter-popup"],.email-popup,.email-modal,[class*="email-popup"],[class*="email-modal"],.signup-popup,.signup-modal,[class*="signup-popup"],[class*="signup-modal"],.subscribe-popup,.subscribe-modal,[class*="subscribe-popup"],#mc_embed_signup,.mc-modal,.mc-banner,.mc-closeModal,.omniconvert-popup,[class*="omniconvert"],.optinmonster-popup,[id*="om-"][class*="campaign"],.sumo-overlay,.sumome-overlay,[class*="sumome"],.hustle-modal,.hustle-popup,[class*="hustle-"],.popup-overlay,.popup-modal,.modal-overlay,[class*="exit-intent"],[class*="exitintent"],.wheelio-popup,[class*="wheelio"],.spin-wheel-popup,.justuno-popup,[class*="justuno"],.wisepops,.wisepops-overlay,[class*="wisepops"],.elegantmodal,.elegant-popup,#zipify-popup,[class*="zipify"],.age-gate,.age-verification,.age-popup,[class*="age-gate"],[class*="age-verif"],[class*="popup-overlay"],[class*="modal-overlay"],[class*="popup-backdrop"]{display:none!important;visibility:hidden!important;opacity:0!important;pointer-events:none!important;height:0!important;overflow:hidden!important;}body.klaviyo-open,body.modal-open,body.popup-open,body.no-scroll,body.noscroll{overflow:auto!important;position:static!important;}</style>`;
7
- var consentAllowCss = `<style id="__ce_consent_allow">#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#onetrust-consent-sdk,#onetrust-banner-sdk,.cc-window,.cc-banner,.cc-overlay,#cookie-notice,#cookie-banner,#cookie-consent,.cookie-notice,.cookie-banner,.cookie-consent,.cookie-popup,.cookie-bar,.cookie-message,.cookie-alert,.gdpr-banner,.gdpr-consent,.gdpr-popup,.gdpr-overlay,#gdpr-consent,#gdpr-banner,.consent-banner,.consent-popup,.consent-overlay,#consent-banner,#consent-popup,[class*="cookie-consent"],[class*="cookie-banner"],[class*="cookie-notice"],[class*="CookieConsent"],[class*="CookieBanner"],[id*="cookie-consent"],[id*="cookie-banner"],[id*="cookie-notice"],[aria-label*="cookie" i],[aria-label*="consent" i],.klaro,.klaro .cookie-modal,#usercentrics-root,.trustarc-banner,#truste-consent-track,#hs-eu-cookie-confirmation,.osano-cm-window,.osano-cm-dialog,.evidon-banner,#_evidon_banner,.js-cookie-consent,.cookie-disclaimer,.shopify-section-cookies,#shopify-section-cookies,#shopify-pc__banner,#shopify-pc__modal,.privacy-banner,.privacy-popup,[data-testid="cookie-banner"],[data-testid="consent-banner"],.amgdprcookie-bar-container,[data-amcookie-js="bar"],.amgdprjs-bar-template,.amgdprcookie-modal-container,.amgdprcookie-modal-overlay,#cmplz-cookiebanner-container,.cmplz-cookiebanner,#iubenda-cs-banner,.iubenda-cs-container,#qc-cmp2-container,.qc-cmp2-consent-info,#didomi-host,.didomi-popup-container,.didomi-notice,#termly-code-snippet-support,[class*="termly"],[class*="gdprcookie"],[class*="amgdpr"],[id*="gdpr-cookie"],[class*="cookie-modal"],[id*="cookie-modal"],[class*="cookieConsent"],[id*="cookieConsent"]{display:revert!important;visibility:visible!important;opacity:1!important;pointer-events:auto!important;height:auto!important;overflow:visible!important;}</style>`;
6
+ var iframeAlwaysShowCss = `<style id="__ce_force_show">#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#onetrust-consent-sdk,#onetrust-banner-sdk,.cc-window,.cc-banner,.cc-overlay,#cookie-notice,#cookie-banner,#cookie-consent,.cookie-notice,.cookie-banner,.cookie-consent,.cookie-popup,.cookie-bar,.cookie-message,.cookie-alert,.gdpr-banner,.gdpr-consent,.gdpr-popup,.gdpr-overlay,#gdpr-consent,#gdpr-banner,.consent-banner,.consent-popup,.consent-overlay,#consent-banner,#consent-popup,[class*="cookie-consent"],[class*="cookie-banner"],[class*="cookie-notice"],[class*="CookieConsent"],[class*="CookieBanner"],[id*="cookie-consent"],[id*="cookie-banner"],[id*="cookie-notice"],[aria-label*="cookie" i],[aria-label*="consent" i],.klaro,.klaro .cookie-modal,#usercentrics-root,.trustarc-banner,#truste-consent-track,#hs-eu-cookie-confirmation,.osano-cm-window,.osano-cm-dialog,.evidon-banner,#_evidon_banner,.js-cookie-consent,.cookie-disclaimer,.shopify-section-cookies,#shopify-section-cookies,#shopify-pc__banner,#shopify-pc__modal,.privacy-banner,.privacy-popup,[data-testid="cookie-banner"],[data-testid="consent-banner"],.amgdprcookie-bar-container,[data-amcookie-js="bar"],.amgdprjs-bar-template,.amgdprcookie-modal-container,.amgdprcookie-modal-overlay,#cmplz-cookiebanner-container,.cmplz-cookiebanner,#iubenda-cs-banner,.iubenda-cs-container,#qc-cmp2-container,.qc-cmp2-consent-info,#didomi-host,.didomi-popup-container,.didomi-notice,#termly-code-snippet-support,[class*="termly"],[class*="gdprcookie"],[class*="amgdpr"],[id*="gdpr-cookie"],[class*="cookie-modal"],[id*="cookie-modal"],[class*="cookieConsent"],[id*="cookieConsent"]{display:revert!important;visibility:visible!important;opacity:1!important;pointer-events:auto!important;height:auto!important;max-height:none!important;overflow:visible!important;}</style>`;
7
+ var iframeAlwaysShowCssGuardScript = `<script id="__ce_force_show_guard">(function(){try{
8
+ function ensureForceShowStyleLast(){
9
+ var style=document.getElementById("__ce_force_show");
10
+ if(!style||!document.head)return;
11
+ if(document.head.lastElementChild!==style){
12
+ document.head.appendChild(style);
13
+ }
14
+ }
15
+ ensureForceShowStyleLast();
16
+ var mo=new MutationObserver(function(){ensureForceShowStyleLast();});
17
+ if(document.head){mo.observe(document.head,{childList:true});}
18
+ window.addEventListener("beforeunload",function(){try{mo.disconnect();}catch(_){}});}
19
+ catch(_){}})();</script>`;
8
20
  var AI_SYSTEM_PROMPT = `You are a CRO expert. Return JSON only with: hypothesis, mutations, summary. Keep 2-6 precise mutations and only valid selectors from snapshot.`;
9
21
  var BRIDGE_SCRIPT = `(function(){if(window.__CONVERSION_BRIDGE_LOADED__)return;window.__CONVERSION_BRIDGE_LOADED__=true;var CHANNEL='conversion-editor';var highlightEl=null;function send(payload){window.parent.postMessage({channel:CHANNEL,payload:payload},'*');}function qs(selector){try{return document.querySelector(selector);}catch(_){return null;}}function getSelector(el){if(!el||el.nodeType!==1)return'';if(el.id)return'#'+CSS.escape(el.id);var parts=[];var current=el;var depth=0;while(current&&current.nodeType===1&&depth<5){var part=current.tagName.toLowerCase();if(current.classList&&current.classList.length){part+='.'+Array.from(current.classList).slice(0,2).map(function(c){return CSS.escape(c);}).join('.');}var parent=current.parentElement;if(parent){var siblings=Array.from(parent.children).filter(function(s){return s.tagName===current.tagName;});if(siblings.length>1)part+=':nth-of-type('+(siblings.indexOf(current)+1)+')';}parts.unshift(part);current=parent;depth+=1;}return parts.join(' > ');}function payloadOf(el){var rect=el.getBoundingClientRect();var style=window.getComputedStyle(el);return{selector:getSelector(el),tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,500),computedStyles:{color:style.color,backgroundColor:style.backgroundColor,fontSize:style.fontSize,fontWeight:style.fontWeight,lineHeight:style.lineHeight,display:style.display},rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}function applyMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.value;break;case'setText':el.textContent=m.value;break;case'setHTML':el.innerHTML=m.value;break;case'setAttribute':if(m.property)el.setAttribute(m.property,m.value);break;case'hide':el.style.display='none';break;case'show':el.style.display='';break;case'insertHTML':if(m.position)el.insertAdjacentHTML(m.position,m.value||'');break;case'insertSection':if(m.position)el.insertAdjacentHTML(m.position,m.sectionHtml||m.value||'');break;case'reorderElement':if(!m.targetSelector)break;var target=qs(m.targetSelector);if(!target||!target.parentNode||!el.parentNode)break;if(m.insertPosition==='before')target.parentNode.insertBefore(el,target);else target.parentNode.insertBefore(el,target.nextSibling);break;default:break;}}function revertMutation(m){var el=qs(m.selector);if(!el)return;switch(m.action){case'setStyle':if(m.property)el.style[m.property]=m.previous||'';break;case'setText':el.textContent=m.previous||'';break;case'setHTML':el.innerHTML=m.previous||'';break;case'setAttribute':if(m.property){if(m.previous!=null)el.setAttribute(m.property,m.previous);else el.removeAttribute(m.property);}break;case'hide':case'show':el.style.display=m.previous||'';break;default:break;}}function captureSnapshot(){var elements=Array.from(document.querySelectorAll('h1,h2,h3,p,button,a,input,select,img,section,div')).slice(0,700).map(function(el){var rect=el.getBoundingClientRect();var cs=window.getComputedStyle(el);return{selector:getSelector(el),parentSelector:el.parentElement?getSelector(el.parentElement):null,tagName:el.tagName.toLowerCase(),textContent:(el.textContent||'').trim().slice(0,300),attributes:Array.from(el.attributes||[]).reduce(function(acc,a){acc[a.name]=a.value;return acc;},{}),computedStyles:{color:cs.color,backgroundColor:cs.backgroundColor,fontSize:cs.fontSize,fontWeight:cs.fontWeight,display:cs.display},childrenCount:el.children?el.children.length:0,aboveFold:rect.top<window.innerHeight,depth:(function(){var d=0,p=el.parentElement;while(p&&d<25){d++;p=p.parentElement;}return d;})()};});send({type:'snapshotCaptured',snapshot:{url:window.location.href,title:document.title,viewport:{width:window.innerWidth,height:window.innerHeight},elements:elements}});}function captureTree(){function toNode(el,depth){var rect=el.getBoundingClientRect();var tag=el.tagName.toLowerCase();var type='generic';if(tag==='section')type='section';else if(tag==='img')type='image';else if(tag==='a')type='link';else if(tag==='button')type='button';else if(tag==='form')type='form';else if(['p','h1','h2','h3','h4','h5','h6','span'].indexOf(tag)>=0)type='text';else if(['div','main','article','aside','header','footer','nav'].indexOf(tag)>=0)type='container';var children=Array.from(el.children||[]).slice(0,50).map(function(c){return toNode(c,depth+1);});return{id:getSelector(el),selector:getSelector(el),tagName:tag,label:(el.getAttribute('aria-label')||el.getAttribute('id')||el.className||tag).toString().slice(0,80),type:type,children:children,depth:depth,isAboveFold:rect.top<window.innerHeight,rect:{top:rect.top,left:rect.left,width:rect.width,height:rect.height}};}send({type:'pageTreeCaptured',tree:[toNode(document.body,0)]});}window.addEventListener('message',function(e){var msg=e.data;if(!msg||msg.channel!==CHANNEL||!msg.payload)return;var p=msg.payload;switch(p.type){case'ping':send({type:'pong'});break;case'applyMutation':applyMutation(p.mutation);break;case'applyMutationBatch':(p.mutations||[]).forEach(applyMutation);break;case'revert':revertMutation(p.mutation);break;case'clearAllMutations':window.location.reload();break;case'captureSnapshot':captureSnapshot();break;case'validateSelectors':send({type:'selectorsValidated',results:(p.selectors||[]).map(function(s){var el=qs(s);return{selector:s,found:!!el,tagName:el?el.tagName.toLowerCase():null};})});break;case'scrollToElement':case'selectElement':var target=qs(p.selector);if(target){target.scrollIntoView({behavior:'smooth',block:'center'});send({type:'elementSelected',element:payloadOf(target)});}break;case'hoverElement':var h=qs(p.selector);if(h){if(highlightEl)highlightEl.style.outline='';h.style.outline='2px solid #3b82f6';highlightEl=h;}break;case'capturePageTree':captureTree();break;default:break;}});document.addEventListener('click',function(e){var el=e.target;if(!(el instanceof Element))return;send({type:'elementSelected',element:payloadOf(el)});},true);send({type:'bridgeReady'});})();`;
10
22
  function buildVvvebEditorHtml() {
@@ -246,7 +258,7 @@ html,body{height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFo
246
258
  /* \u2500\u2500 DOM tree (Elements tab) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
247
259
  .dt-tree{font-size:11px;padding:0px 0 0px 20px;user-select:none}
248
260
  .dt-row{
249
- display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
261
+ width:fit-content;display:flex;align-items:center;gap:2px;min-height:26px;padding:2px 8px 2px 4px;
250
262
  cursor:pointer;color:var(--text-2);border-radius:4px;margin:0 4px
251
263
  }
252
264
  .dt-row:hover{background:var(--bg-hover);color:var(--text)}
@@ -672,10 +684,10 @@ select.pr-inp{cursor:pointer;background:#fff}
672
684
  <button class="tb-dk-btn" id="btn-mode-nav" onclick="setMode('navigate')" title="Navigate mode \u2014 interact with page normally"><i class="bi bi-magic"></i></button>
673
685
  <button class="tb-dk-btn" title="Comments"><i class="bi bi-chat-dots"></i></button>
674
686
  </div>
675
- <!-- btn-close: hidden visually, kept for JS event listener -->
676
- <button id="btn-close" style="display:none" title="Close editor"></button>
677
687
  <button class="tb-sim-btn" id="btn-simulate" onclick="simulateExperiment()"><i class="bi bi-lightning-charge-fill"></i> Simulate</button>
678
- <button class="tb-fin-btn" id="btn-save">Finalize</button>
688
+ <button class="tb-fin-btn" id="btn-save">Save Changes</button>
689
+ <!-- btn-close: kept for JS event listener -->
690
+ <button class="tb-fin-btn" id="btn-close">Close</button>
679
691
  </div>
680
692
 
681
693
  <!-- url-bar: hidden, kept for JS compatibility -->
@@ -1094,6 +1106,8 @@ var iframeContentNavGen = 0;
1094
1106
  var iframeContentApplyTimer = null;
1095
1107
  var iframeEarlyGranularPrimedForGen = null;
1096
1108
  var iframeEarlySyncPrimedForGen = null;
1109
+ var iframeEarlyDomSignature = '';
1110
+ var iframeEarlyDomSignatureNavGen = 0;
1097
1111
  /** insert/reorder entries are applied from early granular + full apply \u2014 skip exact duplicates per iframe nav */
1098
1112
  var appliedStructuralChangesetKeys = {};
1099
1113
  var isDirty = false;
@@ -1105,6 +1119,7 @@ var selectedEl = null;
1105
1119
  var selectedElFingerprint = '';
1106
1120
  var selectedElRecoverMisses = 0;
1107
1121
  var MAX_SELECTED_RECOVER_MISSES = 12;
1122
+ var hoveredTreeEl = null;
1108
1123
  var isDeselectingSelection = false;
1109
1124
  var suppressClickUntil = 0;
1110
1125
  var dragAttachDoc = null;
@@ -1118,6 +1133,7 @@ var iframeSyncAttempts = 0;
1118
1133
  var selectionScrollWin = null;
1119
1134
  var selectionResizeBound = false;
1120
1135
  var clickAttachDoc = null;
1136
+ var hoverAttachDoc = null;
1121
1137
  var changeObserver = null;
1122
1138
  var changeObserverDoc = null;
1123
1139
  /** Incremented while applying changesets / selection chrome so MutationObserver does not mark dirty */
@@ -1330,6 +1346,7 @@ function setMode(mode) {
1330
1346
  document.getElementById('btn-mode-editor').classList.toggle('active', mode === 'editor');
1331
1347
  document.getElementById('btn-mode-nav').classList.toggle('active', mode === 'navigate');
1332
1348
  if (mode === 'navigate') {
1349
+ clearTreeHoverHighlight();
1333
1350
  setDragHandleActive(false);
1334
1351
  deselectElement();
1335
1352
  } else if (mode === 'editor') {
@@ -1713,7 +1730,7 @@ function softReloadEditorIframe() {
1713
1730
  resetIframeBindings();
1714
1731
  setIframePageLoadingUi(true);
1715
1732
  iframe.src = appendIframeReloadBust(src);
1716
- startIframeContentApplyWatcher(navGen);
1733
+ startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
1717
1734
  scheduleDomTreeRefresh();
1718
1735
  }
1719
1736
 
@@ -2192,6 +2209,27 @@ function bodyHasFirstPaintChild(body) {
2192
2209
  return false;
2193
2210
  }
2194
2211
 
2212
+ function computeIframeDomSignature(doc) {
2213
+ if (!doc || !doc.body) return '';
2214
+ var body = doc.body;
2215
+ var tags = [];
2216
+ var walker = null;
2217
+ try {
2218
+ walker = doc.createTreeWalker(body, NodeFilter.SHOW_ELEMENT, null);
2219
+ } catch(_) {
2220
+ walker = null;
2221
+ }
2222
+ var count = 0;
2223
+ if (walker) {
2224
+ while (walker.nextNode() && count < 400) {
2225
+ var node = walker.currentNode;
2226
+ tags.push((node && node.tagName) ? String(node.tagName).toLowerCase() : '');
2227
+ count += 1;
2228
+ }
2229
+ }
2230
+ return String(body.children ? body.children.length : 0) + '|' + String(count) + '|' + tags.join(',');
2231
+ }
2232
+
2195
2233
  /** True when at least one granular changeset selector already matches (nested content painted). */
2196
2234
  function granularAnySelectorMatches(doc, cs) {
2197
2235
  if (!doc || !cs || !cs.length) return false;
@@ -2346,6 +2384,7 @@ function resetIframeBindings() {
2346
2384
  appliedStructuralChangesetKeys = {};
2347
2385
  clickAttachDoc = null;
2348
2386
  dragAttachDoc = null;
2387
+ hoverAttachDoc = null;
2349
2388
  changeObserverDoc = null;
2350
2389
  clearPendingGranularChangesets();
2351
2390
  if (changeObserver) {
@@ -2374,7 +2413,7 @@ function loadPage(proxyUrl) {
2374
2413
  iframe.style.display = 'block';
2375
2414
  setIframePageLoadingUi(true);
2376
2415
  iframe.src = appendIframeReloadBust(proxyUrl);
2377
- startIframeContentApplyWatcher(navGen);
2416
+ startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
2378
2417
  scheduleDomTreeRefresh();
2379
2418
  }
2380
2419
 
@@ -2446,7 +2485,7 @@ function switchVariation(varId) {
2446
2485
  iframe.src = appendIframeReloadBust(src);
2447
2486
  // Do not sync here: the document is still the previous navigation until the
2448
2487
  // iframe load event; an eager sync attached observers / DOM tree to the wrong document.
2449
- startIframeContentApplyWatcher(navGen);
2488
+ startIframeContentApplyWatcher(navGen, iframe.contentDocument || null);
2450
2489
  scheduleDomTreeRefresh();
2451
2490
  }
2452
2491
  } catch(_) {}
@@ -2963,10 +3002,12 @@ function reapplyActiveVariationGranular(iframeDoc) {
2963
3002
  }
2964
3003
 
2965
3004
  /** Poll iframe document during navigation; apply granular edits as soon as DOM can match selectors. */
2966
- function startIframeContentApplyWatcher(navGen) {
3005
+ function startIframeContentApplyWatcher(navGen, prevDocRef) {
2967
3006
  stopIframeContentApplyWatcher();
2968
3007
  iframeEarlyGranularPrimedForGen = null;
2969
3008
  iframeEarlySyncPrimedForGen = null;
3009
+ iframeEarlyDomSignature = '';
3010
+ iframeEarlyDomSignatureNavGen = navGen;
2970
3011
  var iframe = document.getElementById('iframeId');
2971
3012
  iframeContentApplyTimer = setInterval(function() {
2972
3013
  if (navGen !== iframeContentNavGen) {
@@ -2976,6 +3017,7 @@ function startIframeContentApplyWatcher(navGen) {
2976
3017
  try {
2977
3018
  var doc = iframe.contentDocument;
2978
3019
  if (!doc || !doc.body) return;
3020
+ if (prevDocRef && doc === prevDocRef) return;
2979
3021
  var docUrl = '';
2980
3022
  try { docUrl = String(doc.URL || ''); } catch(_) {}
2981
3023
  if (docUrl === 'about:blank') return;
@@ -3010,6 +3052,7 @@ function startIframeContentApplyWatcher(navGen) {
3010
3052
  }
3011
3053
  if (iframeEarlySyncPrimedForGen !== navGen) {
3012
3054
  iframeEarlySyncPrimedForGen = navGen;
3055
+ iframeEarlyDomSignature = computeIframeDomSignature(doc);
3013
3056
  syncIframeInteractions('iframe-early-paint');
3014
3057
  }
3015
3058
  }
@@ -3079,6 +3122,8 @@ function injectIframeSelectionStyles(doc) {
3079
3122
  st.textContent =
3080
3123
  '.vve-selected{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
3081
3124
  'box-shadow:0 0 0 2px rgba(99,102,241,.28),inset 0 0 0 1px rgba(99,102,241,.18)!important;}' +
3125
+ '.vve-tree-hover{outline:2px solid #6366f1!important;outline-offset:2px!important;' +
3126
+ 'box-shadow:0 0 0 2px rgba(99,102,241,.22),inset 0 0 0 1px rgba(99,102,241,.12)!important;}' +
3082
3127
  'html.vve-drag-armed .vve-selected{cursor:grab!important;}' +
3083
3128
  '.vve-dragging{opacity:0.92!important;outline:2px dashed #f59e0b!important;' +
3084
3129
  'outline-offset:2px!important;cursor:grabbing!important;box-shadow:none!important;}';
@@ -3090,6 +3135,47 @@ function injectIframeSelectionStyles(doc) {
3090
3135
  }
3091
3136
  }
3092
3137
 
3138
+ function clearTreeHoverHighlight() {
3139
+ if (!hoveredTreeEl) return;
3140
+ beginSuppressIframeMutationDirty();
3141
+ try {
3142
+ if (hoveredTreeEl.classList) hoveredTreeEl.classList.remove('vve-tree-hover');
3143
+ } catch(_) {
3144
+ } finally {
3145
+ endSuppressIframeMutationDirty();
3146
+ hoveredTreeEl = null;
3147
+ }
3148
+ }
3149
+
3150
+ function setTreeHoverHighlight(el) {
3151
+ if (!el || el.nodeType !== 1) {
3152
+ clearTreeHoverHighlight();
3153
+ return;
3154
+ }
3155
+ if (hoveredTreeEl === el) return;
3156
+ clearTreeHoverHighlight();
3157
+ beginSuppressIframeMutationDirty();
3158
+ try {
3159
+ if (el.classList) el.classList.add('vve-tree-hover');
3160
+ hoveredTreeEl = el;
3161
+ } catch(_) {
3162
+ hoveredTreeEl = null;
3163
+ } finally {
3164
+ endSuppressIframeMutationDirty();
3165
+ }
3166
+ }
3167
+
3168
+ function isTreeHoverOnlyClassMutation(mutation) {
3169
+ if (!mutation || mutation.type !== 'attributes' || mutation.attributeName !== 'class') return false;
3170
+ var oldClass = String(mutation.oldValue || '');
3171
+ var target = mutation.target;
3172
+ var nextClass = '';
3173
+ try {
3174
+ nextClass = target && typeof target.className === 'string' ? target.className : '';
3175
+ } catch(_) {}
3176
+ return oldClass.indexOf('vve-tree-hover') >= 0 || String(nextClass).indexOf('vve-tree-hover') >= 0;
3177
+ }
3178
+
3093
3179
  function setDragHandleActive(on) {
3094
3180
  dragHandleActive = !!on;
3095
3181
  var b = document.getElementById('sf-drag');
@@ -3410,6 +3496,9 @@ function renderDomTree(filterRaw) {
3410
3496
  if (e.target.closest && e.target.closest('.dt-chev')) return;
3411
3497
  selectElementFromTree(el);
3412
3498
  };
3499
+ row.onmouseenter = function() {
3500
+ setTreeHoverHighlight(el);
3501
+ };
3413
3502
  root.appendChild(row);
3414
3503
 
3415
3504
  if (!hasKids || collapsed) return;
@@ -3428,6 +3517,9 @@ function renderDomTree(filterRaw) {
3428
3517
  ? '<div class="dt-muted">No elements match your search.</div>'
3429
3518
  : '<div class="dt-muted">No visible elements yet.</div>';
3430
3519
  }
3520
+ root.onmouseleave = function() {
3521
+ clearTreeHoverHighlight();
3522
+ };
3431
3523
  }
3432
3524
 
3433
3525
  // \u2500\u2500 Utility helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -3847,12 +3939,25 @@ function renderRightPanel(el) {
3847
3939
  var sel = buildSelector(el);
3848
3940
  bindings.forEach(function(b){
3849
3941
  var inp = document.getElementById(b[0]);
3850
- if (inp) inp.addEventListener('input', function(){
3942
+ if (inp) {
3943
+ var onValueChange = function() {
3851
3944
  // Read the original value BEFORE applying the change so we can revert later
3852
3945
  var orig = getOriginalValue(b[0], el);
3853
3946
  b[1](inp.value);
3854
- logChange(sel, b[0], inp.value, el, orig);
3855
- });
3947
+ var valueToLog = inp.value;
3948
+ try {
3949
+ console.log('[V2] input changed', {
3950
+ inputId: b[0],
3951
+ rawValue: inp.value,
3952
+ appliedValue: valueToLog,
3953
+ selector: sel,
3954
+ });
3955
+ } catch(_) {}
3956
+ logChange(sel, b[0], valueToLog, el, orig);
3957
+ };
3958
+ inp.addEventListener('input', onValueChange);
3959
+ inp.addEventListener('change', onValueChange);
3960
+ }
3856
3961
  });
3857
3962
  }
3858
3963
 
@@ -4115,6 +4220,32 @@ function attachClickHandler() {
4115
4220
  } catch(_) {}
4116
4221
  }
4117
4222
 
4223
+ function attachIframeHoverHandler() {
4224
+ try {
4225
+ var iframe = document.getElementById('iframeId');
4226
+ var doc = iframe && iframe.contentDocument;
4227
+ if (!doc || !doc.body) return;
4228
+ if (hoverAttachDoc === doc) return;
4229
+ hoverAttachDoc = doc;
4230
+ doc.addEventListener('mousemove', function(e) {
4231
+ if (currentMode !== 'editor') {
4232
+ clearTreeHoverHighlight();
4233
+ return;
4234
+ }
4235
+ var target = e.target;
4236
+ if (!target || target === doc.body || target === doc.documentElement) {
4237
+ clearTreeHoverHighlight();
4238
+ return;
4239
+ }
4240
+ setTreeHoverHighlight(target);
4241
+ }, true);
4242
+ doc.addEventListener('mouseout', function(e) {
4243
+ if (e.relatedTarget) return;
4244
+ clearTreeHoverHighlight();
4245
+ }, true);
4246
+ } catch(_) {}
4247
+ }
4248
+
4118
4249
  function attachChangeObserver() {
4119
4250
  try {
4120
4251
  var iframe = document.getElementById('iframeId');
@@ -4127,6 +4258,14 @@ function attachChangeObserver() {
4127
4258
  changeObserverDoc = null;
4128
4259
  }
4129
4260
  changeObserver = new MutationObserver(function(mutations) {
4261
+ var hasMeaningfulMutation = false;
4262
+ for (var mi = 0; mi < mutations.length; mi++) {
4263
+ if (!isTreeHoverOnlyClassMutation(mutations[mi])) {
4264
+ hasMeaningfulMutation = true;
4265
+ break;
4266
+ }
4267
+ }
4268
+ if (!hasMeaningfulMutation) return;
4130
4269
  // Dirty state is derived from changesets baseline + stateChanges (not raw DOM mutations).
4131
4270
  // Host scripts can replace selected nodes every few frames (e.g. A/B tool observers).
4132
4271
  // Keep selection sticky by re-resolving from fingerprint.
@@ -4137,7 +4276,7 @@ function attachChangeObserver() {
4137
4276
  updateSelectionToolbar();
4138
4277
  });
4139
4278
  changeObserver.observe(doc.body, {
4140
- childList: true, subtree: true, attributes: true, characterData: true
4279
+ childList: true, subtree: true, attributes: true, characterData: true, attributeOldValue: true
4141
4280
  });
4142
4281
  changeObserverDoc = doc;
4143
4282
  } catch(_) {}
@@ -4163,6 +4302,7 @@ function syncIframeInteractions(reason) {
4163
4302
  injectIframeSelectionStyles(doc);
4164
4303
  refreshPersistentChangesetStyleTagForActiveVariation();
4165
4304
  attachClickHandler();
4305
+ attachIframeHoverHandler();
4166
4306
  attachDragReposition();
4167
4307
  attachChangeObserver();
4168
4308
  startConsistencyWatchdog(doc);
@@ -4521,10 +4661,9 @@ window.addEventListener('load', function() {
4521
4661
  var iframe = document.getElementById('iframeId');
4522
4662
  iframe.addEventListener('load', function() {
4523
4663
  if (!iframe.src || iframe.src === 'about:blank' || iframe.src === window.location.href) return;
4524
- // New iframe navigation: always drop bindings tied to the previous document.
4525
- resetIframeBindings();
4526
4664
  var doc = iframe.contentDocument;
4527
4665
  if (!doc) {
4666
+ resetIframeBindings();
4528
4667
  syncIframeInteractions('iframe-load-no-doc');
4529
4668
  return;
4530
4669
  }
@@ -4533,14 +4672,32 @@ window.addEventListener('load', function() {
4533
4672
  // Stale events: src may already be the proxy URL while the document is still
4534
4673
  // about:blank (e.g. src cleared then reset to force reload). Ask sync path to retry.
4535
4674
  if (docUrl === 'about:blank') {
4675
+ resetIframeBindings();
4536
4676
  syncIframeInteractions('iframe-load-about-blank');
4537
4677
  return;
4538
4678
  }
4679
+ // If early-paint and final load DOM signatures match, avoid a second full apply/reset
4680
+ // that steals focus from sidebar controls while the page is still stabilizing.
4681
+ var shouldRefreshOnFinalLoad = true;
4682
+ if (
4683
+ iframeEarlyDomSignatureNavGen === iframeContentNavGen &&
4684
+ iframeEarlySyncPrimedForGen === iframeContentNavGen &&
4685
+ iframeEarlyDomSignature
4686
+ ) {
4687
+ var finalDomSignature = computeIframeDomSignature(doc);
4688
+ if (finalDomSignature && finalDomSignature === iframeEarlyDomSignature) {
4689
+ shouldRefreshOnFinalLoad = false;
4690
+ }
4691
+ }
4692
+ if (clickAttachDoc !== doc || dragAttachDoc !== doc || changeObserverDoc !== doc || hoverAttachDoc !== doc) {
4693
+ resetIframeBindings();
4694
+ }
4539
4695
  attachIframeLoadingUntilComplete(iframe);
4540
4696
  if (doc.body && iframeDocMatchesNavigatedSrc(iframe, doc)) {
4541
4697
  stopIframeContentApplyWatcher();
4542
- deselectElement();
4543
- applyActiveVariationHtml();
4698
+ if (shouldRefreshOnFinalLoad) {
4699
+ applyActiveVariationHtml();
4700
+ }
4544
4701
  }
4545
4702
  // Always attempt sync; it has its own readiness checks + retry loop.
4546
4703
  syncIframeInteractions('iframe-load');
@@ -4882,9 +5039,12 @@ function createVisualEditorMiddleware(options) {
4882
5039
  }
4883
5040
  );
4884
5041
  if (html.includes("</head>")) {
4885
- html = html.replace("</head>", `${popupHideCss}
4886
- ${consentAllowCss}
4887
- </head>`);
5042
+ html = html.replace(
5043
+ "</head>",
5044
+ `${iframeAlwaysShowCss}
5045
+ ${iframeAlwaysShowCssGuardScript}
5046
+ </head>`
5047
+ );
4888
5048
  }
4889
5049
  html = html.replace(
4890
5050
  /<meta[^>]+http-equiv=["']?\s*(x-frame-options|content-security-policy)\s*["']?[^>]*>/gi,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@accelerated-agency/visual-editor",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "private": false,
5
5
  "description": "Conversion visual editor as a reusable React package",
6
6
  "type": "module",
@@ -25,6 +25,7 @@
25
25
  "dev": "tsup --watch",
26
26
  "clean": "rm -rf dist",
27
27
  "build": "tsup",
28
+ "local:build": "tsup && yarn --cwd ../codebase-server clean && yarn --cwd ../codebase-server dev",
28
29
  "watch": "tsup --watch"
29
30
  },
30
31
  "peerDependencies": {