commerce-kit 0.36.11 → 0.36.12
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/browser.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{useEffect as $,useRef as
|
|
2
|
-
`)},Y=e=>{let t=e;for(;t;){if(t instanceof HTMLElement&&t.dataset.ynsFeedbackUi==="true")return!0;t=t.parentElement}return!1},te=new Set(["HTML","HEAD","SCRIPT","STYLE","NOSCRIPT"]),ne=1e4,oe=e=>{let t=new Date(e),n=t.getTime()-Date.now(),o=t.toLocaleString(void 0,{weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"});if(n<=0)return`Any moment now (estimated by ${o})`;let s=Math.ceil(n/6e4);if(s<60)return`~${s} minutes (by ${o})`;let d=Math.round(n/36e5);if(d<24)return`~${d} ${d===1?"hour":"hours"} (by ${o})`;let l=Math.round(n/864e5);return`~${l} ${l===1?"day":"days"} (by ${o})`};function ie(){let[e,t]=C(null),[n,o]=C(!0),[s,d]=C(!1),[l,r]=C(null),[c,g]=C(null),[S,w]=C(!1),[I,y]=C(!1),u=j(null);$(()=>{if(u.current=J(),!u.current){o(!1);return}let i=!1,a=new AbortController,h=async()=>{try{let P=await fetch(`${u.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include",signal:a.signal});if(i)return;if(!P.ok){t(null);return}let k=await P.json();if(i)return;t(k),k.sessionStatus==="done"&&window.location.reload()}catch{i||t(null)}finally{i||o(!1)}};h();let f=window.setInterval(h,ne);return()=>{i=!0,a.abort(),window.clearInterval(f)}},[]),$(()=>{!e||e.canComment||(d(!1),r(null),w(!1),g(null))},[e]),$(()=>{if(s)return document.body.style.cursor="crosshair",()=>{document.body.style.cursor=""}},[s]),$(()=>{if(!s)return;let i=document.createElement("div");i.dataset.ynsFeedbackUi="true",i.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483644","border: 2px dashed #10b981","background: rgba(16, 185, 129, 0.08)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let a=document.createElement("div");a.dataset.ynsFeedbackUi="true",a.textContent="Click to comment",a.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483645","background: #059669","color: #fff","font-size: 11px","font-family: ui-sans-serif, system-ui, sans-serif","padding: 3px 8px","border-radius: 4px","white-space: nowrap","display: none","box-shadow: 0 2px 6px rgba(0,0,0,0.15)"].join(";"),document.documentElement.appendChild(i),document.documentElement.appendChild(a);let h=null,f=k=>{let L=document.elementFromPoint(k.clientX,k.clientY);if(!L||te.has(L.tagName)||Y(L)){i.style.display="none",a.style.display="none",h=null;return}h=L,requestAnimationFrame(()=>{if(h!==L)return;let R=L.getBoundingClientRect();i.style.top=`${R.top}px`,i.style.left=`${R.left}px`,i.style.width=`${R.width}px`,i.style.height=`${R.height}px`,i.style.display="block",a.style.left=`${k.clientX+14}px`,a.style.top=`${k.clientY+14}px`,a.style.display="block"})},P=()=>{i.style.display="none",a.style.display="none",h=null};return document.addEventListener("mousemove",f,!0),document.addEventListener("mouseleave",P),()=>{document.removeEventListener("mousemove",f,!0),document.removeEventListener("mouseleave",P),i.remove(),a.remove()}},[s]),$(()=>{if(!s)return;let i=a=>{let h=a.target;if(!(h instanceof Element)||Y(h))return;a.preventDefault(),a.stopPropagation();let f=h.getBoundingClientRect(),P=f.width>0?(a.clientX-f.left)/f.width:.5,k=f.height>0?(a.clientY-f.top)/f.height:.5;r({cssSelector:Q(h),pagePath:window.location.pathname,surroundingHtml:ee(h),rect:{top:f.top+window.scrollY,left:f.left+window.scrollX,width:f.width,height:f.height},clickX:a.clientX+window.scrollX,clickY:a.clientY+window.scrollY,offsetXRatio:Math.min(1,Math.max(0,P)),offsetYRatio:Math.min(1,Math.max(0,k))}),d(!1)};return document.addEventListener("click",i,{capture:!0}),()=>document.removeEventListener("click",i,{capture:!0})},[s]);let b=async()=>{if(!u.current)return;let i=await fetch(`${u.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include"});if(!i.ok)return;let a=await i.json();t(a)},m=async(i,a)=>{!u.current||!e||!l||!(await fetch(`${u.current}/api/feedback-comments`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({feedbackSessionId:e.feedbackSessionId,content:i,pagePath:l.pagePath,cssSelector:l.cssSelector,surroundingHtml:l.surroundingHtml,offsetXRatio:l.offsetXRatio,offsetYRatio:l.offsetYRatio,...a.length>0?{attachments:a}:{}})})).ok||(r(null),d(!0),await b())},v=async(i,a,h)=>{!u.current||!(await fetch(`${u.current}/api/feedback-comments/${i}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:a,attachments:h})})).ok||(g(null),await b())},E=async i=>{!u.current||!(await fetch(`${u.current}/api/feedback-comments/${i}`,{method:"DELETE",credentials:"include"})).ok||await b()},M=async()=>{if(!(!u.current||!e)&&window.confirm("Finalize this feedback session? You won't be able to add more comments.")){y(!0);try{let i=await fetch(`${u.current}/api/feedback-sessions/${e.feedbackSessionId}/finalize`,{method:"POST",credentials:"include"});if(!i.ok)return;let h=(await i.json().catch(()=>null))?.status??"processing";t(f=>f&&{...f,canComment:!1,sessionStatus:h})}finally{y(!1)}}},z=i=>{if(i.pagePath!==window.location.pathname){window.location.assign(i.pagePath);return}let a=null;try{a=document.querySelector(i.cssSelector)}catch{a=null}a&&(a.scrollIntoView({behavior:"smooth",block:"center"}),g(i.id))};if(n||!e)return null;if(!e.canComment)return e.sessionStatus!=="processing"&&e.sessionStatus!=="in_review"?null:p("div",{"data-yns-feedback-ui":"true",children:p(se,{progress:e.progress,eta:e.eta,status:e.sessionStatus})});let A=e.comments.filter(i=>i.pagePath===window.location.pathname&&i.status!=="done");return x("div",{"data-yns-feedback-ui":"true",children:[A.map((i,a)=>p(le,{pin:i,number:a+1,editing:c===i.id,feedbackSessionId:e.feedbackSessionId,apiBase:u.current,onStartEdit:()=>g(i.id),onCancelEdit:()=>g(null),onSave:(h,f)=>v(i.id,h,f),onRemove:()=>E(i.id)},i.id)),l&&p(de,{pending:l,feedbackSessionId:e.feedbackSessionId,apiBase:u.current,onCancel:()=>{r(null),d(!0)},onSave:(i,a)=>m(i,a)}),S&&p(ce,{comments:e.comments,currentPath:window.location.pathname,onClose:()=>w(!1),onSelect:z}),x("div",{style:fe,children:[p("button",{type:"button",onClick:()=>d(i=>!i),style:s?ge:W,children:s?"Cancel":"Add comment"}),p("button",{type:"button",onClick:()=>w(i=>!i),style:be,children:S?"Hide list":`List (${e.comments.length})`}),p("button",{type:"button",onClick:M,disabled:I,style:ye,children:I?"Finalizing\u2026":"Finalize"}),p("span",{style:he,children:s?"Click any element to comment":`${A.length} on this page`})]})]})}function se({progress:e,eta:t,status:n}){let[,o]=C(0);$(()=>{let r=window.setInterval(()=>o(c=>c+1),6e4);return()=>window.clearInterval(r)},[]);let s=oe(t);return x("div",{style:_e,children:[p("style",{children:re}),x("div",{style:je,children:[p(ae,{}),p("span",{style:Ue,children:"Submitted"}),p("strong",{style:{fontSize:13},children:n==="in_review"?"Feedback under review":"Applying feedback"})]}),x("div",{style:Xe,children:[p("span",{children:"Review progress"}),p("span",{style:{opacity:.7},children:e.label})]}),p("div",{style:De,children:p("div",{style:{...Ye,width:`${e.fillPct}%`}})}),x("p",{style:He,children:["Estimated delivery: ",p("span",{style:{fontWeight:600},children:s})]})]})}var re="@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }";function ae({size:e=14}){return p("span",{"aria-hidden":!0,style:{display:"inline-block",width:e,height:e,border:"2px solid rgba(16, 185, 129, 0.25)",borderTopColor:"#10b981",borderRadius:999,animation:"yns-feedback-spin 0.9s linear infinite"}})}function le({pin:e,number:t,editing:n,feedbackSessionId:o,apiBase:s,onStartEdit:d,onCancelEdit:l,onSave:r,onRemove:c}){let g=me(e.cssSelector,e.offsetXRatio,e.offsetYRatio);return g?x("div",{style:{position:"absolute",top:g.top,left:g.left,zIndex:2147483600,pointerEvents:"auto"},children:[p("button",{type:"button",onClick:d,style:V,title:e.content,children:t}),n&&p(O,{initial:e.content,initialAttachments:e.attachments,feedbackSessionId:o,apiBase:s,onCancel:l,onSave:r,onRemove:c})]}):null}function de({pending:e,feedbackSessionId:t,apiBase:n,onCancel:o,onSave:s}){return x(Oe,{children:[p("div",{style:{position:"absolute",top:e.rect.top+e.rect.height*e.offsetYRatio-12,left:e.rect.left+e.rect.width*e.offsetXRatio-12,zIndex:2147483600,pointerEvents:"none"},children:p("div",{style:V,children:"\u2022"})}),p("div",{style:{position:"absolute",top:e.clickY+10,left:e.clickX+10,zIndex:2147483647},children:p(O,{initial:"",initialAttachments:[],feedbackSessionId:t,apiBase:n,onCancel:o,onSave:s})})]})}function ce({comments:e,currentPath:t,onClose:n,onSelect:o}){let s=new Map;for(let l of e){let r=s.get(l.pagePath)??[];r.push(l),s.set(l.pagePath,r)}let d=Array.from(s.keys()).sort((l,r)=>l===t?-1:r===t?1:l.localeCompare(r));return x("div",{style:Le,children:[x("div",{style:$e,children:[x("strong",{style:{fontSize:14},children:["Comments (",e.length,")"]}),p("button",{type:"button",onClick:n,style:U,children:"Close"})]}),p("div",{style:Ae,children:e.length===0?p("div",{style:Te,children:"No comments yet. Click \u201CAdd comment\u201D to leave one."}):d.map(l=>x("div",{style:{marginBottom:12},children:[p("div",{style:Fe,children:l===t?`${l} \xB7 current`:l}),(s.get(l)??[]).map((r,c)=>x("button",{type:"button",onClick:()=>o(r),style:Me,disabled:r.status==="done"&&!1,children:[p("span",{style:ze,children:c+1}),p("span",{style:Ne,children:r.content.length>140?`${r.content.slice(0,140)}\u2026`:r.content}),r.status==="done"&&p("span",{style:Be,children:"done"})]},r.id))]},l))})]})}var _=5,ue=5*1024*1024,pe=e=>new Promise(t=>{let n=URL.createObjectURL(e),o=new window.Image;o.onload=()=>{URL.revokeObjectURL(n),t({width:o.naturalWidth,height:o.naturalHeight})},o.onerror=()=>{URL.revokeObjectURL(n),t(null)},o.src=n});function O({initial:e,initialAttachments:t,feedbackSessionId:n,apiBase:o,onCancel:s,onSave:d,onRemove:l}){let[r,c]=C(e),[g,S]=C(t),[w,I]=C(!1),[y,u]=C(!1),[b,m]=C(null),v=j(null),E=j(null);$(()=>{let i=v.current;if(!i)return;i.focus();let a=i.value.length;i.setSelectionRange(a,a)},[]);let M=async i=>{i.preventDefault();let a=r.trim();if(a){I(!0);try{await d(a,g)}finally{I(!1)}}},z=async i=>{if(!i||i.length===0||!o)return;m(null);let a=_-g.length;if(a<=0){m(`Max ${_} images per comment`);return}let h=Array.from(i).slice(0,a);for(let f of h){if(!f.type.startsWith("image/")){m(`"${f.name}" is not an image`);return}if(f.size>ue){m(`"${f.name}" exceeds 5 MB`);return}}u(!0);try{let f=await Promise.all(h.map(pe)),P=new FormData;h.forEach((R,T)=>{P.append("file",R),P.append("width",f[T]?.width?String(f[T]?.width):""),P.append("height",f[T]?.height?String(f[T]?.height):"")});let k=await fetch(`${o}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(n)}`,{method:"POST",credentials:"include",body:P});if(!k.ok){let R=await k.json().catch(()=>null);m(R?.error??"Upload failed");return}let L=await k.json();S(R=>[...R,...L.uploads])}catch{m("Upload failed")}finally{u(!1),E.current&&(E.current.value="")}},A=i=>{S(a=>a.filter(h=>h.url!==i))};return x("form",{onSubmit:M,style:Se,children:[p("textarea",{ref:v,value:r,onChange:i=>c(i.target.value),placeholder:"Leave a comment\u2026",style:ve,rows:3}),g.length>0&&p("div",{style:Ce,children:g.map(i=>x("div",{style:ke,children:[p("img",{src:i.url,alt:"",style:Pe}),p("button",{type:"button",onClick:()=>A(i.url),style:Ie,disabled:w||y,"aria-label":"Remove attachment",children:"\xD7"})]},i.url))}),b&&p("div",{style:Re,children:b}),p("input",{ref:E,type:"file",accept:"image/*",multiple:!0,onChange:i=>void z(i.target.files),style:{display:"none"}}),x("div",{style:we,children:[l&&p("button",{type:"button",onClick:()=>l(),style:Ee,disabled:w,children:"Delete"}),p("button",{type:"button",onClick:()=>E.current?.click(),style:U,disabled:w||y||g.length>=_,children:y?"Uploading\u2026":`Attach image${g.length>0?` (${g.length})`:""}`}),p("div",{style:{flex:1}}),p("button",{type:"button",onClick:s,style:U,disabled:w,children:"Cancel"}),p("button",{type:"submit",style:xe,disabled:w||y||!r.trim(),children:w?"Saving\u2026":"Save"})]})]})}function me(e,t,n){let[o,s]=C(null);return $(()=>{let d=()=>{let r=null;try{r=document.querySelector(e)}catch{r=null}if(!r){s(null);return}let c=r.getBoundingClientRect();s({top:c.top+window.scrollY+c.height*n-12,left:c.left+window.scrollX+c.width*t-12})};d();let l=new ResizeObserver(d);try{let r=document.querySelector(e);r&&l.observe(r)}catch{}return window.addEventListener("scroll",d,!0),window.addEventListener("resize",d),()=>{l.disconnect(),window.removeEventListener("scroll",d,!0),window.removeEventListener("resize",d)}},[e,t,n]),o}var fe={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"rgba(17, 17, 17, 0.92)",color:"white",borderRadius:999,boxShadow:"0 8px 24px rgba(0,0,0,0.25)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',fontSize:14,pointerEvents:"auto"},W={border:"none",background:"white",color:"#111",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},ge={...W,background:"#ef4444",color:"white"},be={border:"1px solid rgba(255,255,255,0.4)",background:"transparent",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:500,fontSize:13},ye={border:"none",background:"#10b981",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},he={opacity:.8,fontSize:12},V={width:24,height:24,borderRadius:999,background:"#10b981",color:"white",border:"2px solid white",cursor:"pointer",fontSize:12,fontWeight:700,boxShadow:"0 2px 6px rgba(0,0,0,0.3)",display:"inline-flex",alignItems:"center",justifyContent:"center",padding:0},Se={background:"white",border:"1px solid #e5e7eb",borderRadius:8,padding:12,width:280,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',display:"flex",flexDirection:"column",gap:8,color:"#111"},ve={width:"100%",border:"1px solid #d1d5db",borderRadius:6,padding:8,fontSize:14,resize:"vertical",fontFamily:"inherit",color:"#111",background:"white",boxSizing:"border-box"},we={display:"flex",alignItems:"center",gap:6},X={border:"1px solid transparent",borderRadius:6,padding:"6px 10px",fontSize:13,cursor:"pointer",fontWeight:500},xe={...X,background:"#111",color:"white"},U={...X,background:"transparent",color:"#374151",borderColor:"#d1d5db"},Ee={...X,background:"transparent",color:"#b91c1c",borderColor:"#fecaca"},Ce={display:"flex",flexWrap:"wrap",gap:6},ke={position:"relative",width:56,height:56,borderRadius:6,overflow:"hidden",border:"1px solid #e5e7eb",background:"#f9fafb"},Pe={width:"100%",height:"100%",objectFit:"cover",display:"block"},Ie={position:"absolute",top:2,right:2,width:18,height:18,borderRadius:999,border:"none",background:"rgba(17, 17, 17, 0.85)",color:"white",fontSize:13,lineHeight:"16px",cursor:"pointer",padding:0,display:"inline-flex",alignItems:"center",justifyContent:"center"},Re={color:"#b91c1c",fontSize:12,margin:0},Le={position:"fixed",top:0,right:0,bottom:0,width:360,maxWidth:"92vw",background:"white",borderLeft:"1px solid #e5e7eb",boxShadow:"-12px 0 32px rgba(0,0,0,0.12)",zIndex:2147483646,display:"flex",flexDirection:"column",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111"},$e={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e5e7eb"},Ae={flex:1,overflow:"auto",padding:"12px 12px 24px"},Te={color:"#6b7280",fontSize:13,padding:"24px 8px",textAlign:"center"},Fe={fontSize:11,textTransform:"uppercase",letterSpacing:.5,color:"#6b7280",padding:"4px 4px 6px",wordBreak:"break-all"},Me={display:"flex",alignItems:"flex-start",gap:8,width:"100%",textAlign:"left",background:"white",border:"1px solid #e5e7eb",borderRadius:6,padding:"8px 10px",cursor:"pointer",fontSize:13,marginBottom:6,color:"#111"},ze={flexShrink:0,width:22,height:22,borderRadius:999,background:"#10b981",color:"white",fontWeight:700,fontSize:11,display:"inline-flex",alignItems:"center",justifyContent:"center"},Ne={flex:1,whiteSpace:"pre-wrap",wordBreak:"break-word"},Be={flexShrink:0,background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:600,padding:"2px 6px",borderRadius:4,alignSelf:"center"},_e={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",flexDirection:"column",gap:8,padding:"12px 16px",width:"min(420px, calc(100vw - 32px))",background:"white",border:"1px solid #e5e7eb",borderRadius:12,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111",pointerEvents:"auto"},je={display:"flex",alignItems:"center",gap:8},Ue={background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:700,padding:"2px 8px",borderRadius:999,textTransform:"uppercase",letterSpacing:.5},Xe={display:"flex",justifyContent:"space-between",fontSize:12},De={width:"100%",height:6,background:"#f3f4f6",borderRadius:999,overflow:"hidden"},Ye={height:"100%",background:"#10b981",borderRadius:999,transition:"width 500ms"},He={margin:0,fontSize:12,color:"#6b7280"};function H(){if(console.log("[YNS Feedback Toolbar] mountFeedbackToolbar() called",{isWindow:typeof window<"u",vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,alreadyMounted:typeof document<"u"&&!!document.getElementById(N)}),typeof window>"u")return null;if(process.env.NEXT_PUBLIC_VERCEL_ENV!=="preview")return console.log("[YNS Feedback Toolbar] gate failed \u2014 NEXT_PUBLIC_VERCEL_ENV is",process.env.NEXT_PUBLIC_VERCEL_ENV),null;if(document.getElementById(N))return null;let e=document.createElement("div");e.id=N,e.dataset.ynsFeedbackUi="true",document.body.appendChild(e);let t=K(e);return t.render(p(ie,{})),{unmount:()=>{t.unmount(),e.remove()}}}typeof window<"u"&&console.log("[YNS Feedback Toolbar] module evaluated",{vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,readyState:document.readyState,willAutoMount:process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"});typeof window<"u"&&process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{H()}):H());function q(){typeof window>"u"||(console.log("[YNS Sandbox Inspectors] module evaluated"),qe(),Ge())}q();var We=new Set(["HEAD","SCRIPT","STYLE","NOSCRIPT","HTML"]);function Ve(e){if(e.id)return`${e.tagName.toLowerCase()}#${CSS.escape(e.id)}`;let t=[],n=e;for(;n&&n!==document.body&&n!==document.documentElement;){let o=n.tagName.toLowerCase();if(n.id){t.unshift(`${o}#${CSS.escape(n.id)}`);break}let s=Array.from(n.classList).filter(r=>!r.startsWith("data-yns-"));s.length>0&&(o+=`.${s.map(r=>CSS.escape(r)).join(".")}`);let d=n.parentElement,l=n.tagName;if(d&&Array.from(d.children).filter(c=>c.tagName===l&&(s.length===0||s.every(g=>c.classList.contains(g)))).length>1){let c=Array.from(d.children).indexOf(n)+1;o+=`:nth-child(${c})`}t.unshift(o),n=n.parentElement}return t.join(" > ")}function G(e,t){let n=e.getBoundingClientRect(),o=window.getComputedStyle(e),s=(e.textContent??"").trim();return{tag:e.tagName.toLowerCase(),id:e.id||void 0,classes:Array.from(e.classList).filter(d=>!d.startsWith("data-yns-")),textContent:s.length>t?`${s.slice(0,t)}\u2026`:s,cssSelector:Ve(e),boundingRect:{top:n.top,left:n.left,width:n.width,height:n.height},computedStyles:{color:o.color,backgroundColor:o.backgroundColor,fontSize:o.fontSize,fontFamily:o.fontFamily,padding:o.padding,margin:o.margin}}}function F(e,t){if(!e||!e.tagName||We.has(e.tagName))return!0;for(let n of t)if(e.hasAttribute?.(n))return!0;return!1}function qe(){let e=!1,t=null,n=null,o=document.createElement("div");o.setAttribute("data-yns-design-overlay","hover"),o.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483646","border: 2px solid #3b82f6","background: rgba(59, 130, 246, 0.08)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let s=document.createElement("div");s.setAttribute("data-yns-design-overlay","label"),s.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483647","background: #3b82f6","color: #fff","font-size: 11px","font-family: ui-monospace, monospace","padding: 2px 6px","border-radius: 3px","white-space: nowrap","display: none"].join(";"),document.documentElement.appendChild(o),document.documentElement.appendChild(s);let d=document.createElement("style");d.setAttribute("data-yns-design-overlay","style"),d.textContent="[data-yns-selected] { outline: 2px solid #3b82f6 !important; outline-offset: 1px; }",document.head.appendChild(d);function l(y){let u=y.getBoundingClientRect();o.style.top=`${u.top}px`,o.style.left=`${u.left}px`,o.style.width=`${u.width}px`,o.style.height=`${u.height}px`,o.style.display="block";let b=Array.from(y.classList).filter(v=>!v.startsWith("data-yns-")),m=y.tagName.toLowerCase()+(b.length?`.${b[0]}`:"");s.textContent=m,s.style.left=`${u.left}px`,s.style.top=`${Math.max(0,u.top-22)}px`,s.style.display="block"}let r=y=>{let u=document.elementFromPoint(y.clientX,y.clientY);if(F(u,["data-yns-design-overlay"])){o.style.display="none",s.style.display="none",t=null;return}t=u,requestAnimationFrame(()=>{t===u&&e&&u&&l(u)})},c=()=>{o.style.display="none",s.style.display="none",t=null},g=y=>{if(!e||!t)return;y.preventDefault(),y.stopPropagation(),y.stopImmediatePropagation();let u=t;if(F(u,["data-yns-design-overlay"]))return;let b=G(u,120),m=b.cssSelector;n&&n.el.removeAttribute("data-yns-selected"),n&&n.cssSelector===m?(n=null,window.parent.postMessage({type:"element-deselected",data:b},"*")):(n={el:u,cssSelector:m},u.setAttribute("data-yns-selected",""),window.parent.postMessage({type:"element-selected",data:b},"*"))},S=y=>{y.key==="Escape"&&e&&(I(),window.parent.postMessage({type:"design-mode-cleared"},"*"))};function w(){e=!0,document.addEventListener("mousemove",r,!0),document.addEventListener("mouseleave",c),document.addEventListener("click",g,!0),document.addEventListener("keydown",S,!0)}function I(){e=!1,document.removeEventListener("mousemove",r,!0),document.removeEventListener("mouseleave",c),document.removeEventListener("click",g,!0),document.removeEventListener("keydown",S,!0),o.style.display="none",s.style.display="none",t=null,n&&(n.el.removeAttribute("data-yns-selected"),n=null)}window.addEventListener("message",y=>{let u=y.data;!u||typeof u!="object"||(u.type==="design-mode-toggle"&&(u.enabled?w():(I(),window.parent.postMessage({type:"design-mode-cleared"},"*"))),u.type==="design-mode-deselect"&&n&&(n.el.removeAttribute("data-yns-selected"),n=null))})}function Ge(){let e=!1,t=null,n=[],o=document.createElement("div");o.setAttribute("data-yns-comment-overlay","hover"),o.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483644","border: 2px dashed #10b981","background: rgba(16, 185, 129, 0.06)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let s=document.createElement("div");s.setAttribute("data-yns-comment-overlay","cursor-label"),s.textContent="Click to comment",s.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483645","background: #059669","color: #fff","font-size: 11px","font-family: ui-sans-serif, system-ui, sans-serif","padding: 3px 8px","border-radius: 4px","white-space: nowrap","display: none","box-shadow: 0 2px 6px rgba(0,0,0,0.15)"].join(";");let d=document.createElement("div");d.setAttribute("data-yns-comment-overlay","pins"),d.style.cssText="position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2147483643;",document.documentElement.appendChild(o),document.documentElement.appendChild(s),document.documentElement.appendChild(d);function l(){d.innerHTML="";for(let b of n){let m=null;try{m=document.querySelector(b.selector)}catch{m=null}if(!m)continue;let v=m.getBoundingClientRect(),E=document.createElement("div");E.style.cssText=["position: fixed",`top: ${v.top-12}px`,`left: ${v.left+v.width/2-12}px`,"width: 24px","height: 24px","border-radius: 50%","background: #059669","color: #fff","display: flex","align-items: center","justify-content: center","font-size: 12px","font-weight: 600","font-family: ui-sans-serif, system-ui, sans-serif","box-shadow: 0 2px 8px rgba(0,0,0,0.2)","pointer-events: none"].join(";"),E.textContent=String(b.number),d.appendChild(E)}}let r=b=>{let m=document.elementFromPoint(b.clientX,b.clientY);if(F(m,["data-yns-comment-overlay","data-yns-design-overlay"])){o.style.display="none",s.style.display="none",t=null;return}t=m,requestAnimationFrame(()=>{if(t===m&&e&&m){let v=m.getBoundingClientRect();o.style.top=`${v.top}px`,o.style.left=`${v.left}px`,o.style.width=`${v.width}px`,o.style.height=`${v.height}px`,o.style.display="block",s.style.left=`${b.clientX+14}px`,s.style.top=`${b.clientY+14}px`,s.style.display="block"}})},c=()=>{o.style.display="none",s.style.display="none",t=null},g=b=>{if(!e||!t)return;b.preventDefault(),b.stopPropagation(),b.stopImmediatePropagation();let m=t;if(F(m,["data-yns-comment-overlay","data-yns-design-overlay"]))return;let v=G(m,200),E=m.getBoundingClientRect();window.parent.postMessage({type:"comment-click",data:{element:v,clickPosition:{x:b.clientX,y:b.clientY},elementRect:{top:E.top,left:E.left,width:E.width,height:E.height},pagePath:window.location.pathname}},"*")},S=b=>{b.key==="Escape"&&e&&(I(),window.parent.postMessage({type:"comment-mode-cleared"},"*"))};function w(){e=!0,document.body.style.cursor="crosshair",document.addEventListener("mousemove",r,!0),document.addEventListener("mouseleave",c),document.addEventListener("click",g,!0),document.addEventListener("keydown",S,!0)}function I(){e=!1,document.body.style.cursor="",document.removeEventListener("mousemove",r,!0),document.removeEventListener("mouseleave",c),document.removeEventListener("click",g,!0),document.removeEventListener("keydown",S,!0),o.style.display="none",s.style.display="none",t=null}let y=!1,u=()=>{y||(y=!0,requestAnimationFrame(()=>{y=!1,l()}))};window.addEventListener("scroll",u,{passive:!0}),window.addEventListener("resize",u,{passive:!0}),window.addEventListener("message",b=>{let m=b.data;!m||typeof m!="object"||(m.type==="comment-mode-toggle"&&(m.enabled?w():I()),m.type==="comment-pins-update"&&(n=m.pins??[],l()),m.type==="comment-pin-remove"&&(n=n.filter(v=>v.id!==m.pinId),l()))})}export{H as mountFeedbackToolbar,q as startSandboxInspectors};
|
|
1
|
+
import{useEffect as $,useRef as U,useState as E}from"react";import{createRoot as J}from"react-dom/client";import{Fragment as q,jsx as l,jsxs as w}from"react/jsx-runtime";var B="yns-feedback-toolbar-root",Q=()=>{if(typeof window>"u")return null;let e=(process.env.NEXT_PUBLIC_YNS_API_BASE??"").trim();if(e)return e.replace(/\/$/,"");let t=window.location.hostname,n=window.location.protocol;return t.endsWith(".yns.store")?`${n}//yns.store`:t.endsWith(".yns.cx")?`${n}//yns.cx`:window.location.origin},Z=e=>{if(e.id)return`#${CSS.escape(e.id)}`;let t=[],n=e;for(;n&&n.nodeType===Node.ELEMENT_NODE&&t.length<6;){let o=n.tagName.toLowerCase(),s=n.getAttribute("class");if(s){let r=s.split(/\s+/).filter(Boolean).slice(0,2).map(u=>`.${CSS.escape(u)}`).join("");o+=r}let c=n.parentElement,d=n.tagName;if(c){let r=Array.from(c.children).filter(u=>u.tagName===d);if(r.length>1){let u=r.indexOf(n)+1;o+=`:nth-of-type(${u})`}}t.unshift(o),n=c}return t.join(" > ")},ee=["id","class","data-testid","aria-label","role","name","href","src"],_=e=>{let t=[];for(let n of ee){let o=e.getAttribute(n);if(!o)continue;let s=o.length>80?`${o.slice(0,80)}\u2026`:o;t.push(` ${n}="${s.replace(/"/g,""")}"`)}return t.join("")},X=e=>{let t=(e.textContent??"").replace(/\s+/g," ").trim();return t?t.length>100?`${t.slice(0,100)}\u2026`:t:""},te=e=>{let t=[],n=e;for(;n&&n!==document.documentElement&&t.length<5;){let u=n.parentElement;if(!u)break;t.unshift(u),n=u}let o=[],s=0;for(let u of t){let b=" ".repeat(s);o.push(`${b}<${u.tagName.toLowerCase()}${_(u)}>`),s++}let c=" ".repeat(s),d=e.tagName.toLowerCase(),r=X(e);if(o.push(`${c}<${d}${_(e)}>${r?`${r}</${d}>`:""} \u2190 TARGET`),!r){let u=" ".repeat(s+1),b=Array.from(e.children).slice(0,6);for(let S of b){let x=X(S);o.push(`${u}<${S.tagName.toLowerCase()}${_(S)}>${x?`${x}</${S.tagName.toLowerCase()}>`:""}`)}e.children.length>6&&o.push(`${u}\u2026 (${e.children.length-6} more children)`),o.push(`${c}</${d}>`)}for(let u=t.length-1;u>=0;u--){let b=" ".repeat(u),S=t[u];S&&o.push(`${b}</${S.tagName.toLowerCase()}>`)}return o.join(`
|
|
2
|
+
`)},D=e=>{let t=e;for(;t;){if(t instanceof HTMLElement&&t.dataset.ynsFeedbackUi==="true")return!0;t=t.parentElement}return!1},ne=new Set(["HTML","HEAD","SCRIPT","STYLE","NOSCRIPT"]),oe=1e4,ie=e=>{let t=new Date(e),n=t.getTime()-Date.now(),o=t.toLocaleString(void 0,{weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"});if(n<=0)return`Any moment now (estimated by ${o})`;let s=Math.ceil(n/6e4);if(s<60)return`~${s} minutes (by ${o})`;let c=Math.round(n/36e5);if(c<24)return`~${c} ${c===1?"hour":"hours"} (by ${o})`;let d=Math.round(n/864e5);return`~${d} ${d===1?"day":"days"} (by ${o})`};function se(){let[e,t]=E(null),[n,o]=E(!0),[s,c]=E(!1),[d,r]=E(null),[u,b]=E(null),[S,x]=E(!1),[I,y]=E(!1),p=U(null);$(()=>{if(p.current=Q(),!p.current){o(!1);return}let i=!1,a=new AbortController,h=async()=>{try{let P=await fetch(`${p.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include",signal:a.signal});if(i)return;if(!P.ok){t(null);return}let k=await P.json();if(i)return;t(k),k.sessionStatus==="done"&&window.location.reload()}catch{i||t(null)}finally{i||o(!1)}};h();let f=window.setInterval(h,oe);return()=>{i=!0,a.abort(),window.clearInterval(f)}},[]),$(()=>{!e||e.canComment||(c(!1),r(null),x(!1),b(null))},[e]),$(()=>{if(s)return document.body.style.cursor="crosshair",()=>{document.body.style.cursor=""}},[s]),$(()=>{if(!s)return;let i=document.createElement("div");i.dataset.ynsFeedbackUi="true",i.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483644","border: 2px dashed #10b981","background: rgba(16, 185, 129, 0.08)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let a=document.createElement("div");a.dataset.ynsFeedbackUi="true",a.textContent="Click to comment",a.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483645","background: #059669","color: #fff","font-size: 11px","font-family: ui-sans-serif, system-ui, sans-serif","padding: 3px 8px","border-radius: 4px","white-space: nowrap","display: none","box-shadow: 0 2px 6px rgba(0,0,0,0.15)"].join(";"),document.documentElement.appendChild(i),document.documentElement.appendChild(a);let h=null,f=k=>{let R=document.elementFromPoint(k.clientX,k.clientY);if(!R||ne.has(R.tagName)||D(R)){i.style.display="none",a.style.display="none",h=null;return}h=R,requestAnimationFrame(()=>{if(h!==R)return;let L=R.getBoundingClientRect();i.style.top=`${L.top}px`,i.style.left=`${L.left}px`,i.style.width=`${L.width}px`,i.style.height=`${L.height}px`,i.style.display="block",a.style.left=`${k.clientX+14}px`,a.style.top=`${k.clientY+14}px`,a.style.display="block"})},P=()=>{i.style.display="none",a.style.display="none",h=null};return document.addEventListener("mousemove",f,!0),document.addEventListener("mouseleave",P),()=>{document.removeEventListener("mousemove",f,!0),document.removeEventListener("mouseleave",P),i.remove(),a.remove()}},[s]),$(()=>{if(!s)return;let i=a=>{let h=a.target;if(!(h instanceof Element)||D(h))return;a.preventDefault(),a.stopPropagation();let f=h.getBoundingClientRect(),P=f.width>0?(a.clientX-f.left)/f.width:.5,k=f.height>0?(a.clientY-f.top)/f.height:.5;r({cssSelector:Z(h),pagePath:window.location.pathname,surroundingHtml:te(h),rect:{top:f.top+window.scrollY,left:f.left+window.scrollX,width:f.width,height:f.height},clickX:a.clientX+window.scrollX,clickY:a.clientY+window.scrollY,offsetXRatio:Math.min(1,Math.max(0,P)),offsetYRatio:Math.min(1,Math.max(0,k))}),c(!1)};return document.addEventListener("click",i,{capture:!0}),()=>document.removeEventListener("click",i,{capture:!0})},[s]);let g=async()=>{if(!p.current)return;let i=await fetch(`${p.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include"});if(!i.ok)return;let a=await i.json();t(a)},m=async(i,a)=>{!p.current||!e||!d||!(await fetch(`${p.current}/api/feedback-comments`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({feedbackSessionId:e.feedbackSessionId,content:i,pagePath:d.pagePath,cssSelector:d.cssSelector,surroundingHtml:d.surroundingHtml,offsetXRatio:d.offsetXRatio,offsetYRatio:d.offsetYRatio,...a.length>0?{attachments:a}:{}})})).ok||(r(null),c(!0),await g())},v=async(i,a,h)=>{!p.current||!(await fetch(`${p.current}/api/feedback-comments/${i}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:a,attachments:h})})).ok||(b(null),await g())},C=async i=>{!p.current||!(await fetch(`${p.current}/api/feedback-comments/${i}`,{method:"DELETE",credentials:"include"})).ok||await g()},z=async()=>{if(!(!p.current||!e)&&window.confirm("Finalize this feedback session? You won't be able to add more comments.")){y(!0);try{let i=await fetch(`${p.current}/api/feedback-sessions/${e.feedbackSessionId}/finalize`,{method:"POST",credentials:"include"});if(!i.ok)return;let h=(await i.json().catch(()=>null))?.status??"processing";t(f=>f&&{...f,canComment:!1,sessionStatus:h})}finally{y(!1)}}},N=i=>{if(i.pagePath!==window.location.pathname){window.location.assign(i.pagePath);return}let a=null;try{a=document.querySelector(i.cssSelector)}catch{a=null}a&&(a.scrollIntoView({behavior:"smooth",block:"center"}),b(i.id))};if(n||!e)return null;if(!e.canComment)return e.sessionStatus!=="processing"&&e.sessionStatus!=="in_review"?null:l("div",{"data-yns-feedback-ui":"true",children:l(re,{progress:e.progress,eta:e.eta,status:e.sessionStatus})});let A=e.comments.filter(i=>i.pagePath===window.location.pathname&&i.status!=="done");return w("div",{"data-yns-feedback-ui":"true",children:[A.map((i,a)=>l(de,{pin:i,number:a+1,editing:u===i.id,feedbackSessionId:e.feedbackSessionId,apiBase:p.current,onStartEdit:()=>b(i.id),onCancelEdit:()=>b(null),onSave:(h,f)=>v(i.id,h,f),onRemove:()=>C(i.id)},i.id)),d&&l(ce,{pending:d,feedbackSessionId:e.feedbackSessionId,apiBase:p.current,onCancel:()=>{r(null),c(!0)},onSave:(i,a)=>m(i,a)}),S&&l(ue,{comments:e.comments,currentPath:window.location.pathname,onClose:()=>x(!1),onSelect:N}),w("div",{style:ge,children:[l("button",{type:"button",onClick:()=>c(i=>!i),style:s?be:O,children:s?"Cancel":"Add comment"}),l("button",{type:"button",onClick:()=>x(i=>!i),style:ye,children:S?"Hide list":`List (${e.comments.length})`}),l("button",{type:"button",onClick:z,disabled:I,style:he,children:I?"Finalizing\u2026":"Finalize"}),l("span",{style:Se,children:s?"Click any element to comment":`${A.length} on this page`})]})]})}function re({progress:e,eta:t,status:n}){let[,o]=E(0);$(()=>{let r=window.setInterval(()=>o(u=>u+1),6e4);return()=>window.clearInterval(r)},[]);let s=ie(t);return w("div",{style:De,children:[l("style",{children:ae}),w("div",{style:Ye,children:[l(le,{}),l("span",{style:He,children:"Submitted"}),l("strong",{style:{fontSize:13},children:n==="in_review"?"Feedback under review":"Applying feedback"})]}),w("div",{style:Oe,children:[l("span",{children:"Review progress"}),l("span",{style:{opacity:.7},children:e.label})]}),l("div",{style:We,children:l("div",{style:{...Ve,width:`${e.fillPct}%`}})}),w("p",{style:qe,children:["Estimated delivery: ",l("span",{style:{fontWeight:600},children:s})]})]})}var ae="@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }";function le({size:e=14}){return l("span",{"aria-hidden":!0,style:{display:"inline-block",width:e,height:e,border:"2px solid rgba(16, 185, 129, 0.25)",borderTopColor:"#10b981",borderRadius:999,animation:"yns-feedback-spin 0.9s linear infinite"}})}function de({pin:e,number:t,editing:n,feedbackSessionId:o,apiBase:s,onStartEdit:c,onCancelEdit:d,onSave:r,onRemove:u}){let b=fe(e.cssSelector,e.offsetXRatio,e.offsetYRatio);return b?w("div",{style:{position:"absolute",top:b.top,left:b.left,zIndex:2147483600,pointerEvents:"auto"},children:[l("button",{type:"button",onClick:c,style:W,title:e.content,children:t}),n&&l(H,{initial:e.content,initialAttachments:e.attachments,feedbackSessionId:o,apiBase:s,onCancel:d,onSave:r,onRemove:u})]}):null}function ce({pending:e,feedbackSessionId:t,apiBase:n,onCancel:o,onSave:s}){return w(q,{children:[l("div",{style:{position:"absolute",top:e.rect.top+e.rect.height*e.offsetYRatio-12,left:e.rect.left+e.rect.width*e.offsetXRatio-12,zIndex:2147483600,pointerEvents:"none"},children:l("div",{style:W,children:"\u2022"})}),l("div",{style:{position:"absolute",top:e.clickY+10,left:e.clickX+10,zIndex:2147483647},children:l(H,{initial:"",initialAttachments:[],feedbackSessionId:t,apiBase:n,onCancel:o,onSave:s})})]})}function ue({comments:e,currentPath:t,onClose:n,onSelect:o}){let s=new Map;for(let d of e){let r=s.get(d.pagePath)??[];r.push(d),s.set(d.pagePath,r)}let c=Array.from(s.keys()).sort((d,r)=>d===t?-1:r===t?1:d.localeCompare(r));return w("div",{style:Fe,children:[w("div",{style:Me,children:[w("strong",{style:{fontSize:14},children:["Comments (",e.length,")"]}),l("button",{type:"button",onClick:n,style:V,children:"Close"})]}),l("div",{style:ze,children:e.length===0?l("div",{style:Ne,children:"No comments yet. Click \u201CAdd comment\u201D to leave one."}):c.map(d=>w("div",{style:{marginBottom:12},children:[l("div",{style:Be,children:d===t?`${d} \xB7 current`:d}),(s.get(d)??[]).map((r,u)=>w("button",{type:"button",onClick:()=>o(r),style:_e,disabled:r.status==="done"&&!1,children:[l("span",{style:je,children:u+1}),l("span",{style:Ue,children:r.content.length>140?`${r.content.slice(0,140)}\u2026`:r.content}),r.status==="done"&&l("span",{style:Xe,children:"done"})]},r.id))]},d))})]})}var j=5,pe=5*1024*1024,me=e=>new Promise(t=>{let n=URL.createObjectURL(e),o=new window.Image;o.onload=()=>{URL.revokeObjectURL(n),t({width:o.naturalWidth,height:o.naturalHeight})},o.onerror=()=>{URL.revokeObjectURL(n),t(null)},o.src=n});function H({initial:e,initialAttachments:t,feedbackSessionId:n,apiBase:o,onCancel:s,onSave:c,onRemove:d}){let[r,u]=E(e),[b,S]=E(t),[x,I]=E(!1),[y,p]=E(!1),[g,m]=E(null),v=U(null),C=U(null);$(()=>{let i=v.current;if(!i)return;i.focus();let a=i.value.length;i.setSelectionRange(a,a)},[]);let z=async i=>{i.preventDefault();let a=r.trim();if(a){I(!0);try{await c(a,b)}finally{I(!1)}}},N=async i=>{if(!i||i.length===0||!o)return;m(null);let a=j-b.length;if(a<=0){m(`Max ${j} images per comment`);return}let h=Array.from(i).slice(0,a);for(let f of h){if(!f.type.startsWith("image/")){m(`"${f.name}" is not an image`);return}if(f.size>pe){m(`"${f.name}" exceeds 5 MB`);return}}p(!0);try{let f=await Promise.all(h.map(me)),P=new FormData;h.forEach((L,T)=>{P.append("file",L),P.append("width",f[T]?.width?String(f[T]?.width):""),P.append("height",f[T]?.height?String(f[T]?.height):"")});let k=await fetch(`${o}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(n)}`,{method:"POST",credentials:"include",body:P});if(!k.ok){let L=await k.json().catch(()=>null);m(L?.error??"Upload failed");return}let R=await k.json();S(L=>[...L,...R.uploads])}catch{m("Upload failed")}finally{p(!1),C.current&&(C.current.value="")}},A=i=>{S(a=>a.filter(h=>h.url!==i))};return w("form",{onSubmit:z,style:ve,children:[l("textarea",{ref:v,value:r,onChange:i=>u(i.target.value),placeholder:"Leave a comment\u2026",style:we,rows:3}),b.length>0&&l("div",{style:ke,children:b.map(i=>w("div",{style:Pe,children:[l("img",{src:i.url,alt:"",style:Ie}),l("button",{type:"button",onClick:()=>A(i.url),style:Le,disabled:x||y,"aria-label":"Remove attachment",children:"\xD7"})]},i.url))}),g&&l("div",{style:Re,children:g}),l("input",{ref:C,type:"file",accept:"image/*",multiple:!0,onChange:i=>void N(i.target.files),style:{display:"none"}}),w("div",{style:xe,children:[d&&l("button",{type:"button",onClick:()=>d(),style:Ee,disabled:x,children:"Delete"}),l("button",{type:"button",onClick:()=>C.current?.click(),style:$e,disabled:x||y||b.length>=j,"aria-label":y?"Uploading\u2026":"Attach image",title:y?"Uploading\u2026":"Attach image",children:y?l(Te,{}):l(Ae,{})}),l("div",{style:{flex:1}}),l("button",{type:"button",onClick:s,style:V,disabled:x,children:"Cancel"}),l("button",{type:"submit",style:Ce,disabled:x||y||!r.trim(),children:x?"Saving\u2026":"Save"})]})]})}function fe(e,t,n){let[o,s]=E(null);return $(()=>{let c=()=>{let r=null;try{r=document.querySelector(e)}catch{r=null}if(!r){s(null);return}let u=r.getBoundingClientRect();s({top:u.top+window.scrollY+u.height*n-12,left:u.left+window.scrollX+u.width*t-12})};c();let d=new ResizeObserver(c);try{let r=document.querySelector(e);r&&d.observe(r)}catch{}return window.addEventListener("scroll",c,!0),window.addEventListener("resize",c),()=>{d.disconnect(),window.removeEventListener("scroll",c,!0),window.removeEventListener("resize",c)}},[e,t,n]),o}var ge={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"rgba(17, 17, 17, 0.92)",color:"white",borderRadius:999,boxShadow:"0 8px 24px rgba(0,0,0,0.25)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',fontSize:14,pointerEvents:"auto"},O={border:"none",background:"white",color:"#111",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},be={...O,background:"#ef4444",color:"white"},ye={border:"1px solid rgba(255,255,255,0.4)",background:"transparent",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:500,fontSize:13},he={border:"none",background:"#10b981",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},Se={opacity:.8,fontSize:12},W={width:24,height:24,borderRadius:999,background:"#10b981",color:"white",border:"2px solid white",cursor:"pointer",fontSize:12,fontWeight:700,boxShadow:"0 2px 6px rgba(0,0,0,0.3)",display:"inline-flex",alignItems:"center",justifyContent:"center",padding:0},ve={background:"white",border:"1px solid #e5e7eb",borderRadius:8,padding:12,width:280,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',display:"flex",flexDirection:"column",gap:8,color:"#111"},we={width:"100%",border:"1px solid #d1d5db",borderRadius:6,padding:8,fontSize:14,resize:"vertical",fontFamily:"inherit",color:"#111",background:"white",boxSizing:"border-box"},xe={display:"flex",alignItems:"center",gap:6},F={border:"1px solid transparent",borderRadius:6,padding:"6px 10px",fontSize:13,cursor:"pointer",fontWeight:500},Ce={...F,background:"#111",color:"white"},V={...F,background:"transparent",color:"#374151",borderColor:"#d1d5db"},Ee={...F,background:"transparent",color:"#b91c1c",borderColor:"#fecaca"},ke={display:"flex",flexWrap:"wrap",gap:6},Pe={position:"relative",width:56,height:56,borderRadius:6,overflow:"hidden",border:"1px solid #e5e7eb",background:"#f9fafb"},Ie={width:"100%",height:"100%",objectFit:"cover",display:"block"},Le={position:"absolute",top:2,right:2,width:18,height:18,borderRadius:999,border:"none",background:"rgba(17, 17, 17, 0.85)",color:"white",fontSize:13,lineHeight:"16px",cursor:"pointer",padding:0,display:"inline-flex",alignItems:"center",justifyContent:"center"},Re={color:"#b91c1c",fontSize:12,margin:0},$e={...F,background:"transparent",color:"#374151",borderColor:"#d1d5db",width:30,height:30,padding:0,display:"inline-flex",alignItems:"center",justifyContent:"center",flexShrink:0};function Ae(){return w("svg",{role:"img","aria-label":"Attach image",width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[l("title",{children:"Attach image"}),l("path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.83l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"})]})}function Te(){return w(q,{children:[l("style",{children:"@keyframes yns-attach-spin { to { transform: rotate(360deg); } }"}),l("span",{"aria-hidden":!0,style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(55, 65, 81, 0.25)",borderTopColor:"#374151",borderRadius:999,animation:"yns-attach-spin 0.9s linear infinite"}})]})}var Fe={position:"fixed",top:0,right:0,bottom:0,width:360,maxWidth:"92vw",background:"white",borderLeft:"1px solid #e5e7eb",boxShadow:"-12px 0 32px rgba(0,0,0,0.12)",zIndex:2147483646,display:"flex",flexDirection:"column",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111"},Me={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e5e7eb"},ze={flex:1,overflow:"auto",padding:"12px 12px 24px"},Ne={color:"#6b7280",fontSize:13,padding:"24px 8px",textAlign:"center"},Be={fontSize:11,textTransform:"uppercase",letterSpacing:.5,color:"#6b7280",padding:"4px 4px 6px",wordBreak:"break-all"},_e={display:"flex",alignItems:"flex-start",gap:8,width:"100%",textAlign:"left",background:"white",border:"1px solid #e5e7eb",borderRadius:6,padding:"8px 10px",cursor:"pointer",fontSize:13,marginBottom:6,color:"#111"},je={flexShrink:0,width:22,height:22,borderRadius:999,background:"#10b981",color:"white",fontWeight:700,fontSize:11,display:"inline-flex",alignItems:"center",justifyContent:"center"},Ue={flex:1,whiteSpace:"pre-wrap",wordBreak:"break-word"},Xe={flexShrink:0,background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:600,padding:"2px 6px",borderRadius:4,alignSelf:"center"},De={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",flexDirection:"column",gap:8,padding:"12px 16px",width:"min(420px, calc(100vw - 32px))",background:"white",border:"1px solid #e5e7eb",borderRadius:12,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111",pointerEvents:"auto"},Ye={display:"flex",alignItems:"center",gap:8},He={background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:700,padding:"2px 8px",borderRadius:999,textTransform:"uppercase",letterSpacing:.5},Oe={display:"flex",justifyContent:"space-between",fontSize:12},We={width:"100%",height:6,background:"#f3f4f6",borderRadius:999,overflow:"hidden"},Ve={height:"100%",background:"#10b981",borderRadius:999,transition:"width 500ms"},qe={margin:0,fontSize:12,color:"#6b7280"};function Y(){if(console.log("[YNS Feedback Toolbar] mountFeedbackToolbar() called",{isWindow:typeof window<"u",vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,alreadyMounted:typeof document<"u"&&!!document.getElementById(B)}),typeof window>"u")return null;if(process.env.NEXT_PUBLIC_VERCEL_ENV!=="preview")return console.log("[YNS Feedback Toolbar] gate failed \u2014 NEXT_PUBLIC_VERCEL_ENV is",process.env.NEXT_PUBLIC_VERCEL_ENV),null;if(document.getElementById(B))return null;let e=document.createElement("div");e.id=B,e.dataset.ynsFeedbackUi="true",document.body.appendChild(e);let t=J(e);return t.render(l(se,{})),{unmount:()=>{t.unmount(),e.remove()}}}typeof window<"u"&&console.log("[YNS Feedback Toolbar] module evaluated",{vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,readyState:document.readyState,willAutoMount:process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"});typeof window<"u"&&process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{Y()}):Y());function G(){typeof window>"u"||(console.log("[YNS Sandbox Inspectors] module evaluated"),Je(),Qe())}G();var Ge=new Set(["HEAD","SCRIPT","STYLE","NOSCRIPT","HTML"]);function Ke(e){if(e.id)return`${e.tagName.toLowerCase()}#${CSS.escape(e.id)}`;let t=[],n=e;for(;n&&n!==document.body&&n!==document.documentElement;){let o=n.tagName.toLowerCase();if(n.id){t.unshift(`${o}#${CSS.escape(n.id)}`);break}let s=Array.from(n.classList).filter(r=>!r.startsWith("data-yns-"));s.length>0&&(o+=`.${s.map(r=>CSS.escape(r)).join(".")}`);let c=n.parentElement,d=n.tagName;if(c&&Array.from(c.children).filter(u=>u.tagName===d&&(s.length===0||s.every(b=>u.classList.contains(b)))).length>1){let u=Array.from(c.children).indexOf(n)+1;o+=`:nth-child(${u})`}t.unshift(o),n=n.parentElement}return t.join(" > ")}function K(e,t){let n=e.getBoundingClientRect(),o=window.getComputedStyle(e),s=(e.textContent??"").trim();return{tag:e.tagName.toLowerCase(),id:e.id||void 0,classes:Array.from(e.classList).filter(c=>!c.startsWith("data-yns-")),textContent:s.length>t?`${s.slice(0,t)}\u2026`:s,cssSelector:Ke(e),boundingRect:{top:n.top,left:n.left,width:n.width,height:n.height},computedStyles:{color:o.color,backgroundColor:o.backgroundColor,fontSize:o.fontSize,fontFamily:o.fontFamily,padding:o.padding,margin:o.margin}}}function M(e,t){if(!e||!e.tagName||Ge.has(e.tagName))return!0;for(let n of t)if(e.hasAttribute?.(n))return!0;return!1}function Je(){let e=!1,t=null,n=null,o=document.createElement("div");o.setAttribute("data-yns-design-overlay","hover"),o.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483646","border: 2px solid #3b82f6","background: rgba(59, 130, 246, 0.08)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let s=document.createElement("div");s.setAttribute("data-yns-design-overlay","label"),s.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483647","background: #3b82f6","color: #fff","font-size: 11px","font-family: ui-monospace, monospace","padding: 2px 6px","border-radius: 3px","white-space: nowrap","display: none"].join(";"),document.documentElement.appendChild(o),document.documentElement.appendChild(s);let c=document.createElement("style");c.setAttribute("data-yns-design-overlay","style"),c.textContent="[data-yns-selected] { outline: 2px solid #3b82f6 !important; outline-offset: 1px; }",document.head.appendChild(c);function d(y){let p=y.getBoundingClientRect();o.style.top=`${p.top}px`,o.style.left=`${p.left}px`,o.style.width=`${p.width}px`,o.style.height=`${p.height}px`,o.style.display="block";let g=Array.from(y.classList).filter(v=>!v.startsWith("data-yns-")),m=y.tagName.toLowerCase()+(g.length?`.${g[0]}`:"");s.textContent=m,s.style.left=`${p.left}px`,s.style.top=`${Math.max(0,p.top-22)}px`,s.style.display="block"}let r=y=>{let p=document.elementFromPoint(y.clientX,y.clientY);if(M(p,["data-yns-design-overlay"])){o.style.display="none",s.style.display="none",t=null;return}t=p,requestAnimationFrame(()=>{t===p&&e&&p&&d(p)})},u=()=>{o.style.display="none",s.style.display="none",t=null},b=y=>{if(!e||!t)return;y.preventDefault(),y.stopPropagation(),y.stopImmediatePropagation();let p=t;if(M(p,["data-yns-design-overlay"]))return;let g=K(p,120),m=g.cssSelector;n&&n.el.removeAttribute("data-yns-selected"),n&&n.cssSelector===m?(n=null,window.parent.postMessage({type:"element-deselected",data:g},"*")):(n={el:p,cssSelector:m},p.setAttribute("data-yns-selected",""),window.parent.postMessage({type:"element-selected",data:g},"*"))},S=y=>{y.key==="Escape"&&e&&(I(),window.parent.postMessage({type:"design-mode-cleared"},"*"))};function x(){e=!0,document.addEventListener("mousemove",r,!0),document.addEventListener("mouseleave",u),document.addEventListener("click",b,!0),document.addEventListener("keydown",S,!0)}function I(){e=!1,document.removeEventListener("mousemove",r,!0),document.removeEventListener("mouseleave",u),document.removeEventListener("click",b,!0),document.removeEventListener("keydown",S,!0),o.style.display="none",s.style.display="none",t=null,n&&(n.el.removeAttribute("data-yns-selected"),n=null)}window.addEventListener("message",y=>{let p=y.data;!p||typeof p!="object"||(p.type==="design-mode-toggle"&&(p.enabled?x():(I(),window.parent.postMessage({type:"design-mode-cleared"},"*"))),p.type==="design-mode-deselect"&&n&&(n.el.removeAttribute("data-yns-selected"),n=null))})}function Qe(){let e=!1,t=null,n=[],o=document.createElement("div");o.setAttribute("data-yns-comment-overlay","hover"),o.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483644","border: 2px dashed #10b981","background: rgba(16, 185, 129, 0.06)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let s=document.createElement("div");s.setAttribute("data-yns-comment-overlay","cursor-label"),s.textContent="Click to comment",s.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483645","background: #059669","color: #fff","font-size: 11px","font-family: ui-sans-serif, system-ui, sans-serif","padding: 3px 8px","border-radius: 4px","white-space: nowrap","display: none","box-shadow: 0 2px 6px rgba(0,0,0,0.15)"].join(";");let c=document.createElement("div");c.setAttribute("data-yns-comment-overlay","pins"),c.style.cssText="position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2147483643;",document.documentElement.appendChild(o),document.documentElement.appendChild(s),document.documentElement.appendChild(c);function d(){c.innerHTML="";for(let g of n){let m=null;try{m=document.querySelector(g.selector)}catch{m=null}if(!m)continue;let v=m.getBoundingClientRect(),C=document.createElement("div");C.style.cssText=["position: fixed",`top: ${v.top-12}px`,`left: ${v.left+v.width/2-12}px`,"width: 24px","height: 24px","border-radius: 50%","background: #059669","color: #fff","display: flex","align-items: center","justify-content: center","font-size: 12px","font-weight: 600","font-family: ui-sans-serif, system-ui, sans-serif","box-shadow: 0 2px 8px rgba(0,0,0,0.2)","pointer-events: none"].join(";"),C.textContent=String(g.number),c.appendChild(C)}}let r=g=>{let m=document.elementFromPoint(g.clientX,g.clientY);if(M(m,["data-yns-comment-overlay","data-yns-design-overlay"])){o.style.display="none",s.style.display="none",t=null;return}t=m,requestAnimationFrame(()=>{if(t===m&&e&&m){let v=m.getBoundingClientRect();o.style.top=`${v.top}px`,o.style.left=`${v.left}px`,o.style.width=`${v.width}px`,o.style.height=`${v.height}px`,o.style.display="block",s.style.left=`${g.clientX+14}px`,s.style.top=`${g.clientY+14}px`,s.style.display="block"}})},u=()=>{o.style.display="none",s.style.display="none",t=null},b=g=>{if(!e||!t)return;g.preventDefault(),g.stopPropagation(),g.stopImmediatePropagation();let m=t;if(M(m,["data-yns-comment-overlay","data-yns-design-overlay"]))return;let v=K(m,200),C=m.getBoundingClientRect();window.parent.postMessage({type:"comment-click",data:{element:v,clickPosition:{x:g.clientX,y:g.clientY},elementRect:{top:C.top,left:C.left,width:C.width,height:C.height},pagePath:window.location.pathname}},"*")},S=g=>{g.key==="Escape"&&e&&(I(),window.parent.postMessage({type:"comment-mode-cleared"},"*"))};function x(){e=!0,document.body.style.cursor="crosshair",document.addEventListener("mousemove",r,!0),document.addEventListener("mouseleave",u),document.addEventListener("click",b,!0),document.addEventListener("keydown",S,!0)}function I(){e=!1,document.body.style.cursor="",document.removeEventListener("mousemove",r,!0),document.removeEventListener("mouseleave",u),document.removeEventListener("click",b,!0),document.removeEventListener("keydown",S,!0),o.style.display="none",s.style.display="none",t=null}let y=!1,p=()=>{y||(y=!0,requestAnimationFrame(()=>{y=!1,d()}))};window.addEventListener("scroll",p,{passive:!0}),window.addEventListener("resize",p,{passive:!0}),window.addEventListener("message",g=>{let m=g.data;!m||typeof m!="object"||(m.type==="comment-mode-toggle"&&(m.enabled?x():I()),m.type==="comment-pins-update"&&(n=m.pins??[],d()),m.type==="comment-pin-remove"&&(n=n.filter(v=>v.id!==m.pinId),d()))})}export{Y as mountFeedbackToolbar,G as startSandboxInspectors};
|
|
3
3
|
//# sourceMappingURL=browser.js.map
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/feedback-toolbar.tsx","../src/sandbox-inspectors.ts"],"sourcesContent":["/**\n * Feedback session toolbar — side-effect entry.\n *\n * Importing `commerce-kit/feedback-toolbar` (or `commerce-kit/browser` for the\n * combined entry) mounts a floating toolbar onto the page that lets reviewers\n * leave click-anchored comments. Comments persist via YNS API endpoints,\n * authenticated with the `better-auth` session cookie sent through\n * `credentials: \"include\"`.\n *\n * Gated on `process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\"` — only Vercel\n * preview deploys get the toolbar. Sandbox dev (env undefined) and production\n * (env === \"production\") skip it.\n */\n\nimport { type CSSProperties, type FormEvent, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst MOUNT_NODE_ID = \"yns-feedback-toolbar-root\";\n\ninterface FeedbackAttachment {\n\turl: string;\n\tcontentType: string;\n\twidth?: number;\n\theight?: number;\n}\n\ninterface FeedbackComment {\n\tid: string;\n\tpagePath: string;\n\tcssSelector: string;\n\tcontent: string;\n\tstatus: \"todo\" | \"done\";\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n\tattachments: FeedbackAttachment[];\n}\n\ntype SessionStatus = \"created\" | \"in_progress\" | \"processing\" | \"in_review\" | \"done\";\n\ninterface ReviewProgress {\n\tfillPct: number;\n\tlabel: string;\n}\n\ninterface ActiveSession {\n\tfeedbackSessionId: string;\n\tcomments: FeedbackComment[];\n\tcanComment: boolean;\n\tsessionStatus: SessionStatus;\n\tcommentTotal: number;\n\tcommentDone: number;\n\teta: string;\n\tprogress: ReviewProgress;\n}\n\ninterface PendingPin {\n\tcssSelector: string;\n\tpagePath: string;\n\tsurroundingHtml: string;\n\trect: { top: number; left: number; width: number; height: number };\n\tclickX: number;\n\tclickY: number;\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n}\n\nconst buildApiBase = (): string | null => {\n\tif (typeof window === \"undefined\") return null;\n\tconst override = (process.env.NEXT_PUBLIC_YNS_API_BASE ?? \"\").trim();\n\tif (override) return override.replace(/\\/$/, \"\");\n\n\t// Toolbar runs on a per-store preview deploy (mystore-preview.yns.{store|cx})\n\t// served by the merchant's Vercel project — that host has no YNS API. Aim\n\t// at the apex `yns.store` / `yns.cx`, which yns-app serves and where the\n\t// API lives. Cookies scoped to `.yns.store` / `.yns.cx` travel along.\n\tconst host = window.location.hostname;\n\tconst protocol = window.location.protocol;\n\tif (host.endsWith(\".yns.store\")) return `${protocol}//yns.store`;\n\tif (host.endsWith(\".yns.cx\")) return `${protocol}//yns.cx`;\n\treturn window.location.origin;\n};\n\nconst computeCssSelector = (el: Element): string => {\n\tif (el.id) return `#${CSS.escape(el.id)}`;\n\tconst path: string[] = [];\n\tlet node: Element | null = el;\n\twhile (node && node.nodeType === Node.ELEMENT_NODE && path.length < 6) {\n\t\tlet part = node.tagName.toLowerCase();\n\t\tconst className = node.getAttribute(\"class\");\n\t\tif (className) {\n\t\t\tconst classes = className\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.slice(0, 2)\n\t\t\t\t.map((c) => `.${CSS.escape(c)}`)\n\t\t\t\t.join(\"\");\n\t\t\tpart += classes;\n\t\t}\n\t\tconst parent: Element | null = node.parentElement;\n\t\tconst tag = node.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings: Element[] = Array.from(parent.children).filter((c) => c.tagName === tag);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst idx = siblings.indexOf(node) + 1;\n\t\t\t\tpart += `:nth-of-type(${idx})`;\n\t\t\t}\n\t\t}\n\t\tpath.unshift(part);\n\t\tnode = parent;\n\t}\n\treturn path.join(\" > \");\n};\n\nconst ATTRS_OF_INTEREST = [\"id\", \"class\", \"data-testid\", \"aria-label\", \"role\", \"name\", \"href\", \"src\"];\n\nconst formatAttrs = (el: Element): string => {\n\tconst parts: string[] = [];\n\tfor (const attr of ATTRS_OF_INTEREST) {\n\t\tconst v = el.getAttribute(attr);\n\t\tif (!v) continue;\n\t\tconst trimmed = v.length > 80 ? `${v.slice(0, 80)}…` : v;\n\t\tparts.push(` ${attr}=\"${trimmed.replace(/\"/g, \""\")}\"`);\n\t}\n\treturn parts.join(\"\");\n};\n\nconst formatTextContent = (el: Element): string => {\n\tconst text = (el.textContent ?? \"\").replace(/\\s+/g, \" \").trim();\n\tif (!text) return \"\";\n\treturn text.length > 100 ? `${text.slice(0, 100)}…` : text;\n};\n\nconst buildSurroundingHtml = (target: Element): string => {\n\tconst ancestors: Element[] = [];\n\tlet cur: Element | null = target;\n\twhile (cur && cur !== document.documentElement && ancestors.length < 5) {\n\t\tconst parent: Element | null = cur.parentElement;\n\t\tif (!parent) break;\n\t\tancestors.unshift(parent);\n\t\tcur = parent;\n\t}\n\n\tconst lines: string[] = [];\n\tlet depth = 0;\n\tfor (const ancestor of ancestors) {\n\t\tconst indent = \" \".repeat(depth);\n\t\tlines.push(`${indent}<${ancestor.tagName.toLowerCase()}${formatAttrs(ancestor)}>`);\n\t\tdepth++;\n\t}\n\n\tconst targetIndent = \" \".repeat(depth);\n\tconst targetTag = target.tagName.toLowerCase();\n\tconst targetText = formatTextContent(target);\n\tlines.push(\n\t\t`${targetIndent}<${targetTag}${formatAttrs(target)}>${\n\t\t\ttargetText ? `${targetText}</${targetTag}>` : \"\"\n\t\t} ← TARGET`,\n\t);\n\n\tif (!targetText) {\n\t\tconst childIndent = \" \".repeat(depth + 1);\n\t\tconst children = Array.from(target.children).slice(0, 6);\n\t\tfor (const child of children) {\n\t\t\tconst childText = formatTextContent(child);\n\t\t\tlines.push(\n\t\t\t\t`${childIndent}<${child.tagName.toLowerCase()}${formatAttrs(child)}>${\n\t\t\t\t\tchildText ? `${childText}</${child.tagName.toLowerCase()}>` : \"\"\n\t\t\t\t}`,\n\t\t\t);\n\t\t}\n\t\tif (target.children.length > 6) {\n\t\t\tlines.push(`${childIndent}… (${target.children.length - 6} more children)`);\n\t\t}\n\t\tlines.push(`${targetIndent}</${targetTag}>`);\n\t}\n\n\tfor (let i = ancestors.length - 1; i >= 0; i--) {\n\t\tconst indent = \" \".repeat(i);\n\t\tconst ancestor = ancestors[i];\n\t\tif (!ancestor) continue;\n\t\tlines.push(`${indent}</${ancestor.tagName.toLowerCase()}>`);\n\t}\n\n\treturn lines.join(\"\\n\");\n};\n\nconst isInsideToolbar = (el: Element | null): boolean => {\n\tlet node = el;\n\twhile (node) {\n\t\tif (node instanceof HTMLElement && node.dataset.ynsFeedbackUi === \"true\") return true;\n\t\tnode = node.parentElement;\n\t}\n\treturn false;\n};\n\nconst IGNORED_TAGS = new Set([\"HTML\", \"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\"]);\n\nconst POLL_INTERVAL_MS = 10_000;\n\n// Format a server-computed ETA as remaining + absolute label. The deadline\n// math itself lives in yns-app's `lib/feedback-comments-api.ts` so the toolbar\n// and YNS lock screen stay in sync.\nconst formatEta = (etaIso: string): string => {\n\tconst eta = new Date(etaIso);\n\tconst remainingMs = eta.getTime() - Date.now();\n\tconst dateLabel = eta.toLocaleString(undefined, {\n\t\tweekday: \"short\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t});\n\n\tif (remainingMs <= 0) return `Any moment now (estimated by ${dateLabel})`;\n\tconst remainingMin = Math.ceil(remainingMs / 60_000);\n\tif (remainingMin < 60) return `~${remainingMin} minutes (by ${dateLabel})`;\n\tconst remainingHours = Math.round(remainingMs / 3_600_000);\n\tif (remainingHours < 24) {\n\t\treturn `~${remainingHours} ${remainingHours === 1 ? \"hour\" : \"hours\"} (by ${dateLabel})`;\n\t}\n\tconst remainingDays = Math.round(remainingMs / 86_400_000);\n\treturn `~${remainingDays} ${remainingDays === 1 ? \"day\" : \"days\"} (by ${dateLabel})`;\n};\n\nfunction FeedbackToolbar() {\n\tconst [session, setSession] = useState<ActiveSession | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [pinMode, setPinMode] = useState(false);\n\tconst [pending, setPending] = useState<PendingPin | null>(null);\n\tconst [editingId, setEditingId] = useState<string | null>(null);\n\tconst [sidebarOpen, setSidebarOpen] = useState(false);\n\tconst [finalizing, setFinalizing] = useState(false);\n\tconst apiBase = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tapiBase.current = buildApiBase();\n\t\tif (!apiBase.current) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet cancelled = false;\n\t\tconst controller = new AbortController();\n\n\t\tconst fetchOnce = async () => {\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(\n\t\t\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t\t\t{ credentials: \"include\", signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tsetSession(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = (await res.json()) as ActiveSession;\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetSession(data);\n\t\t\t\t// Hard-refresh once the AI run lands and the preview is up to date.\n\t\t\t\t// Out of scope to diff anything smarter — the new deploy replaces\n\t\t\t\t// the page entirely so a full reload is appropriate here.\n\t\t\t\tif (data.sessionStatus === \"done\") {\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tif (!cancelled) setSession(null);\n\t\t\t} finally {\n\t\t\t\tif (!cancelled) setLoading(false);\n\t\t\t}\n\t\t};\n\n\t\tvoid fetchOnce();\n\t\tconst interval = window.setInterval(fetchOnce, POLL_INTERVAL_MS);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tcontroller.abort();\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, []);\n\n\t// When the session stops accepting comments (finalized → in_review, or\n\t// closed), tear down any in-flight UI so the hover overlay/popover/sidebar\n\t// don't linger after the toolbar's main render returns null.\n\tuseEffect(() => {\n\t\tif (!session || session.canComment) return;\n\t\tsetPinMode(false);\n\t\tsetPending(null);\n\t\tsetSidebarOpen(false);\n\t\tsetEditingId(null);\n\t}, [session]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\treturn () => {\n\t\t\tdocument.body.style.cursor = \"\";\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.dataset.ynsFeedbackUi = \"true\";\n\t\toverlay.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483644\",\n\t\t\t\"border: 2px dashed #10b981\",\n\t\t\t\"background: rgba(16, 185, 129, 0.08)\",\n\t\t\t\"border-radius: 3px\",\n\t\t\t\"display: none\",\n\t\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t\t].join(\";\");\n\n\t\tconst label = document.createElement(\"div\");\n\t\tlabel.dataset.ynsFeedbackUi = \"true\";\n\t\tlabel.textContent = \"Click to comment\";\n\t\tlabel.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483645\",\n\t\t\t\"background: #059669\",\n\t\t\t\"color: #fff\",\n\t\t\t\"font-size: 11px\",\n\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\"padding: 3px 8px\",\n\t\t\t\"border-radius: 4px\",\n\t\t\t\"white-space: nowrap\",\n\t\t\t\"display: none\",\n\t\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t\t].join(\";\");\n\n\t\tdocument.documentElement.appendChild(overlay);\n\t\tdocument.documentElement.appendChild(label);\n\n\t\tlet hovered: Element | null = null;\n\t\tconst handleMove = (e: MouseEvent) => {\n\t\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\t\tif (!el || IGNORED_TAGS.has(el.tagName) || isInsideToolbar(el)) {\n\t\t\t\toverlay.style.display = \"none\";\n\t\t\t\tlabel.style.display = \"none\";\n\t\t\t\thovered = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thovered = el;\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (hovered !== el) return;\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\t\t\t\tlabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tlabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tlabel.style.display = \"block\";\n\t\t\t});\n\t\t};\n\n\t\tconst handleLeave = () => {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thovered = null;\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleLeave);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMove, true);\n\t\t\tdocument.removeEventListener(\"mouseleave\", handleLeave);\n\t\t\toverlay.remove();\n\t\t\tlabel.remove();\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tconst handleClick = (event: MouseEvent) => {\n\t\t\tconst target = event.target;\n\t\t\tif (!(target instanceof Element)) return;\n\t\t\tif (isInsideToolbar(target)) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\tconst offsetXRatio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5;\n\t\t\tconst offsetYRatio = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;\n\t\t\tsetPending({\n\t\t\t\tcssSelector: computeCssSelector(target),\n\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\tsurroundingHtml: buildSurroundingHtml(target),\n\t\t\t\trect: {\n\t\t\t\t\ttop: rect.top + window.scrollY,\n\t\t\t\t\tleft: rect.left + window.scrollX,\n\t\t\t\t\twidth: rect.width,\n\t\t\t\t\theight: rect.height,\n\t\t\t\t},\n\t\t\t\tclickX: event.clientX + window.scrollX,\n\t\t\t\tclickY: event.clientY + window.scrollY,\n\t\t\t\toffsetXRatio: Math.min(1, Math.max(0, offsetXRatio)),\n\t\t\t\toffsetYRatio: Math.min(1, Math.max(0, offsetYRatio)),\n\t\t\t});\n\t\t\tsetPinMode(false);\n\t\t};\n\t\tdocument.addEventListener(\"click\", handleClick, { capture: true });\n\t\treturn () => document.removeEventListener(\"click\", handleClick, { capture: true });\n\t}, [pinMode]);\n\n\tconst refreshComments = async () => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(\n\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t{ credentials: \"include\" },\n\t\t);\n\t\tif (!res.ok) return;\n\t\tconst data = (await res.json()) as ActiveSession;\n\t\tsetSession(data);\n\t};\n\n\tconst submitNewComment = async (content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current || !session || !pending) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tfeedbackSessionId: session.feedbackSessionId,\n\t\t\t\tcontent,\n\t\t\t\tpagePath: pending.pagePath,\n\t\t\t\tcssSelector: pending.cssSelector,\n\t\t\t\tsurroundingHtml: pending.surroundingHtml,\n\t\t\t\toffsetXRatio: pending.offsetXRatio,\n\t\t\t\toffsetYRatio: pending.offsetYRatio,\n\t\t\t\t...(attachments.length > 0 ? { attachments } : {}),\n\t\t\t}),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetPending(null);\n\t\tsetPinMode(true);\n\t\tawait refreshComments();\n\t};\n\n\tconst updateComment = async (id: string, content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"PATCH\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ content, attachments }),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetEditingId(null);\n\t\tawait refreshComments();\n\t};\n\n\tconst removeComment = async (id: string) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"DELETE\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) return;\n\t\tawait refreshComments();\n\t};\n\n\tconst finalizeSession = async () => {\n\t\tif (!apiBase.current || !session) return;\n\t\tif (!window.confirm(\"Finalize this feedback session? You won't be able to add more comments.\")) return;\n\t\tsetFinalizing(true);\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase.current}/api/feedback-sessions/${session.feedbackSessionId}/finalize`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\" },\n\t\t\t);\n\t\t\tif (!res.ok) return;\n\t\t\t// Flip BOTH canComment and sessionStatus locally. Without the status\n\t\t\t// flip, the render guard sees `canComment=false` + `status=in_progress`\n\t\t\t// (a state the SubmittedPanel branch returns null for) and the toolbar\n\t\t\t// disappears for the ~10s gap until the next poll catches up.\n\t\t\tconst body = (await res.json().catch(() => null)) as { status?: SessionStatus } | null;\n\t\t\tconst nextStatus: SessionStatus = body?.status ?? \"processing\";\n\t\t\tsetSession((prev) => (prev ? { ...prev, canComment: false, sessionStatus: nextStatus } : prev));\n\t\t} finally {\n\t\t\tsetFinalizing(false);\n\t\t}\n\t};\n\n\tconst scrollToComment = (comment: FeedbackComment) => {\n\t\tif (comment.pagePath !== window.location.pathname) {\n\t\t\twindow.location.assign(comment.pagePath);\n\t\t\treturn;\n\t\t}\n\t\tlet el: Element | null = null;\n\t\ttry {\n\t\t\tel = document.querySelector(comment.cssSelector);\n\t\t} catch {\n\t\t\tel = null;\n\t\t}\n\t\tif (!el) return;\n\t\tel.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n\t\tsetEditingId(comment.id);\n\t};\n\n\tif (loading || !session) return null;\n\n\t// Session has been finalized for AI review. Show a small status panel with\n\t// the same progress + ETA shown in YNS, but no commenting controls. Polling\n\t// continues in the background; on `done` we hard-reload to pick up the\n\t// fresh deploy.\n\tif (!session.canComment) {\n\t\tif (session.sessionStatus !== \"processing\" && session.sessionStatus !== \"in_review\") return null;\n\t\treturn (\n\t\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t\t<SubmittedPanel progress={session.progress} eta={session.eta} status={session.sessionStatus} />\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst visiblePins = session.comments.filter(\n\t\t(c) => c.pagePath === window.location.pathname && c.status !== \"done\",\n\t);\n\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t{visiblePins.map((pin, idx) => (\n\t\t\t\t<PinOverlay\n\t\t\t\t\tkey={pin.id}\n\t\t\t\t\tpin={pin}\n\t\t\t\t\tnumber={idx + 1}\n\t\t\t\t\tediting={editingId === pin.id}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonStartEdit={() => setEditingId(pin.id)}\n\t\t\t\t\tonCancelEdit={() => setEditingId(null)}\n\t\t\t\t\tonSave={(content, attachments) => updateComment(pin.id, content, attachments)}\n\t\t\t\t\tonRemove={() => removeComment(pin.id)}\n\t\t\t\t/>\n\t\t\t))}\n\n\t\t\t{pending && (\n\t\t\t\t<PendingCommentPopover\n\t\t\t\t\tpending={pending}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonCancel={() => {\n\t\t\t\t\t\tsetPending(null);\n\t\t\t\t\t\tsetPinMode(true);\n\t\t\t\t\t}}\n\t\t\t\t\tonSave={(content, attachments) => submitNewComment(content, attachments)}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{sidebarOpen && (\n\t\t\t\t<CommentsSidebar\n\t\t\t\t\tcomments={session.comments}\n\t\t\t\t\tcurrentPath={window.location.pathname}\n\t\t\t\t\tonClose={() => setSidebarOpen(false)}\n\t\t\t\t\tonSelect={scrollToComment}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => setPinMode((v) => !v)}\n\t\t\t\t\tstyle={pinMode ? toolbarButtonActiveStyle : toolbarButtonStyle}\n\t\t\t\t>\n\t\t\t\t\t{pinMode ? \"Cancel\" : \"Add comment\"}\n\t\t\t\t</button>\n\t\t\t\t<button type=\"button\" onClick={() => setSidebarOpen((v) => !v)} style={toolbarButtonGhostStyle}>\n\t\t\t\t\t{sidebarOpen ? \"Hide list\" : `List (${session.comments.length})`}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={finalizeSession}\n\t\t\t\t\tdisabled={finalizing}\n\t\t\t\t\tstyle={toolbarButtonFinalizeStyle}\n\t\t\t\t>\n\t\t\t\t\t{finalizing ? \"Finalizing…\" : \"Finalize\"}\n\t\t\t\t</button>\n\t\t\t\t<span style={toolbarHintStyle}>\n\t\t\t\t\t{pinMode ? \"Click any element to comment\" : `${visiblePins.length} on this page`}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SubmittedPanel({\n\tprogress,\n\teta: etaIso,\n\tstatus,\n}: {\n\tprogress: ReviewProgress;\n\teta: string;\n\tstatus: SessionStatus;\n}) {\n\t// Re-render every minute so the relative ETA label stays fresh between\n\t// the 10s data polls (which only re-render when the API payload changes).\n\tconst [, setTick] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = window.setInterval(() => setTick((t) => t + 1), 60_000);\n\t\treturn () => window.clearInterval(id);\n\t}, []);\n\n\tconst eta = formatEta(etaIso);\n\tconst isInReview = status === \"in_review\";\n\tconst headline = isInReview ? \"Feedback under review\" : \"Applying feedback\";\n\n\treturn (\n\t\t<div style={submittedPanelStyle}>\n\t\t\t<style>{spinnerKeyframes}</style>\n\t\t\t<div style={submittedHeaderStyle}>\n\t\t\t\t<Spinner />\n\t\t\t\t<span style={submittedBadgeStyle}>Submitted</span>\n\t\t\t\t<strong style={{ fontSize: 13 }}>{headline}</strong>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressLabelStyle}>\n\t\t\t\t<span>Review progress</span>\n\t\t\t\t<span style={{ opacity: 0.7 }}>{progress.label}</span>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressTrackStyle}>\n\t\t\t\t<div style={{ ...submittedProgressFillStyle, width: `${progress.fillPct}%` }} />\n\t\t\t</div>\n\t\t\t<p style={submittedEtaStyle}>\n\t\t\t\tEstimated delivery: <span style={{ fontWeight: 600 }}>{eta}</span>\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n\n// Inline keyframes — toolbar avoids relying on a global stylesheet.\nconst spinnerKeyframes = `@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }`;\n\nfunction Spinner({ size = 14 }: { size?: number }) {\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborder: \"2px solid rgba(16, 185, 129, 0.25)\",\n\t\t\t\tborderTopColor: \"#10b981\",\n\t\t\t\tborderRadius: 999,\n\t\t\t\tanimation: \"yns-feedback-spin 0.9s linear infinite\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction PinOverlay({\n\tpin,\n\tnumber,\n\tediting,\n\tfeedbackSessionId,\n\tapiBase,\n\tonStartEdit,\n\tonCancelEdit,\n\tonSave,\n\tonRemove,\n}: {\n\tpin: FeedbackComment;\n\tnumber: number;\n\tediting: boolean;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonStartEdit: () => void;\n\tonCancelEdit: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove: () => Promise<void>;\n}) {\n\tconst target = useTargetPosition(pin.cssSelector, pin.offsetXRatio, pin.offsetYRatio);\n\tif (!target) return null;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tposition: \"absolute\",\n\t\t\t\ttop: target.top,\n\t\t\t\tleft: target.left,\n\t\t\t\tzIndex: 2147483600,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t>\n\t\t\t<button type=\"button\" onClick={onStartEdit} style={pinDotStyle} title={pin.content}>\n\t\t\t\t{number}\n\t\t\t</button>\n\t\t\t{editing && (\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial={pin.content}\n\t\t\t\t\tinitialAttachments={pin.attachments}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancelEdit}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t\tonRemove={onRemove}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction PendingCommentPopover({\n\tpending,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n}: {\n\tpending: PendingPin;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n}) {\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.rect.top + pending.rect.height * pending.offsetYRatio - 12,\n\t\t\t\t\tleft: pending.rect.left + pending.rect.width * pending.offsetXRatio - 12,\n\t\t\t\t\tzIndex: 2147483600,\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={pinDotStyle}>•</div>\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.clickY + 10,\n\t\t\t\t\tleft: pending.clickX + 10,\n\t\t\t\t\tzIndex: 2147483647,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial=\"\"\n\t\t\t\t\tinitialAttachments={[]}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancel}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n\nfunction CommentsSidebar({\n\tcomments,\n\tcurrentPath,\n\tonClose,\n\tonSelect,\n}: {\n\tcomments: FeedbackComment[];\n\tcurrentPath: string;\n\tonClose: () => void;\n\tonSelect: (comment: FeedbackComment) => void;\n}) {\n\tconst groups = new Map<string, FeedbackComment[]>();\n\tfor (const c of comments) {\n\t\tconst list = groups.get(c.pagePath) ?? [];\n\t\tlist.push(c);\n\t\tgroups.set(c.pagePath, list);\n\t}\n\tconst paths = Array.from(groups.keys()).sort((a, b) => {\n\t\tif (a === currentPath) return -1;\n\t\tif (b === currentPath) return 1;\n\t\treturn a.localeCompare(b);\n\t});\n\n\treturn (\n\t\t<div style={sidebarStyle}>\n\t\t\t<div style={sidebarHeaderStyle}>\n\t\t\t\t<strong style={{ fontSize: 14 }}>Comments ({comments.length})</strong>\n\t\t\t\t<button type=\"button\" onClick={onClose} style={ghostButtonStyle}>\n\t\t\t\t\tClose\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div style={sidebarScrollStyle}>\n\t\t\t\t{comments.length === 0 ? (\n\t\t\t\t\t<div style={sidebarEmptyStyle}>No comments yet. Click “Add comment” to leave one.</div>\n\t\t\t\t) : (\n\t\t\t\t\tpaths.map((path) => (\n\t\t\t\t\t\t<div key={path} style={{ marginBottom: 12 }}>\n\t\t\t\t\t\t\t<div style={sidebarPathStyle}>{path === currentPath ? `${path} · current` : path}</div>\n\t\t\t\t\t\t\t{(groups.get(path) ?? []).map((c, idx) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tkey={c.id}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => onSelect(c)}\n\t\t\t\t\t\t\t\t\tstyle={sidebarItemStyle}\n\t\t\t\t\t\t\t\t\tdisabled={c.status === \"done\" && false}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemIndexStyle}>{idx + 1}</span>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemTextStyle}>\n\t\t\t\t\t\t\t\t\t\t{c.content.length > 140 ? `${c.content.slice(0, 140)}…` : c.content}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{c.status === \"done\" && <span style={sidebarItemDoneStyle}>done</span>}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nconst MAX_ATTACHMENTS = 5;\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nconst readImageDimensions = (file: File): Promise<{ width: number; height: number } | null> =>\n\tnew Promise((resolve) => {\n\t\tconst url = URL.createObjectURL(file);\n\t\tconst img = new window.Image();\n\t\timg.onload = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve({ width: img.naturalWidth, height: img.naturalHeight });\n\t\t};\n\t\timg.onerror = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve(null);\n\t\t};\n\t\timg.src = url;\n\t});\n\nfunction EditPopover({\n\tinitial,\n\tinitialAttachments,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n\tonRemove,\n}: {\n\tinitial: string;\n\tinitialAttachments: FeedbackAttachment[];\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove?: () => Promise<void>;\n}) {\n\tconst [value, setValue] = useState(initial);\n\tconst [attachments, setAttachments] = useState<FeedbackAttachment[]>(initialAttachments);\n\tconst [saving, setSaving] = useState(false);\n\tconst [uploading, setUploading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst textareaRef = useRef<HTMLTextAreaElement | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement | null>(null);\n\n\tuseEffect(() => {\n\t\tconst el = textareaRef.current;\n\t\tif (!el) return;\n\t\tel.focus();\n\t\tconst len = el.value.length;\n\t\tel.setSelectionRange(len, len);\n\t}, []);\n\n\tconst handleSubmit = async (e: FormEvent) => {\n\t\te.preventDefault();\n\t\tconst content = value.trim();\n\t\tif (!content) return;\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tawait onSave(content, attachments);\n\t\t} finally {\n\t\t\tsetSaving(false);\n\t\t}\n\t};\n\n\tconst handleFiles = async (files: FileList | null) => {\n\t\tif (!files || files.length === 0 || !apiBase) return;\n\t\tsetError(null);\n\t\tconst slots = MAX_ATTACHMENTS - attachments.length;\n\t\tif (slots <= 0) {\n\t\t\tsetError(`Max ${MAX_ATTACHMENTS} images per comment`);\n\t\t\treturn;\n\t\t}\n\t\tconst picked = Array.from(files).slice(0, slots);\n\t\tfor (const f of picked) {\n\t\t\tif (!f.type.startsWith(\"image/\")) {\n\t\t\t\tsetError(`\"${f.name}\" is not an image`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (f.size > MAX_ATTACHMENT_BYTES) {\n\t\t\t\tsetError(`\"${f.name}\" exceeds 5 MB`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tsetUploading(true);\n\t\ttry {\n\t\t\tconst dims = await Promise.all(picked.map(readImageDimensions));\n\t\t\tconst fd = new FormData();\n\t\t\tpicked.forEach((file, i) => {\n\t\t\t\tfd.append(\"file\", file);\n\t\t\t\tfd.append(\"width\", dims[i]?.width ? String(dims[i]?.width) : \"\");\n\t\t\t\tfd.append(\"height\", dims[i]?.height ? String(dims[i]?.height) : \"\");\n\t\t\t});\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(feedbackSessionId)}`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\", body: fd },\n\t\t\t);\n\t\t\tif (!res.ok) {\n\t\t\t\tconst body = (await res.json().catch(() => null)) as { error?: string } | null;\n\t\t\t\tsetError(body?.error ?? \"Upload failed\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst body = (await res.json()) as { uploads: FeedbackAttachment[] };\n\t\t\tsetAttachments((prev) => [...prev, ...body.uploads]);\n\t\t} catch {\n\t\t\tsetError(\"Upload failed\");\n\t\t} finally {\n\t\t\tsetUploading(false);\n\t\t\tif (fileInputRef.current) fileInputRef.current.value = \"\";\n\t\t}\n\t};\n\n\tconst removeAttachment = (url: string) => {\n\t\tsetAttachments((prev) => prev.filter((a) => a.url !== url));\n\t};\n\n\treturn (\n\t\t<form onSubmit={handleSubmit} style={popoverStyle}>\n\t\t\t<textarea\n\t\t\t\tref={textareaRef}\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => setValue(e.target.value)}\n\t\t\t\tplaceholder=\"Leave a comment…\"\n\t\t\t\tstyle={textareaStyle}\n\t\t\t\trows={3}\n\t\t\t/>\n\t\t\t{attachments.length > 0 && (\n\t\t\t\t<div style={attachmentRowStyle}>\n\t\t\t\t\t{attachments.map((att) => (\n\t\t\t\t\t\t<div key={att.url} style={attachmentThumbWrapStyle}>\n\t\t\t\t\t\t\t<img src={att.url} alt=\"\" style={attachmentThumbStyle} />\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => removeAttachment(att.url)}\n\t\t\t\t\t\t\t\tstyle={attachmentRemoveStyle}\n\t\t\t\t\t\t\t\tdisabled={saving || uploading}\n\t\t\t\t\t\t\t\taria-label=\"Remove attachment\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{error && <div style={errorTextStyle}>{error}</div>}\n\t\t\t<input\n\t\t\t\tref={fileInputRef}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept=\"image/*\"\n\t\t\t\tmultiple\n\t\t\t\tonChange={(e) => void handleFiles(e.target.files)}\n\t\t\t\tstyle={{ display: \"none\" }}\n\t\t\t/>\n\t\t\t<div style={popoverActionsStyle}>\n\t\t\t\t{onRemove && (\n\t\t\t\t\t<button type=\"button\" onClick={() => onRemove()} style={dangerButtonStyle} disabled={saving}>\n\t\t\t\t\t\tDelete\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\tstyle={ghostButtonStyle}\n\t\t\t\t\tdisabled={saving || uploading || attachments.length >= MAX_ATTACHMENTS}\n\t\t\t\t>\n\t\t\t\t\t{uploading\n\t\t\t\t\t\t? \"Uploading…\"\n\t\t\t\t\t\t: `Attach image${attachments.length > 0 ? ` (${attachments.length})` : \"\"}`}\n\t\t\t\t</button>\n\t\t\t\t<div style={{ flex: 1 }} />\n\t\t\t\t<button type=\"button\" onClick={onCancel} style={ghostButtonStyle} disabled={saving}>\n\t\t\t\t\tCancel\n\t\t\t\t</button>\n\t\t\t\t<button type=\"submit\" style={primaryButtonStyle} disabled={saving || uploading || !value.trim()}>\n\t\t\t\t\t{saving ? \"Saving…\" : \"Save\"}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</form>\n\t);\n}\n\nfunction useTargetPosition(selector: string, offsetXRatio: number, offsetYRatio: number) {\n\tconst [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n\n\tuseEffect(() => {\n\t\tconst update = () => {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) {\n\t\t\t\tsetPos(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tsetPos({\n\t\t\t\ttop: r.top + window.scrollY + r.height * offsetYRatio - 12,\n\t\t\t\tleft: r.left + window.scrollX + r.width * offsetXRatio - 12,\n\t\t\t});\n\t\t};\n\n\t\tupdate();\n\t\tconst ro = new ResizeObserver(update);\n\t\ttry {\n\t\t\tconst el = document.querySelector(selector);\n\t\t\tif (el) ro.observe(el);\n\t\t} catch {\n\t\t\t// swallow invalid selector\n\t\t}\n\t\twindow.addEventListener(\"scroll\", update, true);\n\t\twindow.addEventListener(\"resize\", update);\n\t\treturn () => {\n\t\t\tro.disconnect();\n\t\t\twindow.removeEventListener(\"scroll\", update, true);\n\t\t\twindow.removeEventListener(\"resize\", update);\n\t\t};\n\t}, [selector, offsetXRatio, offsetYRatio]);\n\n\treturn pos;\n}\n\n// Inline styles — toolbar is injected into arbitrary stores, so we avoid relying\n// on any CSS framework being present.\nconst toolbarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n\tpadding: \"8px 12px\",\n\tbackground: \"rgba(17, 17, 17, 0.92)\",\n\tcolor: \"white\",\n\tborderRadius: 999,\n\tboxShadow: \"0 8px 24px rgba(0,0,0,0.25)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tfontSize: 14,\n\tpointerEvents: \"auto\",\n};\n\nconst toolbarButtonStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarButtonActiveStyle: CSSProperties = {\n\t...toolbarButtonStyle,\n\tbackground: \"#ef4444\",\n\tcolor: \"white\",\n};\n\nconst toolbarButtonGhostStyle: CSSProperties = {\n\tborder: \"1px solid rgba(255,255,255,0.4)\",\n\tbackground: \"transparent\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n\tfontSize: 13,\n};\n\nconst toolbarButtonFinalizeStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarHintStyle: CSSProperties = {\n\topacity: 0.8,\n\tfontSize: 12,\n};\n\nconst pinDotStyle: CSSProperties = {\n\twidth: 24,\n\theight: 24,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tborder: \"2px solid white\",\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 700,\n\tboxShadow: \"0 2px 6px rgba(0,0,0,0.3)\",\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tpadding: 0,\n};\n\nconst popoverStyle: CSSProperties = {\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tpadding: 12,\n\twidth: 280,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tcolor: \"#111\",\n};\n\nconst textareaStyle: CSSProperties = {\n\twidth: \"100%\",\n\tborder: \"1px solid #d1d5db\",\n\tborderRadius: 6,\n\tpadding: 8,\n\tfontSize: 14,\n\tresize: \"vertical\",\n\tfontFamily: \"inherit\",\n\tcolor: \"#111\",\n\tbackground: \"white\",\n\tboxSizing: \"border-box\",\n};\n\nconst popoverActionsStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 6,\n};\n\nconst baseButton: CSSProperties = {\n\tborder: \"1px solid transparent\",\n\tborderRadius: 6,\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n};\n\nconst primaryButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"#111\",\n\tcolor: \"white\",\n};\n\nconst ghostButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n};\n\nconst dangerButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#b91c1c\",\n\tborderColor: \"#fecaca\",\n};\n\nconst attachmentRowStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tflexWrap: \"wrap\",\n\tgap: 6,\n};\n\nconst attachmentThumbWrapStyle: CSSProperties = {\n\tposition: \"relative\",\n\twidth: 56,\n\theight: 56,\n\tborderRadius: 6,\n\toverflow: \"hidden\",\n\tborder: \"1px solid #e5e7eb\",\n\tbackground: \"#f9fafb\",\n};\n\nconst attachmentThumbStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: \"100%\",\n\tobjectFit: \"cover\",\n\tdisplay: \"block\",\n};\n\nconst attachmentRemoveStyle: CSSProperties = {\n\tposition: \"absolute\",\n\ttop: 2,\n\tright: 2,\n\twidth: 18,\n\theight: 18,\n\tborderRadius: 999,\n\tborder: \"none\",\n\tbackground: \"rgba(17, 17, 17, 0.85)\",\n\tcolor: \"white\",\n\tfontSize: 13,\n\tlineHeight: \"16px\",\n\tcursor: \"pointer\",\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst errorTextStyle: CSSProperties = {\n\tcolor: \"#b91c1c\",\n\tfontSize: 12,\n\tmargin: 0,\n};\n\nconst sidebarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\ttop: 0,\n\tright: 0,\n\tbottom: 0,\n\twidth: 360,\n\tmaxWidth: \"92vw\",\n\tbackground: \"white\",\n\tborderLeft: \"1px solid #e5e7eb\",\n\tboxShadow: \"-12px 0 32px rgba(0,0,0,0.12)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n};\n\nconst sidebarHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"space-between\",\n\tpadding: \"12px 16px\",\n\tborderBottom: \"1px solid #e5e7eb\",\n};\n\nconst sidebarScrollStyle: CSSProperties = {\n\tflex: 1,\n\toverflow: \"auto\",\n\tpadding: \"12px 12px 24px\",\n};\n\nconst sidebarEmptyStyle: CSSProperties = {\n\tcolor: \"#6b7280\",\n\tfontSize: 13,\n\tpadding: \"24px 8px\",\n\ttextAlign: \"center\",\n};\n\nconst sidebarPathStyle: CSSProperties = {\n\tfontSize: 11,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n\tcolor: \"#6b7280\",\n\tpadding: \"4px 4px 6px\",\n\twordBreak: \"break-all\",\n};\n\nconst sidebarItemStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"flex-start\",\n\tgap: 8,\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 6,\n\tpadding: \"8px 10px\",\n\tcursor: \"pointer\",\n\tfontSize: 13,\n\tmarginBottom: 6,\n\tcolor: \"#111\",\n};\n\nconst sidebarItemIndexStyle: CSSProperties = {\n\tflexShrink: 0,\n\twidth: 22,\n\theight: 22,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tfontWeight: 700,\n\tfontSize: 11,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst sidebarItemTextStyle: CSSProperties = {\n\tflex: 1,\n\twhiteSpace: \"pre-wrap\",\n\twordBreak: \"break-word\",\n};\n\nconst sidebarItemDoneStyle: CSSProperties = {\n\tflexShrink: 0,\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 600,\n\tpadding: \"2px 6px\",\n\tborderRadius: 4,\n\talignSelf: \"center\",\n};\n\nconst submittedPanelStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tpadding: \"12px 16px\",\n\twidth: \"min(420px, calc(100vw - 32px))\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 12,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n\tpointerEvents: \"auto\",\n};\n\nconst submittedHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n};\n\nconst submittedBadgeStyle: CSSProperties = {\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 700,\n\tpadding: \"2px 8px\",\n\tborderRadius: 999,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n};\n\nconst submittedProgressLabelStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tjustifyContent: \"space-between\",\n\tfontSize: 12,\n};\n\nconst submittedProgressTrackStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: 6,\n\tbackground: \"#f3f4f6\",\n\tborderRadius: 999,\n\toverflow: \"hidden\",\n};\n\nconst submittedProgressFillStyle: CSSProperties = {\n\theight: \"100%\",\n\tbackground: \"#10b981\",\n\tborderRadius: 999,\n\ttransition: \"width 500ms\",\n};\n\nconst submittedEtaStyle: CSSProperties = {\n\tmargin: 0,\n\tfontSize: 12,\n\tcolor: \"#6b7280\",\n};\n\nexport function mountFeedbackToolbar(): { unmount: () => void } | null {\n\tconsole.log(\"[YNS Feedback Toolbar] mountFeedbackToolbar() called\", {\n\t\tisWindow: typeof window !== \"undefined\",\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\talreadyMounted: typeof document !== \"undefined\" && Boolean(document.getElementById(MOUNT_NODE_ID)),\n\t});\n\tif (typeof window === \"undefined\") return null;\n\tif (process.env.NEXT_PUBLIC_VERCEL_ENV !== \"preview\") {\n\t\tconsole.log(\n\t\t\t\"[YNS Feedback Toolbar] gate failed — NEXT_PUBLIC_VERCEL_ENV is\",\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\t);\n\t\treturn null;\n\t}\n\tif (document.getElementById(MOUNT_NODE_ID)) return null;\n\n\tconst container = document.createElement(\"div\");\n\tcontainer.id = MOUNT_NODE_ID;\n\tcontainer.dataset.ynsFeedbackUi = \"true\";\n\tdocument.body.appendChild(container);\n\n\tconst root = createRoot(container);\n\troot.render(<FeedbackToolbar />);\n\n\treturn {\n\t\tunmount: () => {\n\t\t\troot.unmount();\n\t\t\tcontainer.remove();\n\t\t},\n\t};\n}\n\n// Auto-mount on import. Wait for DOM ready.\nif (typeof window !== \"undefined\") {\n\tconsole.log(\"[YNS Feedback Toolbar] module evaluated\", {\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\treadyState: document.readyState,\n\t\twillAutoMount: process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\",\n\t});\n}\nif (typeof window !== \"undefined\" && process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\") {\n\tif (document.readyState === \"loading\") {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\t\tmountFeedbackToolbar();\n\t\t});\n\t} else {\n\t\tmountFeedbackToolbar();\n\t}\n}\n","/**\n * Sandbox Inspectors — side-effect entry.\n *\n * Auto-mounts the design-mode + comment-mode inspectors inside per-store\n * storefronts running under the AI Builder sandbox dev server. Communicates\n * with the parent /design iframe via postMessage.\n *\n * Always-on: inspectors are passive postMessage listeners and don't affect the\n * page until the AI Builder iframe sends a `*-mode-toggle` message. Gating on\n * NODE_ENV is wrong (always \"production\" on Vercel) and any build-time gate\n * gets folded out by the commerce-kit bundle, leaving an empty file.\n */\n\n/**\n * Exported so `browser.tsx` can `export { startSandboxInspectors }` — the\n * named binding anchors this module against esbuild tree-shaking when\n * commerce-kit is bundled. Without it, an `export * from \"./sandbox-inspectors\"`\n * re-exports an empty binding set and esbuild DCEs the whole file.\n */\nexport function startSandboxInspectors(): void {\n\tif (typeof window === \"undefined\") return;\n\tconsole.log(\"[YNS Sandbox Inspectors] module evaluated\");\n\tinitDesignModeInspector();\n\tinitCommentModeInspector();\n}\n\n// Auto-start on import.\nstartSandboxInspectors();\n\ninterface ElementInfo {\n\ttag: string;\n\tid: string | undefined;\n\tclasses: string[];\n\ttextContent: string;\n\tcssSelector: string;\n\tboundingRect: { top: number; left: number; width: number; height: number };\n\tcomputedStyles: Partial<{\n\t\tcolor: string;\n\t\tbackgroundColor: string;\n\t\tfontSize: string;\n\t\tfontFamily: string;\n\t\tpadding: string;\n\t\tmargin: string;\n\t}>;\n}\n\nconst IGNORED_TAGS = new Set([\"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\", \"HTML\"]);\n\nfunction generateCssSelector(el: Element): string {\n\tif (el.id) return `${el.tagName.toLowerCase()}#${CSS.escape(el.id)}`;\n\n\tconst parts: string[] = [];\n\tlet current: Element | null = el;\n\twhile (current && current !== document.body && current !== document.documentElement) {\n\t\tlet selector = current.tagName.toLowerCase();\n\n\t\tif (current.id) {\n\t\t\tparts.unshift(`${selector}#${CSS.escape(current.id)}`);\n\t\t\tbreak;\n\t\t}\n\n\t\tconst classes = Array.from(current.classList).filter((c) => !c.startsWith(\"data-yns-\"));\n\t\tif (classes.length > 0) {\n\t\t\tselector += `.${classes.map((c) => CSS.escape(c)).join(\".\")}`;\n\t\t}\n\n\t\tconst parent: Element | null = current.parentElement;\n\t\tconst tag = current.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings = Array.from(parent.children).filter(\n\t\t\t\t(s) => s.tagName === tag && (classes.length === 0 || classes.every((c) => s.classList.contains(c))),\n\t\t\t);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst index = Array.from(parent.children).indexOf(current) + 1;\n\t\t\t\tselector += `:nth-child(${index})`;\n\t\t\t}\n\t\t}\n\n\t\tparts.unshift(selector);\n\t\tcurrent = current.parentElement;\n\t}\n\treturn parts.join(\" > \");\n}\n\nfunction buildElementInfo(el: Element, textLimit: number): ElementInfo {\n\tconst rect = el.getBoundingClientRect();\n\tconst computed = window.getComputedStyle(el);\n\tconst text = (el.textContent ?? \"\").trim();\n\treturn {\n\t\ttag: el.tagName.toLowerCase(),\n\t\tid: el.id || undefined,\n\t\tclasses: Array.from(el.classList).filter((c) => !c.startsWith(\"data-yns-\")),\n\t\ttextContent: text.length > textLimit ? `${text.slice(0, textLimit)}…` : text,\n\t\tcssSelector: generateCssSelector(el),\n\t\tboundingRect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },\n\t\tcomputedStyles: {\n\t\t\tcolor: computed.color,\n\t\t\tbackgroundColor: computed.backgroundColor,\n\t\t\tfontSize: computed.fontSize,\n\t\t\tfontFamily: computed.fontFamily,\n\t\t\tpadding: computed.padding,\n\t\t\tmargin: computed.margin,\n\t\t},\n\t};\n}\n\nfunction shouldIgnore(el: Element | null, overlayAttrs: string[]): el is null {\n\tif (!el || !el.tagName) return true;\n\tif (IGNORED_TAGS.has(el.tagName)) return true;\n\tfor (const attr of overlayAttrs) {\n\t\tif (el.hasAttribute?.(attr)) return true;\n\t}\n\treturn false;\n}\n\nfunction initDesignModeInspector() {\n\tlet enabled = false;\n\tlet hoveredElement: Element | null = null;\n\tlet selectedElement: { el: Element; cssSelector: string } | null = null;\n\n\tconst overlay = document.createElement(\"div\");\n\toverlay.setAttribute(\"data-yns-design-overlay\", \"hover\");\n\toverlay.style.cssText = [\n\t\t\"position: fixed\",\n\t\t\"pointer-events: none\",\n\t\t\"z-index: 2147483646\",\n\t\t\"border: 2px solid #3b82f6\",\n\t\t\"background: rgba(59, 130, 246, 0.08)\",\n\t\t\"border-radius: 3px\",\n\t\t\"display: none\",\n\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t].join(\";\");\n\n\tconst label = document.createElement(\"div\");\n\tlabel.setAttribute(\"data-yns-design-overlay\", \"label\");\n\tlabel.style.cssText = [\n\t\t\"position: fixed\",\n\t\t\"pointer-events: none\",\n\t\t\"z-index: 2147483647\",\n\t\t\"background: #3b82f6\",\n\t\t\"color: #fff\",\n\t\t\"font-size: 11px\",\n\t\t\"font-family: ui-monospace, monospace\",\n\t\t\"padding: 2px 6px\",\n\t\t\"border-radius: 3px\",\n\t\t\"white-space: nowrap\",\n\t\t\"display: none\",\n\t].join(\";\");\n\n\tdocument.documentElement.appendChild(overlay);\n\tdocument.documentElement.appendChild(label);\n\n\tconst style = document.createElement(\"style\");\n\tstyle.setAttribute(\"data-yns-design-overlay\", \"style\");\n\tstyle.textContent = \"[data-yns-selected] { outline: 2px solid #3b82f6 !important; outline-offset: 1px; }\";\n\tdocument.head.appendChild(style);\n\n\tfunction positionOverlay(el: Element) {\n\t\tconst rect = el.getBoundingClientRect();\n\t\toverlay.style.top = `${rect.top}px`;\n\t\toverlay.style.left = `${rect.left}px`;\n\t\toverlay.style.width = `${rect.width}px`;\n\t\toverlay.style.height = `${rect.height}px`;\n\t\toverlay.style.display = \"block\";\n\n\t\tconst classes = Array.from(el.classList).filter((c) => !c.startsWith(\"data-yns-\"));\n\t\tconst labelText = el.tagName.toLowerCase() + (classes.length ? `.${classes[0]}` : \"\");\n\t\tlabel.textContent = labelText;\n\t\tlabel.style.left = `${rect.left}px`;\n\t\tlabel.style.top = `${Math.max(0, rect.top - 22)}px`;\n\t\tlabel.style.display = \"block\";\n\t}\n\n\tconst handleMouseMove = (e: MouseEvent) => {\n\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\tif (shouldIgnore(el, [\"data-yns-design-overlay\"])) {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thoveredElement = null;\n\t\t\treturn;\n\t\t}\n\t\thoveredElement = el;\n\t\trequestAnimationFrame(() => {\n\t\t\tif (hoveredElement === el && enabled && el) {\n\t\t\t\tpositionOverlay(el);\n\t\t\t}\n\t\t});\n\t};\n\n\tconst handleMouseLeave = () => {\n\t\toverlay.style.display = \"none\";\n\t\tlabel.style.display = \"none\";\n\t\thoveredElement = null;\n\t};\n\n\tconst handleClick = (e: MouseEvent) => {\n\t\tif (!enabled || !hoveredElement) return;\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t\te.stopImmediatePropagation();\n\n\t\tconst el = hoveredElement;\n\t\tif (shouldIgnore(el, [\"data-yns-design-overlay\"])) return;\n\n\t\tconst info = buildElementInfo(el, 120);\n\t\tconst selector = info.cssSelector;\n\n\t\tif (selectedElement) {\n\t\t\tselectedElement.el.removeAttribute(\"data-yns-selected\");\n\t\t}\n\n\t\tif (selectedElement && selectedElement.cssSelector === selector) {\n\t\t\tselectedElement = null;\n\t\t\twindow.parent.postMessage({ type: \"element-deselected\", data: info }, \"*\");\n\t\t} else {\n\t\t\tselectedElement = { el, cssSelector: selector };\n\t\t\tel.setAttribute(\"data-yns-selected\", \"\");\n\t\t\twindow.parent.postMessage({ type: \"element-selected\", data: info }, \"*\");\n\t\t}\n\t};\n\n\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\tif (e.key === \"Escape\" && enabled) {\n\t\t\tdisableDesignMode();\n\t\t\twindow.parent.postMessage({ type: \"design-mode-cleared\" }, \"*\");\n\t\t}\n\t};\n\n\tfunction enableDesignMode() {\n\t\tenabled = true;\n\t\tdocument.addEventListener(\"mousemove\", handleMouseMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleMouseLeave);\n\t\tdocument.addEventListener(\"click\", handleClick, true);\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown, true);\n\t}\n\n\tfunction disableDesignMode() {\n\t\tenabled = false;\n\t\tdocument.removeEventListener(\"mousemove\", handleMouseMove, true);\n\t\tdocument.removeEventListener(\"mouseleave\", handleMouseLeave);\n\t\tdocument.removeEventListener(\"click\", handleClick, true);\n\t\tdocument.removeEventListener(\"keydown\", handleKeyDown, true);\n\n\t\toverlay.style.display = \"none\";\n\t\tlabel.style.display = \"none\";\n\t\thoveredElement = null;\n\n\t\tif (selectedElement) {\n\t\t\tselectedElement.el.removeAttribute(\"data-yns-selected\");\n\t\t\tselectedElement = null;\n\t\t}\n\t}\n\n\twindow.addEventListener(\"message\", (event) => {\n\t\tconst msg = event.data;\n\t\tif (!msg || typeof msg !== \"object\") return;\n\n\t\tif (msg.type === \"design-mode-toggle\") {\n\t\t\tif (msg.enabled) {\n\t\t\t\tenableDesignMode();\n\t\t\t} else {\n\t\t\t\tdisableDesignMode();\n\t\t\t\twindow.parent.postMessage({ type: \"design-mode-cleared\" }, \"*\");\n\t\t\t}\n\t\t}\n\n\t\tif (msg.type === \"design-mode-deselect\") {\n\t\t\tif (selectedElement) {\n\t\t\t\tselectedElement.el.removeAttribute(\"data-yns-selected\");\n\t\t\t\tselectedElement = null;\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction initCommentModeInspector() {\n\tlet enabled = false;\n\tlet hoveredElement: Element | null = null;\n\tlet pins: Array<{ id: string; selector: string; number: number }> = [];\n\n\tconst overlay = document.createElement(\"div\");\n\toverlay.setAttribute(\"data-yns-comment-overlay\", \"hover\");\n\toverlay.style.cssText = [\n\t\t\"position: fixed\",\n\t\t\"pointer-events: none\",\n\t\t\"z-index: 2147483644\",\n\t\t\"border: 2px dashed #10b981\",\n\t\t\"background: rgba(16, 185, 129, 0.06)\",\n\t\t\"border-radius: 3px\",\n\t\t\"display: none\",\n\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t].join(\";\");\n\n\tconst cursorLabel = document.createElement(\"div\");\n\tcursorLabel.setAttribute(\"data-yns-comment-overlay\", \"cursor-label\");\n\tcursorLabel.textContent = \"Click to comment\";\n\tcursorLabel.style.cssText = [\n\t\t\"position: fixed\",\n\t\t\"pointer-events: none\",\n\t\t\"z-index: 2147483645\",\n\t\t\"background: #059669\",\n\t\t\"color: #fff\",\n\t\t\"font-size: 11px\",\n\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\"padding: 3px 8px\",\n\t\t\"border-radius: 4px\",\n\t\t\"white-space: nowrap\",\n\t\t\"display: none\",\n\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t].join(\";\");\n\n\tconst pinContainer = document.createElement(\"div\");\n\tpinContainer.setAttribute(\"data-yns-comment-overlay\", \"pins\");\n\tpinContainer.style.cssText =\n\t\t\"position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2147483643;\";\n\n\tdocument.documentElement.appendChild(overlay);\n\tdocument.documentElement.appendChild(cursorLabel);\n\tdocument.documentElement.appendChild(pinContainer);\n\n\tfunction renderPins() {\n\t\tpinContainer.innerHTML = \"\";\n\t\tfor (const pin of pins) {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(pin.selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) continue;\n\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tconst pinEl = document.createElement(\"div\");\n\t\t\tpinEl.style.cssText = [\n\t\t\t\t\"position: fixed\",\n\t\t\t\t`top: ${r.top - 12}px`,\n\t\t\t\t`left: ${r.left + r.width / 2 - 12}px`,\n\t\t\t\t\"width: 24px\",\n\t\t\t\t\"height: 24px\",\n\t\t\t\t\"border-radius: 50%\",\n\t\t\t\t\"background: #059669\",\n\t\t\t\t\"color: #fff\",\n\t\t\t\t\"display: flex\",\n\t\t\t\t\"align-items: center\",\n\t\t\t\t\"justify-content: center\",\n\t\t\t\t\"font-size: 12px\",\n\t\t\t\t\"font-weight: 600\",\n\t\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\t\"box-shadow: 0 2px 8px rgba(0,0,0,0.2)\",\n\t\t\t\t\"pointer-events: none\",\n\t\t\t].join(\";\");\n\t\t\tpinEl.textContent = String(pin.number);\n\t\t\tpinContainer.appendChild(pinEl);\n\t\t}\n\t}\n\n\tconst handleMouseMove = (e: MouseEvent) => {\n\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\tif (shouldIgnore(el, [\"data-yns-comment-overlay\", \"data-yns-design-overlay\"])) {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tcursorLabel.style.display = \"none\";\n\t\t\thoveredElement = null;\n\t\t\treturn;\n\t\t}\n\t\thoveredElement = el;\n\t\trequestAnimationFrame(() => {\n\t\t\tif (hoveredElement === el && enabled && el) {\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\n\t\t\t\tcursorLabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tcursorLabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tcursorLabel.style.display = \"block\";\n\t\t\t}\n\t\t});\n\t};\n\n\tconst handleMouseLeave = () => {\n\t\toverlay.style.display = \"none\";\n\t\tcursorLabel.style.display = \"none\";\n\t\thoveredElement = null;\n\t};\n\n\tconst handleClick = (e: MouseEvent) => {\n\t\tif (!enabled || !hoveredElement) return;\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t\te.stopImmediatePropagation();\n\n\t\tconst el = hoveredElement;\n\t\tif (shouldIgnore(el, [\"data-yns-comment-overlay\", \"data-yns-design-overlay\"])) return;\n\n\t\tconst info = buildElementInfo(el, 200);\n\t\tconst rect = el.getBoundingClientRect();\n\n\t\twindow.parent.postMessage(\n\t\t\t{\n\t\t\t\ttype: \"comment-click\",\n\t\t\t\tdata: {\n\t\t\t\t\telement: info,\n\t\t\t\t\tclickPosition: { x: e.clientX, y: e.clientY },\n\t\t\t\t\telementRect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },\n\t\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"*\",\n\t\t);\n\t};\n\n\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\tif (e.key === \"Escape\" && enabled) {\n\t\t\tdisableCommentMode();\n\t\t\twindow.parent.postMessage({ type: \"comment-mode-cleared\" }, \"*\");\n\t\t}\n\t};\n\n\tfunction enableCommentMode() {\n\t\tenabled = true;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\tdocument.addEventListener(\"mousemove\", handleMouseMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleMouseLeave);\n\t\tdocument.addEventListener(\"click\", handleClick, true);\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown, true);\n\t}\n\n\tfunction disableCommentMode() {\n\t\tenabled = false;\n\t\tdocument.body.style.cursor = \"\";\n\t\tdocument.removeEventListener(\"mousemove\", handleMouseMove, true);\n\t\tdocument.removeEventListener(\"mouseleave\", handleMouseLeave);\n\t\tdocument.removeEventListener(\"click\", handleClick, true);\n\t\tdocument.removeEventListener(\"keydown\", handleKeyDown, true);\n\n\t\toverlay.style.display = \"none\";\n\t\tcursorLabel.style.display = \"none\";\n\t\thoveredElement = null;\n\t}\n\n\tlet rafPending = false;\n\tconst scheduleRender = () => {\n\t\tif (rafPending) return;\n\t\trafPending = true;\n\t\trequestAnimationFrame(() => {\n\t\t\trafPending = false;\n\t\t\trenderPins();\n\t\t});\n\t};\n\twindow.addEventListener(\"scroll\", scheduleRender, { passive: true });\n\twindow.addEventListener(\"resize\", scheduleRender, { passive: true });\n\n\twindow.addEventListener(\"message\", (event) => {\n\t\tconst msg = event.data;\n\t\tif (!msg || typeof msg !== \"object\") return;\n\n\t\tif (msg.type === \"comment-mode-toggle\") {\n\t\t\tif (msg.enabled) enableCommentMode();\n\t\t\telse disableCommentMode();\n\t\t}\n\n\t\tif (msg.type === \"comment-pins-update\") {\n\t\t\tpins = msg.pins ?? [];\n\t\t\trenderPins();\n\t\t}\n\n\t\tif (msg.type === \"comment-pin-remove\") {\n\t\t\tpins = pins.filter((p) => p.id !== msg.pinId);\n\t\t\trenderPins();\n\t\t}\n\t});\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","createRoot","Fragment","jsx","jsxs","MOUNT_NODE_ID","buildApiBase","override","host","protocol","computeCssSelector","el","path","node","part","className","classes","parent","tag","siblings","idx","ATTRS_OF_INTEREST","formatAttrs","parts","attr","v","trimmed","formatTextContent","text","buildSurroundingHtml","target","ancestors","cur","lines","depth","ancestor","indent","targetIndent","targetTag","targetText","childIndent","children","child","childText","i","isInsideToolbar","IGNORED_TAGS","POLL_INTERVAL_MS","formatEta","etaIso","eta","remainingMs","dateLabel","remainingMin","remainingHours","remainingDays","FeedbackToolbar","session","setSession","loading","setLoading","pinMode","setPinMode","pending","setPending","editingId","setEditingId","sidebarOpen","setSidebarOpen","finalizing","setFinalizing","apiBase","cancelled","controller","fetchOnce","res","data","interval","overlay","label","hovered","handleMove","e","rect","handleLeave","handleClick","event","offsetXRatio","offsetYRatio","refreshComments","submitNewComment","content","attachments","updateComment","id","removeComment","finalizeSession","nextStatus","prev","scrollToComment","comment","SubmittedPanel","visiblePins","c","pin","PinOverlay","PendingCommentPopover","CommentsSidebar","toolbarStyle","toolbarButtonActiveStyle","toolbarButtonStyle","toolbarButtonGhostStyle","toolbarButtonFinalizeStyle","toolbarHintStyle","progress","status","setTick","t","submittedPanelStyle","spinnerKeyframes","submittedHeaderStyle","Spinner","submittedBadgeStyle","submittedProgressLabelStyle","submittedProgressTrackStyle","submittedProgressFillStyle","submittedEtaStyle","size","number","editing","feedbackSessionId","onStartEdit","onCancelEdit","onSave","onRemove","useTargetPosition","pinDotStyle","EditPopover","onCancel","comments","currentPath","onClose","onSelect","groups","list","paths","a","b","sidebarStyle","sidebarHeaderStyle","ghostButtonStyle","sidebarScrollStyle","sidebarEmptyStyle","sidebarPathStyle","sidebarItemStyle","sidebarItemIndexStyle","sidebarItemTextStyle","sidebarItemDoneStyle","MAX_ATTACHMENTS","MAX_ATTACHMENT_BYTES","readImageDimensions","file","resolve","url","img","initial","initialAttachments","value","setValue","setAttachments","saving","setSaving","uploading","setUploading","error","setError","textareaRef","fileInputRef","len","handleSubmit","handleFiles","files","slots","picked","dims","fd","body","removeAttachment","popoverStyle","textareaStyle","attachmentRowStyle","att","attachmentThumbWrapStyle","attachmentThumbStyle","attachmentRemoveStyle","errorTextStyle","popoverActionsStyle","dangerButtonStyle","primaryButtonStyle","selector","pos","setPos","update","r","ro","baseButton","mountFeedbackToolbar","container","root","startSandboxInspectors","initDesignModeInspector","initCommentModeInspector","IGNORED_TAGS","generateCssSelector","el","parts","current","selector","classes","c","parent","tag","s","index","buildElementInfo","textLimit","rect","computed","text","shouldIgnore","overlayAttrs","attr","enabled","hoveredElement","selectedElement","overlay","label","style","positionOverlay","labelText","handleMouseMove","e","handleMouseLeave","handleClick","info","handleKeyDown","disableDesignMode","enableDesignMode","event","msg","pins","cursorLabel","pinContainer","renderPins","pin","r","pinEl","disableCommentMode","enableCommentMode","rafPending","scheduleRender","p"]}
|
|
1
|
+
{"version":3,"sources":["../src/feedback-toolbar.tsx","../src/sandbox-inspectors.ts"],"sourcesContent":["/**\n * Feedback session toolbar — side-effect entry.\n *\n * Importing `commerce-kit/feedback-toolbar` (or `commerce-kit/browser` for the\n * combined entry) mounts a floating toolbar onto the page that lets reviewers\n * leave click-anchored comments. Comments persist via YNS API endpoints,\n * authenticated with the `better-auth` session cookie sent through\n * `credentials: \"include\"`.\n *\n * Gated on `process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\"` — only Vercel\n * preview deploys get the toolbar. Sandbox dev (env undefined) and production\n * (env === \"production\") skip it.\n */\n\nimport { type CSSProperties, type FormEvent, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst MOUNT_NODE_ID = \"yns-feedback-toolbar-root\";\n\ninterface FeedbackAttachment {\n\turl: string;\n\tcontentType: string;\n\twidth?: number;\n\theight?: number;\n}\n\ninterface FeedbackComment {\n\tid: string;\n\tpagePath: string;\n\tcssSelector: string;\n\tcontent: string;\n\tstatus: \"todo\" | \"done\";\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n\tattachments: FeedbackAttachment[];\n}\n\ntype SessionStatus = \"created\" | \"in_progress\" | \"processing\" | \"in_review\" | \"done\";\n\ninterface ReviewProgress {\n\tfillPct: number;\n\tlabel: string;\n}\n\ninterface ActiveSession {\n\tfeedbackSessionId: string;\n\tcomments: FeedbackComment[];\n\tcanComment: boolean;\n\tsessionStatus: SessionStatus;\n\tcommentTotal: number;\n\tcommentDone: number;\n\teta: string;\n\tprogress: ReviewProgress;\n}\n\ninterface PendingPin {\n\tcssSelector: string;\n\tpagePath: string;\n\tsurroundingHtml: string;\n\trect: { top: number; left: number; width: number; height: number };\n\tclickX: number;\n\tclickY: number;\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n}\n\nconst buildApiBase = (): string | null => {\n\tif (typeof window === \"undefined\") return null;\n\tconst override = (process.env.NEXT_PUBLIC_YNS_API_BASE ?? \"\").trim();\n\tif (override) return override.replace(/\\/$/, \"\");\n\n\t// Toolbar runs on a per-store preview deploy (mystore-preview.yns.{store|cx})\n\t// served by the merchant's Vercel project — that host has no YNS API. Aim\n\t// at the apex `yns.store` / `yns.cx`, which yns-app serves and where the\n\t// API lives. Cookies scoped to `.yns.store` / `.yns.cx` travel along.\n\tconst host = window.location.hostname;\n\tconst protocol = window.location.protocol;\n\tif (host.endsWith(\".yns.store\")) return `${protocol}//yns.store`;\n\tif (host.endsWith(\".yns.cx\")) return `${protocol}//yns.cx`;\n\treturn window.location.origin;\n};\n\nconst computeCssSelector = (el: Element): string => {\n\tif (el.id) return `#${CSS.escape(el.id)}`;\n\tconst path: string[] = [];\n\tlet node: Element | null = el;\n\twhile (node && node.nodeType === Node.ELEMENT_NODE && path.length < 6) {\n\t\tlet part = node.tagName.toLowerCase();\n\t\tconst className = node.getAttribute(\"class\");\n\t\tif (className) {\n\t\t\tconst classes = className\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.slice(0, 2)\n\t\t\t\t.map((c) => `.${CSS.escape(c)}`)\n\t\t\t\t.join(\"\");\n\t\t\tpart += classes;\n\t\t}\n\t\tconst parent: Element | null = node.parentElement;\n\t\tconst tag = node.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings: Element[] = Array.from(parent.children).filter((c) => c.tagName === tag);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst idx = siblings.indexOf(node) + 1;\n\t\t\t\tpart += `:nth-of-type(${idx})`;\n\t\t\t}\n\t\t}\n\t\tpath.unshift(part);\n\t\tnode = parent;\n\t}\n\treturn path.join(\" > \");\n};\n\nconst ATTRS_OF_INTEREST = [\"id\", \"class\", \"data-testid\", \"aria-label\", \"role\", \"name\", \"href\", \"src\"];\n\nconst formatAttrs = (el: Element): string => {\n\tconst parts: string[] = [];\n\tfor (const attr of ATTRS_OF_INTEREST) {\n\t\tconst v = el.getAttribute(attr);\n\t\tif (!v) continue;\n\t\tconst trimmed = v.length > 80 ? `${v.slice(0, 80)}…` : v;\n\t\tparts.push(` ${attr}=\"${trimmed.replace(/\"/g, \""\")}\"`);\n\t}\n\treturn parts.join(\"\");\n};\n\nconst formatTextContent = (el: Element): string => {\n\tconst text = (el.textContent ?? \"\").replace(/\\s+/g, \" \").trim();\n\tif (!text) return \"\";\n\treturn text.length > 100 ? `${text.slice(0, 100)}…` : text;\n};\n\nconst buildSurroundingHtml = (target: Element): string => {\n\tconst ancestors: Element[] = [];\n\tlet cur: Element | null = target;\n\twhile (cur && cur !== document.documentElement && ancestors.length < 5) {\n\t\tconst parent: Element | null = cur.parentElement;\n\t\tif (!parent) break;\n\t\tancestors.unshift(parent);\n\t\tcur = parent;\n\t}\n\n\tconst lines: string[] = [];\n\tlet depth = 0;\n\tfor (const ancestor of ancestors) {\n\t\tconst indent = \" \".repeat(depth);\n\t\tlines.push(`${indent}<${ancestor.tagName.toLowerCase()}${formatAttrs(ancestor)}>`);\n\t\tdepth++;\n\t}\n\n\tconst targetIndent = \" \".repeat(depth);\n\tconst targetTag = target.tagName.toLowerCase();\n\tconst targetText = formatTextContent(target);\n\tlines.push(\n\t\t`${targetIndent}<${targetTag}${formatAttrs(target)}>${\n\t\t\ttargetText ? `${targetText}</${targetTag}>` : \"\"\n\t\t} ← TARGET`,\n\t);\n\n\tif (!targetText) {\n\t\tconst childIndent = \" \".repeat(depth + 1);\n\t\tconst children = Array.from(target.children).slice(0, 6);\n\t\tfor (const child of children) {\n\t\t\tconst childText = formatTextContent(child);\n\t\t\tlines.push(\n\t\t\t\t`${childIndent}<${child.tagName.toLowerCase()}${formatAttrs(child)}>${\n\t\t\t\t\tchildText ? `${childText}</${child.tagName.toLowerCase()}>` : \"\"\n\t\t\t\t}`,\n\t\t\t);\n\t\t}\n\t\tif (target.children.length > 6) {\n\t\t\tlines.push(`${childIndent}… (${target.children.length - 6} more children)`);\n\t\t}\n\t\tlines.push(`${targetIndent}</${targetTag}>`);\n\t}\n\n\tfor (let i = ancestors.length - 1; i >= 0; i--) {\n\t\tconst indent = \" \".repeat(i);\n\t\tconst ancestor = ancestors[i];\n\t\tif (!ancestor) continue;\n\t\tlines.push(`${indent}</${ancestor.tagName.toLowerCase()}>`);\n\t}\n\n\treturn lines.join(\"\\n\");\n};\n\nconst isInsideToolbar = (el: Element | null): boolean => {\n\tlet node = el;\n\twhile (node) {\n\t\tif (node instanceof HTMLElement && node.dataset.ynsFeedbackUi === \"true\") return true;\n\t\tnode = node.parentElement;\n\t}\n\treturn false;\n};\n\nconst IGNORED_TAGS = new Set([\"HTML\", \"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\"]);\n\nconst POLL_INTERVAL_MS = 10_000;\n\n// Format a server-computed ETA as remaining + absolute label. The deadline\n// math itself lives in yns-app's `lib/feedback-comments-api.ts` so the toolbar\n// and YNS lock screen stay in sync.\nconst formatEta = (etaIso: string): string => {\n\tconst eta = new Date(etaIso);\n\tconst remainingMs = eta.getTime() - Date.now();\n\tconst dateLabel = eta.toLocaleString(undefined, {\n\t\tweekday: \"short\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t});\n\n\tif (remainingMs <= 0) return `Any moment now (estimated by ${dateLabel})`;\n\tconst remainingMin = Math.ceil(remainingMs / 60_000);\n\tif (remainingMin < 60) return `~${remainingMin} minutes (by ${dateLabel})`;\n\tconst remainingHours = Math.round(remainingMs / 3_600_000);\n\tif (remainingHours < 24) {\n\t\treturn `~${remainingHours} ${remainingHours === 1 ? \"hour\" : \"hours\"} (by ${dateLabel})`;\n\t}\n\tconst remainingDays = Math.round(remainingMs / 86_400_000);\n\treturn `~${remainingDays} ${remainingDays === 1 ? \"day\" : \"days\"} (by ${dateLabel})`;\n};\n\nfunction FeedbackToolbar() {\n\tconst [session, setSession] = useState<ActiveSession | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [pinMode, setPinMode] = useState(false);\n\tconst [pending, setPending] = useState<PendingPin | null>(null);\n\tconst [editingId, setEditingId] = useState<string | null>(null);\n\tconst [sidebarOpen, setSidebarOpen] = useState(false);\n\tconst [finalizing, setFinalizing] = useState(false);\n\tconst apiBase = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tapiBase.current = buildApiBase();\n\t\tif (!apiBase.current) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet cancelled = false;\n\t\tconst controller = new AbortController();\n\n\t\tconst fetchOnce = async () => {\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(\n\t\t\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t\t\t{ credentials: \"include\", signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tsetSession(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = (await res.json()) as ActiveSession;\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetSession(data);\n\t\t\t\t// Hard-refresh once the AI run lands and the preview is up to date.\n\t\t\t\t// Out of scope to diff anything smarter — the new deploy replaces\n\t\t\t\t// the page entirely so a full reload is appropriate here.\n\t\t\t\tif (data.sessionStatus === \"done\") {\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tif (!cancelled) setSession(null);\n\t\t\t} finally {\n\t\t\t\tif (!cancelled) setLoading(false);\n\t\t\t}\n\t\t};\n\n\t\tvoid fetchOnce();\n\t\tconst interval = window.setInterval(fetchOnce, POLL_INTERVAL_MS);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tcontroller.abort();\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, []);\n\n\t// When the session stops accepting comments (finalized → in_review, or\n\t// closed), tear down any in-flight UI so the hover overlay/popover/sidebar\n\t// don't linger after the toolbar's main render returns null.\n\tuseEffect(() => {\n\t\tif (!session || session.canComment) return;\n\t\tsetPinMode(false);\n\t\tsetPending(null);\n\t\tsetSidebarOpen(false);\n\t\tsetEditingId(null);\n\t}, [session]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\treturn () => {\n\t\t\tdocument.body.style.cursor = \"\";\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.dataset.ynsFeedbackUi = \"true\";\n\t\toverlay.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483644\",\n\t\t\t\"border: 2px dashed #10b981\",\n\t\t\t\"background: rgba(16, 185, 129, 0.08)\",\n\t\t\t\"border-radius: 3px\",\n\t\t\t\"display: none\",\n\t\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t\t].join(\";\");\n\n\t\tconst label = document.createElement(\"div\");\n\t\tlabel.dataset.ynsFeedbackUi = \"true\";\n\t\tlabel.textContent = \"Click to comment\";\n\t\tlabel.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483645\",\n\t\t\t\"background: #059669\",\n\t\t\t\"color: #fff\",\n\t\t\t\"font-size: 11px\",\n\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\"padding: 3px 8px\",\n\t\t\t\"border-radius: 4px\",\n\t\t\t\"white-space: nowrap\",\n\t\t\t\"display: none\",\n\t\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t\t].join(\";\");\n\n\t\tdocument.documentElement.appendChild(overlay);\n\t\tdocument.documentElement.appendChild(label);\n\n\t\tlet hovered: Element | null = null;\n\t\tconst handleMove = (e: MouseEvent) => {\n\t\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\t\tif (!el || IGNORED_TAGS.has(el.tagName) || isInsideToolbar(el)) {\n\t\t\t\toverlay.style.display = \"none\";\n\t\t\t\tlabel.style.display = \"none\";\n\t\t\t\thovered = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thovered = el;\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (hovered !== el) return;\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\t\t\t\tlabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tlabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tlabel.style.display = \"block\";\n\t\t\t});\n\t\t};\n\n\t\tconst handleLeave = () => {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thovered = null;\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleLeave);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMove, true);\n\t\t\tdocument.removeEventListener(\"mouseleave\", handleLeave);\n\t\t\toverlay.remove();\n\t\t\tlabel.remove();\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tconst handleClick = (event: MouseEvent) => {\n\t\t\tconst target = event.target;\n\t\t\tif (!(target instanceof Element)) return;\n\t\t\tif (isInsideToolbar(target)) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\tconst offsetXRatio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5;\n\t\t\tconst offsetYRatio = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;\n\t\t\tsetPending({\n\t\t\t\tcssSelector: computeCssSelector(target),\n\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\tsurroundingHtml: buildSurroundingHtml(target),\n\t\t\t\trect: {\n\t\t\t\t\ttop: rect.top + window.scrollY,\n\t\t\t\t\tleft: rect.left + window.scrollX,\n\t\t\t\t\twidth: rect.width,\n\t\t\t\t\theight: rect.height,\n\t\t\t\t},\n\t\t\t\tclickX: event.clientX + window.scrollX,\n\t\t\t\tclickY: event.clientY + window.scrollY,\n\t\t\t\toffsetXRatio: Math.min(1, Math.max(0, offsetXRatio)),\n\t\t\t\toffsetYRatio: Math.min(1, Math.max(0, offsetYRatio)),\n\t\t\t});\n\t\t\tsetPinMode(false);\n\t\t};\n\t\tdocument.addEventListener(\"click\", handleClick, { capture: true });\n\t\treturn () => document.removeEventListener(\"click\", handleClick, { capture: true });\n\t}, [pinMode]);\n\n\tconst refreshComments = async () => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(\n\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t{ credentials: \"include\" },\n\t\t);\n\t\tif (!res.ok) return;\n\t\tconst data = (await res.json()) as ActiveSession;\n\t\tsetSession(data);\n\t};\n\n\tconst submitNewComment = async (content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current || !session || !pending) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tfeedbackSessionId: session.feedbackSessionId,\n\t\t\t\tcontent,\n\t\t\t\tpagePath: pending.pagePath,\n\t\t\t\tcssSelector: pending.cssSelector,\n\t\t\t\tsurroundingHtml: pending.surroundingHtml,\n\t\t\t\toffsetXRatio: pending.offsetXRatio,\n\t\t\t\toffsetYRatio: pending.offsetYRatio,\n\t\t\t\t...(attachments.length > 0 ? { attachments } : {}),\n\t\t\t}),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetPending(null);\n\t\tsetPinMode(true);\n\t\tawait refreshComments();\n\t};\n\n\tconst updateComment = async (id: string, content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"PATCH\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ content, attachments }),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetEditingId(null);\n\t\tawait refreshComments();\n\t};\n\n\tconst removeComment = async (id: string) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"DELETE\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) return;\n\t\tawait refreshComments();\n\t};\n\n\tconst finalizeSession = async () => {\n\t\tif (!apiBase.current || !session) return;\n\t\tif (!window.confirm(\"Finalize this feedback session? You won't be able to add more comments.\")) return;\n\t\tsetFinalizing(true);\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase.current}/api/feedback-sessions/${session.feedbackSessionId}/finalize`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\" },\n\t\t\t);\n\t\t\tif (!res.ok) return;\n\t\t\t// Flip BOTH canComment and sessionStatus locally. Without the status\n\t\t\t// flip, the render guard sees `canComment=false` + `status=in_progress`\n\t\t\t// (a state the SubmittedPanel branch returns null for) and the toolbar\n\t\t\t// disappears for the ~10s gap until the next poll catches up.\n\t\t\tconst body = (await res.json().catch(() => null)) as { status?: SessionStatus } | null;\n\t\t\tconst nextStatus: SessionStatus = body?.status ?? \"processing\";\n\t\t\tsetSession((prev) => (prev ? { ...prev, canComment: false, sessionStatus: nextStatus } : prev));\n\t\t} finally {\n\t\t\tsetFinalizing(false);\n\t\t}\n\t};\n\n\tconst scrollToComment = (comment: FeedbackComment) => {\n\t\tif (comment.pagePath !== window.location.pathname) {\n\t\t\twindow.location.assign(comment.pagePath);\n\t\t\treturn;\n\t\t}\n\t\tlet el: Element | null = null;\n\t\ttry {\n\t\t\tel = document.querySelector(comment.cssSelector);\n\t\t} catch {\n\t\t\tel = null;\n\t\t}\n\t\tif (!el) return;\n\t\tel.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n\t\tsetEditingId(comment.id);\n\t};\n\n\tif (loading || !session) return null;\n\n\t// Session has been finalized for AI review. Show a small status panel with\n\t// the same progress + ETA shown in YNS, but no commenting controls. Polling\n\t// continues in the background; on `done` we hard-reload to pick up the\n\t// fresh deploy.\n\tif (!session.canComment) {\n\t\tif (session.sessionStatus !== \"processing\" && session.sessionStatus !== \"in_review\") return null;\n\t\treturn (\n\t\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t\t<SubmittedPanel progress={session.progress} eta={session.eta} status={session.sessionStatus} />\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst visiblePins = session.comments.filter(\n\t\t(c) => c.pagePath === window.location.pathname && c.status !== \"done\",\n\t);\n\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t{visiblePins.map((pin, idx) => (\n\t\t\t\t<PinOverlay\n\t\t\t\t\tkey={pin.id}\n\t\t\t\t\tpin={pin}\n\t\t\t\t\tnumber={idx + 1}\n\t\t\t\t\tediting={editingId === pin.id}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonStartEdit={() => setEditingId(pin.id)}\n\t\t\t\t\tonCancelEdit={() => setEditingId(null)}\n\t\t\t\t\tonSave={(content, attachments) => updateComment(pin.id, content, attachments)}\n\t\t\t\t\tonRemove={() => removeComment(pin.id)}\n\t\t\t\t/>\n\t\t\t))}\n\n\t\t\t{pending && (\n\t\t\t\t<PendingCommentPopover\n\t\t\t\t\tpending={pending}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonCancel={() => {\n\t\t\t\t\t\tsetPending(null);\n\t\t\t\t\t\tsetPinMode(true);\n\t\t\t\t\t}}\n\t\t\t\t\tonSave={(content, attachments) => submitNewComment(content, attachments)}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{sidebarOpen && (\n\t\t\t\t<CommentsSidebar\n\t\t\t\t\tcomments={session.comments}\n\t\t\t\t\tcurrentPath={window.location.pathname}\n\t\t\t\t\tonClose={() => setSidebarOpen(false)}\n\t\t\t\t\tonSelect={scrollToComment}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => setPinMode((v) => !v)}\n\t\t\t\t\tstyle={pinMode ? toolbarButtonActiveStyle : toolbarButtonStyle}\n\t\t\t\t>\n\t\t\t\t\t{pinMode ? \"Cancel\" : \"Add comment\"}\n\t\t\t\t</button>\n\t\t\t\t<button type=\"button\" onClick={() => setSidebarOpen((v) => !v)} style={toolbarButtonGhostStyle}>\n\t\t\t\t\t{sidebarOpen ? \"Hide list\" : `List (${session.comments.length})`}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={finalizeSession}\n\t\t\t\t\tdisabled={finalizing}\n\t\t\t\t\tstyle={toolbarButtonFinalizeStyle}\n\t\t\t\t>\n\t\t\t\t\t{finalizing ? \"Finalizing…\" : \"Finalize\"}\n\t\t\t\t</button>\n\t\t\t\t<span style={toolbarHintStyle}>\n\t\t\t\t\t{pinMode ? \"Click any element to comment\" : `${visiblePins.length} on this page`}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SubmittedPanel({\n\tprogress,\n\teta: etaIso,\n\tstatus,\n}: {\n\tprogress: ReviewProgress;\n\teta: string;\n\tstatus: SessionStatus;\n}) {\n\t// Re-render every minute so the relative ETA label stays fresh between\n\t// the 10s data polls (which only re-render when the API payload changes).\n\tconst [, setTick] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = window.setInterval(() => setTick((t) => t + 1), 60_000);\n\t\treturn () => window.clearInterval(id);\n\t}, []);\n\n\tconst eta = formatEta(etaIso);\n\tconst isInReview = status === \"in_review\";\n\tconst headline = isInReview ? \"Feedback under review\" : \"Applying feedback\";\n\n\treturn (\n\t\t<div style={submittedPanelStyle}>\n\t\t\t<style>{spinnerKeyframes}</style>\n\t\t\t<div style={submittedHeaderStyle}>\n\t\t\t\t<Spinner />\n\t\t\t\t<span style={submittedBadgeStyle}>Submitted</span>\n\t\t\t\t<strong style={{ fontSize: 13 }}>{headline}</strong>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressLabelStyle}>\n\t\t\t\t<span>Review progress</span>\n\t\t\t\t<span style={{ opacity: 0.7 }}>{progress.label}</span>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressTrackStyle}>\n\t\t\t\t<div style={{ ...submittedProgressFillStyle, width: `${progress.fillPct}%` }} />\n\t\t\t</div>\n\t\t\t<p style={submittedEtaStyle}>\n\t\t\t\tEstimated delivery: <span style={{ fontWeight: 600 }}>{eta}</span>\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n\n// Inline keyframes — toolbar avoids relying on a global stylesheet.\nconst spinnerKeyframes = `@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }`;\n\nfunction Spinner({ size = 14 }: { size?: number }) {\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborder: \"2px solid rgba(16, 185, 129, 0.25)\",\n\t\t\t\tborderTopColor: \"#10b981\",\n\t\t\t\tborderRadius: 999,\n\t\t\t\tanimation: \"yns-feedback-spin 0.9s linear infinite\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction PinOverlay({\n\tpin,\n\tnumber,\n\tediting,\n\tfeedbackSessionId,\n\tapiBase,\n\tonStartEdit,\n\tonCancelEdit,\n\tonSave,\n\tonRemove,\n}: {\n\tpin: FeedbackComment;\n\tnumber: number;\n\tediting: boolean;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonStartEdit: () => void;\n\tonCancelEdit: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove: () => Promise<void>;\n}) {\n\tconst target = useTargetPosition(pin.cssSelector, pin.offsetXRatio, pin.offsetYRatio);\n\tif (!target) return null;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tposition: \"absolute\",\n\t\t\t\ttop: target.top,\n\t\t\t\tleft: target.left,\n\t\t\t\tzIndex: 2147483600,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t>\n\t\t\t<button type=\"button\" onClick={onStartEdit} style={pinDotStyle} title={pin.content}>\n\t\t\t\t{number}\n\t\t\t</button>\n\t\t\t{editing && (\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial={pin.content}\n\t\t\t\t\tinitialAttachments={pin.attachments}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancelEdit}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t\tonRemove={onRemove}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction PendingCommentPopover({\n\tpending,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n}: {\n\tpending: PendingPin;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n}) {\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.rect.top + pending.rect.height * pending.offsetYRatio - 12,\n\t\t\t\t\tleft: pending.rect.left + pending.rect.width * pending.offsetXRatio - 12,\n\t\t\t\t\tzIndex: 2147483600,\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={pinDotStyle}>•</div>\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.clickY + 10,\n\t\t\t\t\tleft: pending.clickX + 10,\n\t\t\t\t\tzIndex: 2147483647,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial=\"\"\n\t\t\t\t\tinitialAttachments={[]}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancel}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n\nfunction CommentsSidebar({\n\tcomments,\n\tcurrentPath,\n\tonClose,\n\tonSelect,\n}: {\n\tcomments: FeedbackComment[];\n\tcurrentPath: string;\n\tonClose: () => void;\n\tonSelect: (comment: FeedbackComment) => void;\n}) {\n\tconst groups = new Map<string, FeedbackComment[]>();\n\tfor (const c of comments) {\n\t\tconst list = groups.get(c.pagePath) ?? [];\n\t\tlist.push(c);\n\t\tgroups.set(c.pagePath, list);\n\t}\n\tconst paths = Array.from(groups.keys()).sort((a, b) => {\n\t\tif (a === currentPath) return -1;\n\t\tif (b === currentPath) return 1;\n\t\treturn a.localeCompare(b);\n\t});\n\n\treturn (\n\t\t<div style={sidebarStyle}>\n\t\t\t<div style={sidebarHeaderStyle}>\n\t\t\t\t<strong style={{ fontSize: 14 }}>Comments ({comments.length})</strong>\n\t\t\t\t<button type=\"button\" onClick={onClose} style={ghostButtonStyle}>\n\t\t\t\t\tClose\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div style={sidebarScrollStyle}>\n\t\t\t\t{comments.length === 0 ? (\n\t\t\t\t\t<div style={sidebarEmptyStyle}>No comments yet. Click “Add comment” to leave one.</div>\n\t\t\t\t) : (\n\t\t\t\t\tpaths.map((path) => (\n\t\t\t\t\t\t<div key={path} style={{ marginBottom: 12 }}>\n\t\t\t\t\t\t\t<div style={sidebarPathStyle}>{path === currentPath ? `${path} · current` : path}</div>\n\t\t\t\t\t\t\t{(groups.get(path) ?? []).map((c, idx) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tkey={c.id}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => onSelect(c)}\n\t\t\t\t\t\t\t\t\tstyle={sidebarItemStyle}\n\t\t\t\t\t\t\t\t\tdisabled={c.status === \"done\" && false}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemIndexStyle}>{idx + 1}</span>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemTextStyle}>\n\t\t\t\t\t\t\t\t\t\t{c.content.length > 140 ? `${c.content.slice(0, 140)}…` : c.content}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{c.status === \"done\" && <span style={sidebarItemDoneStyle}>done</span>}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nconst MAX_ATTACHMENTS = 5;\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nconst readImageDimensions = (file: File): Promise<{ width: number; height: number } | null> =>\n\tnew Promise((resolve) => {\n\t\tconst url = URL.createObjectURL(file);\n\t\tconst img = new window.Image();\n\t\timg.onload = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve({ width: img.naturalWidth, height: img.naturalHeight });\n\t\t};\n\t\timg.onerror = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve(null);\n\t\t};\n\t\timg.src = url;\n\t});\n\nfunction EditPopover({\n\tinitial,\n\tinitialAttachments,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n\tonRemove,\n}: {\n\tinitial: string;\n\tinitialAttachments: FeedbackAttachment[];\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove?: () => Promise<void>;\n}) {\n\tconst [value, setValue] = useState(initial);\n\tconst [attachments, setAttachments] = useState<FeedbackAttachment[]>(initialAttachments);\n\tconst [saving, setSaving] = useState(false);\n\tconst [uploading, setUploading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst textareaRef = useRef<HTMLTextAreaElement | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement | null>(null);\n\n\tuseEffect(() => {\n\t\tconst el = textareaRef.current;\n\t\tif (!el) return;\n\t\tel.focus();\n\t\tconst len = el.value.length;\n\t\tel.setSelectionRange(len, len);\n\t}, []);\n\n\tconst handleSubmit = async (e: FormEvent) => {\n\t\te.preventDefault();\n\t\tconst content = value.trim();\n\t\tif (!content) return;\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tawait onSave(content, attachments);\n\t\t} finally {\n\t\t\tsetSaving(false);\n\t\t}\n\t};\n\n\tconst handleFiles = async (files: FileList | null) => {\n\t\tif (!files || files.length === 0 || !apiBase) return;\n\t\tsetError(null);\n\t\tconst slots = MAX_ATTACHMENTS - attachments.length;\n\t\tif (slots <= 0) {\n\t\t\tsetError(`Max ${MAX_ATTACHMENTS} images per comment`);\n\t\t\treturn;\n\t\t}\n\t\tconst picked = Array.from(files).slice(0, slots);\n\t\tfor (const f of picked) {\n\t\t\tif (!f.type.startsWith(\"image/\")) {\n\t\t\t\tsetError(`\"${f.name}\" is not an image`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (f.size > MAX_ATTACHMENT_BYTES) {\n\t\t\t\tsetError(`\"${f.name}\" exceeds 5 MB`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tsetUploading(true);\n\t\ttry {\n\t\t\tconst dims = await Promise.all(picked.map(readImageDimensions));\n\t\t\tconst fd = new FormData();\n\t\t\tpicked.forEach((file, i) => {\n\t\t\t\tfd.append(\"file\", file);\n\t\t\t\tfd.append(\"width\", dims[i]?.width ? String(dims[i]?.width) : \"\");\n\t\t\t\tfd.append(\"height\", dims[i]?.height ? String(dims[i]?.height) : \"\");\n\t\t\t});\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(feedbackSessionId)}`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\", body: fd },\n\t\t\t);\n\t\t\tif (!res.ok) {\n\t\t\t\tconst body = (await res.json().catch(() => null)) as { error?: string } | null;\n\t\t\t\tsetError(body?.error ?? \"Upload failed\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst body = (await res.json()) as { uploads: FeedbackAttachment[] };\n\t\t\tsetAttachments((prev) => [...prev, ...body.uploads]);\n\t\t} catch {\n\t\t\tsetError(\"Upload failed\");\n\t\t} finally {\n\t\t\tsetUploading(false);\n\t\t\tif (fileInputRef.current) fileInputRef.current.value = \"\";\n\t\t}\n\t};\n\n\tconst removeAttachment = (url: string) => {\n\t\tsetAttachments((prev) => prev.filter((a) => a.url !== url));\n\t};\n\n\treturn (\n\t\t<form onSubmit={handleSubmit} style={popoverStyle}>\n\t\t\t<textarea\n\t\t\t\tref={textareaRef}\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => setValue(e.target.value)}\n\t\t\t\tplaceholder=\"Leave a comment…\"\n\t\t\t\tstyle={textareaStyle}\n\t\t\t\trows={3}\n\t\t\t/>\n\t\t\t{attachments.length > 0 && (\n\t\t\t\t<div style={attachmentRowStyle}>\n\t\t\t\t\t{attachments.map((att) => (\n\t\t\t\t\t\t<div key={att.url} style={attachmentThumbWrapStyle}>\n\t\t\t\t\t\t\t<img src={att.url} alt=\"\" style={attachmentThumbStyle} />\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => removeAttachment(att.url)}\n\t\t\t\t\t\t\t\tstyle={attachmentRemoveStyle}\n\t\t\t\t\t\t\t\tdisabled={saving || uploading}\n\t\t\t\t\t\t\t\taria-label=\"Remove attachment\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{error && <div style={errorTextStyle}>{error}</div>}\n\t\t\t<input\n\t\t\t\tref={fileInputRef}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept=\"image/*\"\n\t\t\t\tmultiple\n\t\t\t\tonChange={(e) => void handleFiles(e.target.files)}\n\t\t\t\tstyle={{ display: \"none\" }}\n\t\t\t/>\n\t\t\t<div style={popoverActionsStyle}>\n\t\t\t\t{onRemove && (\n\t\t\t\t\t<button type=\"button\" onClick={() => onRemove()} style={dangerButtonStyle} disabled={saving}>\n\t\t\t\t\t\tDelete\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\tstyle={attachIconButtonStyle}\n\t\t\t\t\tdisabled={saving || uploading || attachments.length >= MAX_ATTACHMENTS}\n\t\t\t\t\taria-label={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t\ttitle={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t>\n\t\t\t\t\t{uploading ? <SpinnerGlyph /> : <PaperclipGlyph />}\n\t\t\t\t</button>\n\t\t\t\t<div style={{ flex: 1 }} />\n\t\t\t\t<button type=\"button\" onClick={onCancel} style={ghostButtonStyle} disabled={saving}>\n\t\t\t\t\tCancel\n\t\t\t\t</button>\n\t\t\t\t<button type=\"submit\" style={primaryButtonStyle} disabled={saving || uploading || !value.trim()}>\n\t\t\t\t\t{saving ? \"Saving…\" : \"Save\"}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</form>\n\t);\n}\n\nfunction useTargetPosition(selector: string, offsetXRatio: number, offsetYRatio: number) {\n\tconst [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n\n\tuseEffect(() => {\n\t\tconst update = () => {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) {\n\t\t\t\tsetPos(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tsetPos({\n\t\t\t\ttop: r.top + window.scrollY + r.height * offsetYRatio - 12,\n\t\t\t\tleft: r.left + window.scrollX + r.width * offsetXRatio - 12,\n\t\t\t});\n\t\t};\n\n\t\tupdate();\n\t\tconst ro = new ResizeObserver(update);\n\t\ttry {\n\t\t\tconst el = document.querySelector(selector);\n\t\t\tif (el) ro.observe(el);\n\t\t} catch {\n\t\t\t// swallow invalid selector\n\t\t}\n\t\twindow.addEventListener(\"scroll\", update, true);\n\t\twindow.addEventListener(\"resize\", update);\n\t\treturn () => {\n\t\t\tro.disconnect();\n\t\t\twindow.removeEventListener(\"scroll\", update, true);\n\t\t\twindow.removeEventListener(\"resize\", update);\n\t\t};\n\t}, [selector, offsetXRatio, offsetYRatio]);\n\n\treturn pos;\n}\n\n// Inline styles — toolbar is injected into arbitrary stores, so we avoid relying\n// on any CSS framework being present.\nconst toolbarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n\tpadding: \"8px 12px\",\n\tbackground: \"rgba(17, 17, 17, 0.92)\",\n\tcolor: \"white\",\n\tborderRadius: 999,\n\tboxShadow: \"0 8px 24px rgba(0,0,0,0.25)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tfontSize: 14,\n\tpointerEvents: \"auto\",\n};\n\nconst toolbarButtonStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarButtonActiveStyle: CSSProperties = {\n\t...toolbarButtonStyle,\n\tbackground: \"#ef4444\",\n\tcolor: \"white\",\n};\n\nconst toolbarButtonGhostStyle: CSSProperties = {\n\tborder: \"1px solid rgba(255,255,255,0.4)\",\n\tbackground: \"transparent\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n\tfontSize: 13,\n};\n\nconst toolbarButtonFinalizeStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarHintStyle: CSSProperties = {\n\topacity: 0.8,\n\tfontSize: 12,\n};\n\nconst pinDotStyle: CSSProperties = {\n\twidth: 24,\n\theight: 24,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tborder: \"2px solid white\",\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 700,\n\tboxShadow: \"0 2px 6px rgba(0,0,0,0.3)\",\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tpadding: 0,\n};\n\nconst popoverStyle: CSSProperties = {\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tpadding: 12,\n\twidth: 280,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tcolor: \"#111\",\n};\n\nconst textareaStyle: CSSProperties = {\n\twidth: \"100%\",\n\tborder: \"1px solid #d1d5db\",\n\tborderRadius: 6,\n\tpadding: 8,\n\tfontSize: 14,\n\tresize: \"vertical\",\n\tfontFamily: \"inherit\",\n\tcolor: \"#111\",\n\tbackground: \"white\",\n\tboxSizing: \"border-box\",\n};\n\nconst popoverActionsStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 6,\n};\n\nconst baseButton: CSSProperties = {\n\tborder: \"1px solid transparent\",\n\tborderRadius: 6,\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n};\n\nconst primaryButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"#111\",\n\tcolor: \"white\",\n};\n\nconst ghostButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n};\n\nconst dangerButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#b91c1c\",\n\tborderColor: \"#fecaca\",\n};\n\nconst attachmentRowStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tflexWrap: \"wrap\",\n\tgap: 6,\n};\n\nconst attachmentThumbWrapStyle: CSSProperties = {\n\tposition: \"relative\",\n\twidth: 56,\n\theight: 56,\n\tborderRadius: 6,\n\toverflow: \"hidden\",\n\tborder: \"1px solid #e5e7eb\",\n\tbackground: \"#f9fafb\",\n};\n\nconst attachmentThumbStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: \"100%\",\n\tobjectFit: \"cover\",\n\tdisplay: \"block\",\n};\n\nconst attachmentRemoveStyle: CSSProperties = {\n\tposition: \"absolute\",\n\ttop: 2,\n\tright: 2,\n\twidth: 18,\n\theight: 18,\n\tborderRadius: 999,\n\tborder: \"none\",\n\tbackground: \"rgba(17, 17, 17, 0.85)\",\n\tcolor: \"white\",\n\tfontSize: 13,\n\tlineHeight: \"16px\",\n\tcursor: \"pointer\",\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst errorTextStyle: CSSProperties = {\n\tcolor: \"#b91c1c\",\n\tfontSize: 12,\n\tmargin: 0,\n};\n\nconst attachIconButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n\twidth: 30,\n\theight: 30,\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tflexShrink: 0,\n};\n\nfunction PaperclipGlyph() {\n\treturn (\n\t\t<svg\n\t\t\trole=\"img\"\n\t\t\taria-label=\"Attach image\"\n\t\t\twidth=\"14\"\n\t\t\theight=\"14\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t>\n\t\t\t<title>Attach image</title>\n\t\t\t<path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.83l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\" />\n\t\t</svg>\n\t);\n}\n\nfunction SpinnerGlyph() {\n\treturn (\n\t\t<>\n\t\t\t<style>{`@keyframes yns-attach-spin { to { transform: rotate(360deg); } }`}</style>\n\t\t\t<span\n\t\t\t\taria-hidden\n\t\t\t\tstyle={{\n\t\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\t\twidth: 12,\n\t\t\t\t\theight: 12,\n\t\t\t\t\tborder: \"2px solid rgba(55, 65, 81, 0.25)\",\n\t\t\t\t\tborderTopColor: \"#374151\",\n\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\tanimation: \"yns-attach-spin 0.9s linear infinite\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t</>\n\t);\n}\n\nconst sidebarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\ttop: 0,\n\tright: 0,\n\tbottom: 0,\n\twidth: 360,\n\tmaxWidth: \"92vw\",\n\tbackground: \"white\",\n\tborderLeft: \"1px solid #e5e7eb\",\n\tboxShadow: \"-12px 0 32px rgba(0,0,0,0.12)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n};\n\nconst sidebarHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"space-between\",\n\tpadding: \"12px 16px\",\n\tborderBottom: \"1px solid #e5e7eb\",\n};\n\nconst sidebarScrollStyle: CSSProperties = {\n\tflex: 1,\n\toverflow: \"auto\",\n\tpadding: \"12px 12px 24px\",\n};\n\nconst sidebarEmptyStyle: CSSProperties = {\n\tcolor: \"#6b7280\",\n\tfontSize: 13,\n\tpadding: \"24px 8px\",\n\ttextAlign: \"center\",\n};\n\nconst sidebarPathStyle: CSSProperties = {\n\tfontSize: 11,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n\tcolor: \"#6b7280\",\n\tpadding: \"4px 4px 6px\",\n\twordBreak: \"break-all\",\n};\n\nconst sidebarItemStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"flex-start\",\n\tgap: 8,\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 6,\n\tpadding: \"8px 10px\",\n\tcursor: \"pointer\",\n\tfontSize: 13,\n\tmarginBottom: 6,\n\tcolor: \"#111\",\n};\n\nconst sidebarItemIndexStyle: CSSProperties = {\n\tflexShrink: 0,\n\twidth: 22,\n\theight: 22,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tfontWeight: 700,\n\tfontSize: 11,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst sidebarItemTextStyle: CSSProperties = {\n\tflex: 1,\n\twhiteSpace: \"pre-wrap\",\n\twordBreak: \"break-word\",\n};\n\nconst sidebarItemDoneStyle: CSSProperties = {\n\tflexShrink: 0,\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 600,\n\tpadding: \"2px 6px\",\n\tborderRadius: 4,\n\talignSelf: \"center\",\n};\n\nconst submittedPanelStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tpadding: \"12px 16px\",\n\twidth: \"min(420px, calc(100vw - 32px))\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 12,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n\tpointerEvents: \"auto\",\n};\n\nconst submittedHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n};\n\nconst submittedBadgeStyle: CSSProperties = {\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 700,\n\tpadding: \"2px 8px\",\n\tborderRadius: 999,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n};\n\nconst submittedProgressLabelStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tjustifyContent: \"space-between\",\n\tfontSize: 12,\n};\n\nconst submittedProgressTrackStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: 6,\n\tbackground: \"#f3f4f6\",\n\tborderRadius: 999,\n\toverflow: \"hidden\",\n};\n\nconst submittedProgressFillStyle: CSSProperties = {\n\theight: \"100%\",\n\tbackground: \"#10b981\",\n\tborderRadius: 999,\n\ttransition: \"width 500ms\",\n};\n\nconst submittedEtaStyle: CSSProperties = {\n\tmargin: 0,\n\tfontSize: 12,\n\tcolor: \"#6b7280\",\n};\n\nexport function mountFeedbackToolbar(): { unmount: () => void } | null {\n\tconsole.log(\"[YNS Feedback Toolbar] mountFeedbackToolbar() called\", {\n\t\tisWindow: typeof window !== \"undefined\",\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\talreadyMounted: typeof document !== \"undefined\" && Boolean(document.getElementById(MOUNT_NODE_ID)),\n\t});\n\tif (typeof window === \"undefined\") return null;\n\tif (process.env.NEXT_PUBLIC_VERCEL_ENV !== \"preview\") {\n\t\tconsole.log(\n\t\t\t\"[YNS Feedback Toolbar] gate failed — NEXT_PUBLIC_VERCEL_ENV is\",\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\t);\n\t\treturn null;\n\t}\n\tif (document.getElementById(MOUNT_NODE_ID)) return null;\n\n\tconst container = document.createElement(\"div\");\n\tcontainer.id = MOUNT_NODE_ID;\n\tcontainer.dataset.ynsFeedbackUi = \"true\";\n\tdocument.body.appendChild(container);\n\n\tconst root = createRoot(container);\n\troot.render(<FeedbackToolbar />);\n\n\treturn {\n\t\tunmount: () => {\n\t\t\troot.unmount();\n\t\t\tcontainer.remove();\n\t\t},\n\t};\n}\n\n// Auto-mount on import. Wait for DOM ready.\nif (typeof window !== \"undefined\") {\n\tconsole.log(\"[YNS Feedback Toolbar] module evaluated\", {\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\treadyState: document.readyState,\n\t\twillAutoMount: process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\",\n\t});\n}\nif (typeof window !== \"undefined\" && process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\") {\n\tif (document.readyState === \"loading\") {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\t\tmountFeedbackToolbar();\n\t\t});\n\t} else {\n\t\tmountFeedbackToolbar();\n\t}\n}\n","/**\n * Sandbox Inspectors — side-effect entry.\n *\n * Auto-mounts the design-mode + comment-mode inspectors inside per-store\n * storefronts running under the AI Builder sandbox dev server. Communicates\n * with the parent /design iframe via postMessage.\n *\n * Always-on: inspectors are passive postMessage listeners and don't affect the\n * page until the AI Builder iframe sends a `*-mode-toggle` message. Gating on\n * NODE_ENV is wrong (always \"production\" on Vercel) and any build-time gate\n * gets folded out by the commerce-kit bundle, leaving an empty file.\n */\n\n/**\n * Exported so `browser.tsx` can `export { startSandboxInspectors }` — the\n * named binding anchors this module against esbuild tree-shaking when\n * commerce-kit is bundled. Without it, an `export * from \"./sandbox-inspectors\"`\n * re-exports an empty binding set and esbuild DCEs the whole file.\n */\nexport function startSandboxInspectors(): void {\n\tif (typeof window === \"undefined\") return;\n\tconsole.log(\"[YNS Sandbox Inspectors] module evaluated\");\n\tinitDesignModeInspector();\n\tinitCommentModeInspector();\n}\n\n// Auto-start on import.\nstartSandboxInspectors();\n\ninterface ElementInfo {\n\ttag: string;\n\tid: string | undefined;\n\tclasses: string[];\n\ttextContent: string;\n\tcssSelector: string;\n\tboundingRect: { top: number; left: number; width: number; height: number };\n\tcomputedStyles: Partial<{\n\t\tcolor: string;\n\t\tbackgroundColor: string;\n\t\tfontSize: string;\n\t\tfontFamily: string;\n\t\tpadding: string;\n\t\tmargin: string;\n\t}>;\n}\n\nconst IGNORED_TAGS = new Set([\"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\", \"HTML\"]);\n\nfunction generateCssSelector(el: Element): string {\n\tif (el.id) return `${el.tagName.toLowerCase()}#${CSS.escape(el.id)}`;\n\n\tconst parts: string[] = [];\n\tlet current: Element | null = el;\n\twhile (current && current !== document.body && current !== document.documentElement) {\n\t\tlet selector = current.tagName.toLowerCase();\n\n\t\tif (current.id) {\n\t\t\tparts.unshift(`${selector}#${CSS.escape(current.id)}`);\n\t\t\tbreak;\n\t\t}\n\n\t\tconst classes = Array.from(current.classList).filter((c) => !c.startsWith(\"data-yns-\"));\n\t\tif (classes.length > 0) {\n\t\t\tselector += `.${classes.map((c) => CSS.escape(c)).join(\".\")}`;\n\t\t}\n\n\t\tconst parent: Element | null = current.parentElement;\n\t\tconst tag = current.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings = Array.from(parent.children).filter(\n\t\t\t\t(s) => s.tagName === tag && (classes.length === 0 || classes.every((c) => s.classList.contains(c))),\n\t\t\t);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst index = Array.from(parent.children).indexOf(current) + 1;\n\t\t\t\tselector += `:nth-child(${index})`;\n\t\t\t}\n\t\t}\n\n\t\tparts.unshift(selector);\n\t\tcurrent = current.parentElement;\n\t}\n\treturn parts.join(\" > \");\n}\n\nfunction buildElementInfo(el: Element, textLimit: number): ElementInfo {\n\tconst rect = el.getBoundingClientRect();\n\tconst computed = window.getComputedStyle(el);\n\tconst text = (el.textContent ?? \"\").trim();\n\treturn {\n\t\ttag: el.tagName.toLowerCase(),\n\t\tid: el.id || undefined,\n\t\tclasses: Array.from(el.classList).filter((c) => !c.startsWith(\"data-yns-\")),\n\t\ttextContent: text.length > textLimit ? `${text.slice(0, textLimit)}…` : text,\n\t\tcssSelector: generateCssSelector(el),\n\t\tboundingRect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },\n\t\tcomputedStyles: {\n\t\t\tcolor: computed.color,\n\t\t\tbackgroundColor: computed.backgroundColor,\n\t\t\tfontSize: computed.fontSize,\n\t\t\tfontFamily: computed.fontFamily,\n\t\t\tpadding: computed.padding,\n\t\t\tmargin: computed.margin,\n\t\t},\n\t};\n}\n\nfunction shouldIgnore(el: Element | null, overlayAttrs: string[]): el is null {\n\tif (!el || !el.tagName) return true;\n\tif (IGNORED_TAGS.has(el.tagName)) return true;\n\tfor (const attr of overlayAttrs) {\n\t\tif (el.hasAttribute?.(attr)) return true;\n\t}\n\treturn false;\n}\n\nfunction initDesignModeInspector() {\n\tlet enabled = false;\n\tlet hoveredElement: Element | null = null;\n\tlet selectedElement: { el: Element; cssSelector: string } | null = null;\n\n\tconst overlay = document.createElement(\"div\");\n\toverlay.setAttribute(\"data-yns-design-overlay\", \"hover\");\n\toverlay.style.cssText = [\n\t\t\"position: fixed\",\n\t\t\"pointer-events: none\",\n\t\t\"z-index: 2147483646\",\n\t\t\"border: 2px solid #3b82f6\",\n\t\t\"background: rgba(59, 130, 246, 0.08)\",\n\t\t\"border-radius: 3px\",\n\t\t\"display: none\",\n\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t].join(\";\");\n\n\tconst label = document.createElement(\"div\");\n\tlabel.setAttribute(\"data-yns-design-overlay\", \"label\");\n\tlabel.style.cssText = [\n\t\t\"position: fixed\",\n\t\t\"pointer-events: none\",\n\t\t\"z-index: 2147483647\",\n\t\t\"background: #3b82f6\",\n\t\t\"color: #fff\",\n\t\t\"font-size: 11px\",\n\t\t\"font-family: ui-monospace, monospace\",\n\t\t\"padding: 2px 6px\",\n\t\t\"border-radius: 3px\",\n\t\t\"white-space: nowrap\",\n\t\t\"display: none\",\n\t].join(\";\");\n\n\tdocument.documentElement.appendChild(overlay);\n\tdocument.documentElement.appendChild(label);\n\n\tconst style = document.createElement(\"style\");\n\tstyle.setAttribute(\"data-yns-design-overlay\", \"style\");\n\tstyle.textContent = \"[data-yns-selected] { outline: 2px solid #3b82f6 !important; outline-offset: 1px; }\";\n\tdocument.head.appendChild(style);\n\n\tfunction positionOverlay(el: Element) {\n\t\tconst rect = el.getBoundingClientRect();\n\t\toverlay.style.top = `${rect.top}px`;\n\t\toverlay.style.left = `${rect.left}px`;\n\t\toverlay.style.width = `${rect.width}px`;\n\t\toverlay.style.height = `${rect.height}px`;\n\t\toverlay.style.display = \"block\";\n\n\t\tconst classes = Array.from(el.classList).filter((c) => !c.startsWith(\"data-yns-\"));\n\t\tconst labelText = el.tagName.toLowerCase() + (classes.length ? `.${classes[0]}` : \"\");\n\t\tlabel.textContent = labelText;\n\t\tlabel.style.left = `${rect.left}px`;\n\t\tlabel.style.top = `${Math.max(0, rect.top - 22)}px`;\n\t\tlabel.style.display = \"block\";\n\t}\n\n\tconst handleMouseMove = (e: MouseEvent) => {\n\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\tif (shouldIgnore(el, [\"data-yns-design-overlay\"])) {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thoveredElement = null;\n\t\t\treturn;\n\t\t}\n\t\thoveredElement = el;\n\t\trequestAnimationFrame(() => {\n\t\t\tif (hoveredElement === el && enabled && el) {\n\t\t\t\tpositionOverlay(el);\n\t\t\t}\n\t\t});\n\t};\n\n\tconst handleMouseLeave = () => {\n\t\toverlay.style.display = \"none\";\n\t\tlabel.style.display = \"none\";\n\t\thoveredElement = null;\n\t};\n\n\tconst handleClick = (e: MouseEvent) => {\n\t\tif (!enabled || !hoveredElement) return;\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t\te.stopImmediatePropagation();\n\n\t\tconst el = hoveredElement;\n\t\tif (shouldIgnore(el, [\"data-yns-design-overlay\"])) return;\n\n\t\tconst info = buildElementInfo(el, 120);\n\t\tconst selector = info.cssSelector;\n\n\t\tif (selectedElement) {\n\t\t\tselectedElement.el.removeAttribute(\"data-yns-selected\");\n\t\t}\n\n\t\tif (selectedElement && selectedElement.cssSelector === selector) {\n\t\t\tselectedElement = null;\n\t\t\twindow.parent.postMessage({ type: \"element-deselected\", data: info }, \"*\");\n\t\t} else {\n\t\t\tselectedElement = { el, cssSelector: selector };\n\t\t\tel.setAttribute(\"data-yns-selected\", \"\");\n\t\t\twindow.parent.postMessage({ type: \"element-selected\", data: info }, \"*\");\n\t\t}\n\t};\n\n\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\tif (e.key === \"Escape\" && enabled) {\n\t\t\tdisableDesignMode();\n\t\t\twindow.parent.postMessage({ type: \"design-mode-cleared\" }, \"*\");\n\t\t}\n\t};\n\n\tfunction enableDesignMode() {\n\t\tenabled = true;\n\t\tdocument.addEventListener(\"mousemove\", handleMouseMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleMouseLeave);\n\t\tdocument.addEventListener(\"click\", handleClick, true);\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown, true);\n\t}\n\n\tfunction disableDesignMode() {\n\t\tenabled = false;\n\t\tdocument.removeEventListener(\"mousemove\", handleMouseMove, true);\n\t\tdocument.removeEventListener(\"mouseleave\", handleMouseLeave);\n\t\tdocument.removeEventListener(\"click\", handleClick, true);\n\t\tdocument.removeEventListener(\"keydown\", handleKeyDown, true);\n\n\t\toverlay.style.display = \"none\";\n\t\tlabel.style.display = \"none\";\n\t\thoveredElement = null;\n\n\t\tif (selectedElement) {\n\t\t\tselectedElement.el.removeAttribute(\"data-yns-selected\");\n\t\t\tselectedElement = null;\n\t\t}\n\t}\n\n\twindow.addEventListener(\"message\", (event) => {\n\t\tconst msg = event.data;\n\t\tif (!msg || typeof msg !== \"object\") return;\n\n\t\tif (msg.type === \"design-mode-toggle\") {\n\t\t\tif (msg.enabled) {\n\t\t\t\tenableDesignMode();\n\t\t\t} else {\n\t\t\t\tdisableDesignMode();\n\t\t\t\twindow.parent.postMessage({ type: \"design-mode-cleared\" }, \"*\");\n\t\t\t}\n\t\t}\n\n\t\tif (msg.type === \"design-mode-deselect\") {\n\t\t\tif (selectedElement) {\n\t\t\t\tselectedElement.el.removeAttribute(\"data-yns-selected\");\n\t\t\t\tselectedElement = null;\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction initCommentModeInspector() {\n\tlet enabled = false;\n\tlet hoveredElement: Element | null = null;\n\tlet pins: Array<{ id: string; selector: string; number: number }> = [];\n\n\tconst overlay = document.createElement(\"div\");\n\toverlay.setAttribute(\"data-yns-comment-overlay\", \"hover\");\n\toverlay.style.cssText = [\n\t\t\"position: fixed\",\n\t\t\"pointer-events: none\",\n\t\t\"z-index: 2147483644\",\n\t\t\"border: 2px dashed #10b981\",\n\t\t\"background: rgba(16, 185, 129, 0.06)\",\n\t\t\"border-radius: 3px\",\n\t\t\"display: none\",\n\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t].join(\";\");\n\n\tconst cursorLabel = document.createElement(\"div\");\n\tcursorLabel.setAttribute(\"data-yns-comment-overlay\", \"cursor-label\");\n\tcursorLabel.textContent = \"Click to comment\";\n\tcursorLabel.style.cssText = [\n\t\t\"position: fixed\",\n\t\t\"pointer-events: none\",\n\t\t\"z-index: 2147483645\",\n\t\t\"background: #059669\",\n\t\t\"color: #fff\",\n\t\t\"font-size: 11px\",\n\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\"padding: 3px 8px\",\n\t\t\"border-radius: 4px\",\n\t\t\"white-space: nowrap\",\n\t\t\"display: none\",\n\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t].join(\";\");\n\n\tconst pinContainer = document.createElement(\"div\");\n\tpinContainer.setAttribute(\"data-yns-comment-overlay\", \"pins\");\n\tpinContainer.style.cssText =\n\t\t\"position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 2147483643;\";\n\n\tdocument.documentElement.appendChild(overlay);\n\tdocument.documentElement.appendChild(cursorLabel);\n\tdocument.documentElement.appendChild(pinContainer);\n\n\tfunction renderPins() {\n\t\tpinContainer.innerHTML = \"\";\n\t\tfor (const pin of pins) {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(pin.selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) continue;\n\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tconst pinEl = document.createElement(\"div\");\n\t\t\tpinEl.style.cssText = [\n\t\t\t\t\"position: fixed\",\n\t\t\t\t`top: ${r.top - 12}px`,\n\t\t\t\t`left: ${r.left + r.width / 2 - 12}px`,\n\t\t\t\t\"width: 24px\",\n\t\t\t\t\"height: 24px\",\n\t\t\t\t\"border-radius: 50%\",\n\t\t\t\t\"background: #059669\",\n\t\t\t\t\"color: #fff\",\n\t\t\t\t\"display: flex\",\n\t\t\t\t\"align-items: center\",\n\t\t\t\t\"justify-content: center\",\n\t\t\t\t\"font-size: 12px\",\n\t\t\t\t\"font-weight: 600\",\n\t\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\t\"box-shadow: 0 2px 8px rgba(0,0,0,0.2)\",\n\t\t\t\t\"pointer-events: none\",\n\t\t\t].join(\";\");\n\t\t\tpinEl.textContent = String(pin.number);\n\t\t\tpinContainer.appendChild(pinEl);\n\t\t}\n\t}\n\n\tconst handleMouseMove = (e: MouseEvent) => {\n\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\tif (shouldIgnore(el, [\"data-yns-comment-overlay\", \"data-yns-design-overlay\"])) {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tcursorLabel.style.display = \"none\";\n\t\t\thoveredElement = null;\n\t\t\treturn;\n\t\t}\n\t\thoveredElement = el;\n\t\trequestAnimationFrame(() => {\n\t\t\tif (hoveredElement === el && enabled && el) {\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\n\t\t\t\tcursorLabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tcursorLabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tcursorLabel.style.display = \"block\";\n\t\t\t}\n\t\t});\n\t};\n\n\tconst handleMouseLeave = () => {\n\t\toverlay.style.display = \"none\";\n\t\tcursorLabel.style.display = \"none\";\n\t\thoveredElement = null;\n\t};\n\n\tconst handleClick = (e: MouseEvent) => {\n\t\tif (!enabled || !hoveredElement) return;\n\t\te.preventDefault();\n\t\te.stopPropagation();\n\t\te.stopImmediatePropagation();\n\n\t\tconst el = hoveredElement;\n\t\tif (shouldIgnore(el, [\"data-yns-comment-overlay\", \"data-yns-design-overlay\"])) return;\n\n\t\tconst info = buildElementInfo(el, 200);\n\t\tconst rect = el.getBoundingClientRect();\n\n\t\twindow.parent.postMessage(\n\t\t\t{\n\t\t\t\ttype: \"comment-click\",\n\t\t\t\tdata: {\n\t\t\t\t\telement: info,\n\t\t\t\t\tclickPosition: { x: e.clientX, y: e.clientY },\n\t\t\t\t\telementRect: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },\n\t\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"*\",\n\t\t);\n\t};\n\n\tconst handleKeyDown = (e: KeyboardEvent) => {\n\t\tif (e.key === \"Escape\" && enabled) {\n\t\t\tdisableCommentMode();\n\t\t\twindow.parent.postMessage({ type: \"comment-mode-cleared\" }, \"*\");\n\t\t}\n\t};\n\n\tfunction enableCommentMode() {\n\t\tenabled = true;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\tdocument.addEventListener(\"mousemove\", handleMouseMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleMouseLeave);\n\t\tdocument.addEventListener(\"click\", handleClick, true);\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown, true);\n\t}\n\n\tfunction disableCommentMode() {\n\t\tenabled = false;\n\t\tdocument.body.style.cursor = \"\";\n\t\tdocument.removeEventListener(\"mousemove\", handleMouseMove, true);\n\t\tdocument.removeEventListener(\"mouseleave\", handleMouseLeave);\n\t\tdocument.removeEventListener(\"click\", handleClick, true);\n\t\tdocument.removeEventListener(\"keydown\", handleKeyDown, true);\n\n\t\toverlay.style.display = \"none\";\n\t\tcursorLabel.style.display = \"none\";\n\t\thoveredElement = null;\n\t}\n\n\tlet rafPending = false;\n\tconst scheduleRender = () => {\n\t\tif (rafPending) return;\n\t\trafPending = true;\n\t\trequestAnimationFrame(() => {\n\t\t\trafPending = false;\n\t\t\trenderPins();\n\t\t});\n\t};\n\twindow.addEventListener(\"scroll\", scheduleRender, { passive: true });\n\twindow.addEventListener(\"resize\", scheduleRender, { passive: true });\n\n\twindow.addEventListener(\"message\", (event) => {\n\t\tconst msg = event.data;\n\t\tif (!msg || typeof msg !== \"object\") return;\n\n\t\tif (msg.type === \"comment-mode-toggle\") {\n\t\t\tif (msg.enabled) enableCommentMode();\n\t\t\telse disableCommentMode();\n\t\t}\n\n\t\tif (msg.type === \"comment-pins-update\") {\n\t\t\tpins = msg.pins ?? [];\n\t\t\trenderPins();\n\t\t}\n\n\t\tif (msg.type === \"comment-pin-remove\") {\n\t\t\tpins = pins.filter((p) => p.id !== msg.pinId);\n\t\t\trenderPins();\n\t\t}\n\t});\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","createRoot","Fragment","jsx","jsxs","MOUNT_NODE_ID","buildApiBase","override","host","protocol","computeCssSelector","el","path","node","part","className","classes","c","parent","tag","siblings","idx","ATTRS_OF_INTEREST","formatAttrs","parts","attr","v","trimmed","formatTextContent","text","buildSurroundingHtml","target","ancestors","cur","lines","depth","ancestor","indent","targetIndent","targetTag","targetText","childIndent","children","child","childText","i","isInsideToolbar","IGNORED_TAGS","POLL_INTERVAL_MS","formatEta","etaIso","eta","remainingMs","dateLabel","remainingMin","remainingHours","remainingDays","FeedbackToolbar","session","setSession","loading","setLoading","pinMode","setPinMode","pending","setPending","editingId","setEditingId","sidebarOpen","setSidebarOpen","finalizing","setFinalizing","apiBase","cancelled","controller","fetchOnce","res","data","interval","overlay","label","hovered","handleMove","e","rect","handleLeave","handleClick","event","offsetXRatio","offsetYRatio","refreshComments","submitNewComment","content","attachments","updateComment","id","removeComment","finalizeSession","nextStatus","prev","scrollToComment","comment","SubmittedPanel","visiblePins","pin","PinOverlay","PendingCommentPopover","CommentsSidebar","toolbarStyle","toolbarButtonActiveStyle","toolbarButtonStyle","toolbarButtonGhostStyle","toolbarButtonFinalizeStyle","toolbarHintStyle","progress","status","setTick","t","submittedPanelStyle","spinnerKeyframes","submittedHeaderStyle","Spinner","submittedBadgeStyle","submittedProgressLabelStyle","submittedProgressTrackStyle","submittedProgressFillStyle","submittedEtaStyle","size","number","editing","feedbackSessionId","onStartEdit","onCancelEdit","onSave","onRemove","useTargetPosition","pinDotStyle","EditPopover","onCancel","comments","currentPath","onClose","onSelect","groups","list","paths","a","b","sidebarStyle","sidebarHeaderStyle","ghostButtonStyle","sidebarScrollStyle","sidebarEmptyStyle","sidebarPathStyle","sidebarItemStyle","sidebarItemIndexStyle","sidebarItemTextStyle","sidebarItemDoneStyle","MAX_ATTACHMENTS","MAX_ATTACHMENT_BYTES","readImageDimensions","file","resolve","url","img","initial","initialAttachments","value","setValue","setAttachments","saving","setSaving","uploading","setUploading","error","setError","textareaRef","fileInputRef","len","handleSubmit","handleFiles","files","slots","picked","dims","fd","body","removeAttachment","popoverStyle","textareaStyle","attachmentRowStyle","att","attachmentThumbWrapStyle","attachmentThumbStyle","attachmentRemoveStyle","errorTextStyle","popoverActionsStyle","dangerButtonStyle","attachIconButtonStyle","SpinnerGlyph","PaperclipGlyph","primaryButtonStyle","selector","pos","setPos","update","r","ro","baseButton","mountFeedbackToolbar","container","root","startSandboxInspectors","initDesignModeInspector","initCommentModeInspector","IGNORED_TAGS","generateCssSelector","el","parts","current","selector","classes","c","parent","tag","s","index","buildElementInfo","textLimit","rect","computed","text","shouldIgnore","overlayAttrs","attr","enabled","hoveredElement","selectedElement","overlay","label","style","positionOverlay","labelText","handleMouseMove","e","handleMouseLeave","handleClick","info","handleKeyDown","disableDesignMode","enableDesignMode","event","msg","pins","cursorLabel","pinContainer","renderPins","pin","r","pinEl","disableCommentMode","enableCommentMode","rafPending","scheduleRender","p"]}
|
package/dist/feedback-toolbar.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{useEffect as
|
|
2
|
-
`)},H=e=>{let n=e;for(;n;){if(n instanceof HTMLElement&&n.dataset.ynsFeedbackUi==="true")return!0;n=n.parentElement}return!1},Q=new Set(["HTML","HEAD","SCRIPT","STYLE","NOSCRIPT"]),Z=1e4,ee=e=>{let n=new Date(e),r=n.getTime()-Date.now(),s=n.toLocaleString(void 0,{weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"});if(r<=0)return`Any moment now (estimated by ${s})`;let c=Math.ceil(r/6e4);if(c<60)return`~${c} minutes (by ${s})`;let p=Math.round(r/36e5);if(p<24)return`~${p} ${p===1?"hour":"hours"} (by ${s})`;let l=Math.round(r/864e5);return`~${l} ${l===1?"day":"days"} (by ${s})`};function te(){let[e,n]=h(null),[r,s]=h(!0),[c,p]=h(!1),[l,a]=h(null),[d,f]=h(null),[y,S]=h(!1),[$,P]=h(!1),b=M(null);E(()=>{if(b.current=q(),!b.current){s(!1);return}let t=!1,o=new AbortController,m=async()=>{try{let v=await fetch(`${b.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include",signal:o.signal});if(t)return;if(!v.ok){n(null);return}let w=await v.json();if(t)return;n(w),w.sessionStatus==="done"&&window.location.reload()}catch{t||n(null)}finally{t||s(!1)}};m();let u=window.setInterval(m,Z);return()=>{t=!0,o.abort(),window.clearInterval(u)}},[]),E(()=>{!e||e.canComment||(p(!1),a(null),S(!1),f(null))},[e]),E(()=>{if(c)return document.body.style.cursor="crosshair",()=>{document.body.style.cursor=""}},[c]),E(()=>{if(!c)return;let t=document.createElement("div");t.dataset.ynsFeedbackUi="true",t.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483644","border: 2px dashed #10b981","background: rgba(16, 185, 129, 0.08)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let o=document.createElement("div");o.dataset.ynsFeedbackUi="true",o.textContent="Click to comment",o.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483645","background: #059669","color: #fff","font-size: 11px","font-family: ui-sans-serif, system-ui, sans-serif","padding: 3px 8px","border-radius: 4px","white-space: nowrap","display: none","box-shadow: 0 2px 6px rgba(0,0,0,0.15)"].join(";"),document.documentElement.appendChild(t),document.documentElement.appendChild(o);let m=null,u=w=>{let k=document.elementFromPoint(w.clientX,w.clientY);if(!k||Q.has(k.tagName)||H(k)){t.style.display="none",o.style.display="none",m=null;return}m=k,requestAnimationFrame(()=>{if(m!==k)return;let C=k.getBoundingClientRect();t.style.top=`${C.top}px`,t.style.left=`${C.left}px`,t.style.width=`${C.width}px`,t.style.height=`${C.height}px`,t.style.display="block",o.style.left=`${w.clientX+14}px`,o.style.top=`${w.clientY+14}px`,o.style.display="block"})},v=()=>{t.style.display="none",o.style.display="none",m=null};return document.addEventListener("mousemove",u,!0),document.addEventListener("mouseleave",v),()=>{document.removeEventListener("mousemove",u,!0),document.removeEventListener("mouseleave",v),t.remove(),o.remove()}},[c]),E(()=>{if(!c)return;let t=o=>{let m=o.target;if(!(m instanceof Element)||H(m))return;o.preventDefault(),o.stopPropagation();let u=m.getBoundingClientRect(),v=u.width>0?(o.clientX-u.left)/u.width:.5,w=u.height>0?(o.clientY-u.top)/u.height:.5;a({cssSelector:G(m),pagePath:window.location.pathname,surroundingHtml:K(m),rect:{top:u.top+window.scrollY,left:u.left+window.scrollX,width:u.width,height:u.height},clickX:o.clientX+window.scrollX,clickY:o.clientY+window.scrollY,offsetXRatio:Math.min(1,Math.max(0,v)),offsetYRatio:Math.min(1,Math.max(0,w))}),p(!1)};return document.addEventListener("click",t,{capture:!0}),()=>document.removeEventListener("click",t,{capture:!0})},[c]);let R=async()=>{if(!b.current)return;let t=await fetch(`${b.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include"});if(!t.ok)return;let o=await t.json();n(o)},x=async(t,o)=>{!b.current||!e||!l||!(await fetch(`${b.current}/api/feedback-comments`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({feedbackSessionId:e.feedbackSessionId,content:t,pagePath:l.pagePath,cssSelector:l.cssSelector,surroundingHtml:l.surroundingHtml,offsetXRatio:l.offsetXRatio,offsetYRatio:l.offsetYRatio,...o.length>0?{attachments:o}:{}})})).ok||(a(null),p(!0),await R())},T=async(t,o,m)=>{!b.current||!(await fetch(`${b.current}/api/feedback-comments/${t}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:o,attachments:m})})).ok||(f(null),await R())},I=async t=>{!b.current||!(await fetch(`${b.current}/api/feedback-comments/${t}`,{method:"DELETE",credentials:"include"})).ok||await R()},F=async()=>{if(!(!b.current||!e)&&window.confirm("Finalize this feedback session? You won't be able to add more comments.")){P(!0);try{let t=await fetch(`${b.current}/api/feedback-sessions/${e.feedbackSessionId}/finalize`,{method:"POST",credentials:"include"});if(!t.ok)return;let m=(await t.json().catch(()=>null))?.status??"processing";n(u=>u&&{...u,canComment:!1,sessionStatus:m})}finally{P(!1)}}},_=t=>{if(t.pagePath!==window.location.pathname){window.location.assign(t.pagePath);return}let o=null;try{o=document.querySelector(t.cssSelector)}catch{o=null}o&&(o.scrollIntoView({behavior:"smooth",block:"center"}),f(t.id))};if(r||!e)return null;if(!e.canComment)return e.sessionStatus!=="processing"&&e.sessionStatus!=="in_review"?null:i("div",{"data-yns-feedback-ui":"true",children:i(ne,{progress:e.progress,eta:e.eta,status:e.sessionStatus})});let A=e.comments.filter(t=>t.pagePath===window.location.pathname&&t.status!=="done");return g("div",{"data-yns-feedback-ui":"true",children:[A.map((t,o)=>i(re,{pin:t,number:o+1,editing:d===t.id,feedbackSessionId:e.feedbackSessionId,apiBase:b.current,onStartEdit:()=>f(t.id),onCancelEdit:()=>f(null),onSave:(m,u)=>T(t.id,m,u),onRemove:()=>I(t.id)},t.id)),l&&i(se,{pending:l,feedbackSessionId:e.feedbackSessionId,apiBase:b.current,onCancel:()=>{a(null),p(!0)},onSave:(t,o)=>x(t,o)}),y&&i(ae,{comments:e.comments,currentPath:window.location.pathname,onClose:()=>S(!1),onSelect:_}),g("div",{style:ue,children:[i("button",{type:"button",onClick:()=>p(t=>!t),style:c?pe:D,children:c?"Cancel":"Add comment"}),i("button",{type:"button",onClick:()=>S(t=>!t),style:me,children:y?"Hide list":`List (${e.comments.length})`}),i("button",{type:"button",onClick:F,disabled:$,style:fe,children:$?"Finalizing\u2026":"Finalize"}),i("span",{style:be,children:c?"Click any element to comment":`${A.length} on this page`})]})]})}function ne({progress:e,eta:n,status:r}){let[,s]=h(0);E(()=>{let a=window.setInterval(()=>s(d=>d+1),6e4);return()=>window.clearInterval(a)},[]);let c=ee(n);return g("div",{style:Be,children:[i("style",{children:oe}),g("div",{style:Ne,children:[i(ie,{}),i("span",{style:ze,children:"Submitted"}),i("strong",{style:{fontSize:13},children:r==="in_review"?"Feedback under review":"Applying feedback"})]}),g("div",{style:Me,children:[i("span",{children:"Review progress"}),i("span",{style:{opacity:.7},children:e.label})]}),i("div",{style:Ue,children:i("div",{style:{...je,width:`${e.fillPct}%`}})}),g("p",{style:Xe,children:["Estimated delivery: ",i("span",{style:{fontWeight:600},children:c})]})]})}var oe="@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }";function ie({size:e=14}){return i("span",{"aria-hidden":!0,style:{display:"inline-block",width:e,height:e,border:"2px solid rgba(16, 185, 129, 0.25)",borderTopColor:"#10b981",borderRadius:999,animation:"yns-feedback-spin 0.9s linear infinite"}})}function re({pin:e,number:n,editing:r,feedbackSessionId:s,apiBase:c,onStartEdit:p,onCancelEdit:l,onSave:a,onRemove:d}){let f=de(e.cssSelector,e.offsetXRatio,e.offsetYRatio);return f?g("div",{style:{position:"absolute",top:f.top,left:f.left,zIndex:2147483600,pointerEvents:"auto"},children:[i("button",{type:"button",onClick:p,style:V,title:e.content,children:n}),r&&i(O,{initial:e.content,initialAttachments:e.attachments,feedbackSessionId:s,apiBase:c,onCancel:l,onSave:a,onRemove:d})]}):null}function se({pending:e,feedbackSessionId:n,apiBase:r,onCancel:s,onSave:c}){return g(He,{children:[i("div",{style:{position:"absolute",top:e.rect.top+e.rect.height*e.offsetYRatio-12,left:e.rect.left+e.rect.width*e.offsetXRatio-12,zIndex:2147483600,pointerEvents:"none"},children:i("div",{style:V,children:"\u2022"})}),i("div",{style:{position:"absolute",top:e.clickY+10,left:e.clickX+10,zIndex:2147483647},children:i(O,{initial:"",initialAttachments:[],feedbackSessionId:n,apiBase:r,onCancel:s,onSave:c})})]})}function ae({comments:e,currentPath:n,onClose:r,onSelect:s}){let c=new Map;for(let l of e){let a=c.get(l.pagePath)??[];a.push(l),c.set(l.pagePath,a)}let p=Array.from(c.keys()).sort((l,a)=>l===n?-1:a===n?1:l.localeCompare(a));return g("div",{style:Pe,children:[g("div",{style:Re,children:[g("strong",{style:{fontSize:14},children:["Comments (",e.length,")"]}),i("button",{type:"button",onClick:r,style:U,children:"Close"})]}),i("div",{style:Ie,children:e.length===0?i("div",{style:$e,children:"No comments yet. Click \u201CAdd comment\u201D to leave one."}):p.map(l=>g("div",{style:{marginBottom:12},children:[i("div",{style:Te,children:l===n?`${l} \xB7 current`:l}),(c.get(l)??[]).map((a,d)=>g("button",{type:"button",onClick:()=>s(a),style:Ae,disabled:a.status==="done"&&!1,children:[i("span",{style:Le,children:d+1}),i("span",{style:Fe,children:a.content.length>140?`${a.content.slice(0,140)}\u2026`:a.content}),a.status==="done"&&i("span",{style:_e,children:"done"})]},a.id))]},l))})]})}var z=5,le=5*1024*1024,ce=e=>new Promise(n=>{let r=URL.createObjectURL(e),s=new window.Image;s.onload=()=>{URL.revokeObjectURL(r),n({width:s.naturalWidth,height:s.naturalHeight})},s.onerror=()=>{URL.revokeObjectURL(r),n(null)},s.src=r});function O({initial:e,initialAttachments:n,feedbackSessionId:r,apiBase:s,onCancel:c,onSave:p,onRemove:l}){let[a,d]=h(e),[f,y]=h(n),[S,$]=h(!1),[P,b]=h(!1),[R,x]=h(null),T=M(null),I=M(null);E(()=>{let t=T.current;if(!t)return;t.focus();let o=t.value.length;t.setSelectionRange(o,o)},[]);let F=async t=>{t.preventDefault();let o=a.trim();if(o){$(!0);try{await p(o,f)}finally{$(!1)}}},_=async t=>{if(!t||t.length===0||!s)return;x(null);let o=z-f.length;if(o<=0){x(`Max ${z} images per comment`);return}let m=Array.from(t).slice(0,o);for(let u of m){if(!u.type.startsWith("image/")){x(`"${u.name}" is not an image`);return}if(u.size>le){x(`"${u.name}" exceeds 5 MB`);return}}b(!0);try{let u=await Promise.all(m.map(ce)),v=new FormData;m.forEach((C,L)=>{v.append("file",C),v.append("width",u[L]?.width?String(u[L]?.width):""),v.append("height",u[L]?.height?String(u[L]?.height):"")});let w=await fetch(`${s}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(r)}`,{method:"POST",credentials:"include",body:v});if(!w.ok){let C=await w.json().catch(()=>null);x(C?.error??"Upload failed");return}let k=await w.json();y(C=>[...C,...k.uploads])}catch{x("Upload failed")}finally{b(!1),I.current&&(I.current.value="")}},A=t=>{y(o=>o.filter(m=>m.url!==t))};return g("form",{onSubmit:F,style:ge,children:[i("textarea",{ref:T,value:a,onChange:t=>d(t.target.value),placeholder:"Leave a comment\u2026",style:he,rows:3}),f.length>0&&i("div",{style:ve,children:f.map(t=>g("div",{style:Ce,children:[i("img",{src:t.url,alt:"",style:xe}),i("button",{type:"button",onClick:()=>A(t.url),style:ke,disabled:S||P,"aria-label":"Remove attachment",children:"\xD7"})]},t.url))}),R&&i("div",{style:Ee,children:R}),i("input",{ref:I,type:"file",accept:"image/*",multiple:!0,onChange:t=>void _(t.target.files),style:{display:"none"}}),g("div",{style:ye,children:[l&&i("button",{type:"button",onClick:()=>l(),style:we,disabled:S,children:"Delete"}),i("button",{type:"button",onClick:()=>I.current?.click(),style:U,disabled:S||P||f.length>=z,children:P?"Uploading\u2026":`Attach image${f.length>0?` (${f.length})`:""}`}),i("div",{style:{flex:1}}),i("button",{type:"button",onClick:c,style:U,disabled:S,children:"Cancel"}),i("button",{type:"submit",style:Se,disabled:S||P||!a.trim(),children:S?"Saving\u2026":"Save"})]})]})}function de(e,n,r){let[s,c]=h(null);return E(()=>{let p=()=>{let a=null;try{a=document.querySelector(e)}catch{a=null}if(!a){c(null);return}let d=a.getBoundingClientRect();c({top:d.top+window.scrollY+d.height*r-12,left:d.left+window.scrollX+d.width*n-12})};p();let l=new ResizeObserver(p);try{let a=document.querySelector(e);a&&l.observe(a)}catch{}return window.addEventListener("scroll",p,!0),window.addEventListener("resize",p),()=>{l.disconnect(),window.removeEventListener("scroll",p,!0),window.removeEventListener("resize",p)}},[e,n,r]),s}var ue={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"rgba(17, 17, 17, 0.92)",color:"white",borderRadius:999,boxShadow:"0 8px 24px rgba(0,0,0,0.25)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',fontSize:14,pointerEvents:"auto"},D={border:"none",background:"white",color:"#111",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},pe={...D,background:"#ef4444",color:"white"},me={border:"1px solid rgba(255,255,255,0.4)",background:"transparent",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:500,fontSize:13},fe={border:"none",background:"#10b981",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},be={opacity:.8,fontSize:12},V={width:24,height:24,borderRadius:999,background:"#10b981",color:"white",border:"2px solid white",cursor:"pointer",fontSize:12,fontWeight:700,boxShadow:"0 2px 6px rgba(0,0,0,0.3)",display:"inline-flex",alignItems:"center",justifyContent:"center",padding:0},ge={background:"white",border:"1px solid #e5e7eb",borderRadius:8,padding:12,width:280,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',display:"flex",flexDirection:"column",gap:8,color:"#111"},he={width:"100%",border:"1px solid #d1d5db",borderRadius:6,padding:8,fontSize:14,resize:"vertical",fontFamily:"inherit",color:"#111",background:"white",boxSizing:"border-box"},ye={display:"flex",alignItems:"center",gap:6},j={border:"1px solid transparent",borderRadius:6,padding:"6px 10px",fontSize:13,cursor:"pointer",fontWeight:500},Se={...j,background:"#111",color:"white"},U={...j,background:"transparent",color:"#374151",borderColor:"#d1d5db"},we={...j,background:"transparent",color:"#b91c1c",borderColor:"#fecaca"},ve={display:"flex",flexWrap:"wrap",gap:6},Ce={position:"relative",width:56,height:56,borderRadius:6,overflow:"hidden",border:"1px solid #e5e7eb",background:"#f9fafb"},xe={width:"100%",height:"100%",objectFit:"cover",display:"block"},ke={position:"absolute",top:2,right:2,width:18,height:18,borderRadius:999,border:"none",background:"rgba(17, 17, 17, 0.85)",color:"white",fontSize:13,lineHeight:"16px",cursor:"pointer",padding:0,display:"inline-flex",alignItems:"center",justifyContent:"center"},Ee={color:"#b91c1c",fontSize:12,margin:0},Pe={position:"fixed",top:0,right:0,bottom:0,width:360,maxWidth:"92vw",background:"white",borderLeft:"1px solid #e5e7eb",boxShadow:"-12px 0 32px rgba(0,0,0,0.12)",zIndex:2147483646,display:"flex",flexDirection:"column",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111"},Re={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e5e7eb"},Ie={flex:1,overflow:"auto",padding:"12px 12px 24px"},$e={color:"#6b7280",fontSize:13,padding:"24px 8px",textAlign:"center"},Te={fontSize:11,textTransform:"uppercase",letterSpacing:.5,color:"#6b7280",padding:"4px 4px 6px",wordBreak:"break-all"},Ae={display:"flex",alignItems:"flex-start",gap:8,width:"100%",textAlign:"left",background:"white",border:"1px solid #e5e7eb",borderRadius:6,padding:"8px 10px",cursor:"pointer",fontSize:13,marginBottom:6,color:"#111"},Le={flexShrink:0,width:22,height:22,borderRadius:999,background:"#10b981",color:"white",fontWeight:700,fontSize:11,display:"inline-flex",alignItems:"center",justifyContent:"center"},Fe={flex:1,whiteSpace:"pre-wrap",wordBreak:"break-word"},_e={flexShrink:0,background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:600,padding:"2px 6px",borderRadius:4,alignSelf:"center"},Be={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",flexDirection:"column",gap:8,padding:"12px 16px",width:"min(420px, calc(100vw - 32px))",background:"white",border:"1px solid #e5e7eb",borderRadius:12,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111",pointerEvents:"auto"},Ne={display:"flex",alignItems:"center",gap:8},ze={background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:700,padding:"2px 8px",borderRadius:999,textTransform:"uppercase",letterSpacing:.5},Me={display:"flex",justifyContent:"space-between",fontSize:12},Ue={width:"100%",height:6,background:"#f3f4f6",borderRadius:999,overflow:"hidden"},je={height:"100%",background:"#10b981",borderRadius:999,transition:"width 500ms"},Xe={margin:0,fontSize:12,color:"#6b7280"};function Y(){if(console.log("[YNS Feedback Toolbar] mountFeedbackToolbar() called",{isWindow:typeof window<"u",vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,alreadyMounted:typeof document<"u"&&!!document.getElementById(B)}),typeof window>"u")return null;if(process.env.NEXT_PUBLIC_VERCEL_ENV!=="preview")return console.log("[YNS Feedback Toolbar] gate failed \u2014 NEXT_PUBLIC_VERCEL_ENV is",process.env.NEXT_PUBLIC_VERCEL_ENV),null;if(document.getElementById(B))return null;let e=document.createElement("div");e.id=B,e.dataset.ynsFeedbackUi="true",document.body.appendChild(e);let n=W(e);return n.render(i(te,{})),{unmount:()=>{n.unmount(),e.remove()}}}typeof window<"u"&&console.log("[YNS Feedback Toolbar] module evaluated",{vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,readyState:document.readyState,willAutoMount:process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"});typeof window<"u"&&process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{Y()}):Y());export{Y as mountFeedbackToolbar};
|
|
1
|
+
import{useEffect as P,useRef as U,useState as h}from"react";import{createRoot as G}from"react-dom/client";import{Fragment as V,jsx as i,jsxs as g}from"react/jsx-runtime";var N="yns-feedback-toolbar-root",q=()=>{if(typeof window>"u")return null;let e=(process.env.NEXT_PUBLIC_YNS_API_BASE??"").trim();if(e)return e.replace(/\/$/,"");let n=window.location.hostname,r=window.location.protocol;return n.endsWith(".yns.store")?`${r}//yns.store`:n.endsWith(".yns.cx")?`${r}//yns.cx`:window.location.origin},J=e=>{if(e.id)return`#${CSS.escape(e.id)}`;let n=[],r=e;for(;r&&r.nodeType===Node.ELEMENT_NODE&&n.length<6;){let s=r.tagName.toLowerCase(),c=r.getAttribute("class");if(c){let a=c.split(/\s+/).filter(Boolean).slice(0,2).map(d=>`.${CSS.escape(d)}`).join("");s+=a}let p=r.parentElement,l=r.tagName;if(p){let a=Array.from(p.children).filter(d=>d.tagName===l);if(a.length>1){let d=a.indexOf(r)+1;s+=`:nth-of-type(${d})`}}n.unshift(s),r=p}return n.join(" > ")},K=["id","class","data-testid","aria-label","role","name","href","src"],z=e=>{let n=[];for(let r of K){let s=e.getAttribute(r);if(!s)continue;let c=s.length>80?`${s.slice(0,80)}\u2026`:s;n.push(` ${r}="${c.replace(/"/g,""")}"`)}return n.join("")},j=e=>{let n=(e.textContent??"").replace(/\s+/g," ").trim();return n?n.length>100?`${n.slice(0,100)}\u2026`:n:""},Q=e=>{let n=[],r=e;for(;r&&r!==document.documentElement&&n.length<5;){let d=r.parentElement;if(!d)break;n.unshift(d),r=d}let s=[],c=0;for(let d of n){let f=" ".repeat(c);s.push(`${f}<${d.tagName.toLowerCase()}${z(d)}>`),c++}let p=" ".repeat(c),l=e.tagName.toLowerCase(),a=j(e);if(s.push(`${p}<${l}${z(e)}>${a?`${a}</${l}>`:""} \u2190 TARGET`),!a){let d=" ".repeat(c+1),f=Array.from(e.children).slice(0,6);for(let y of f){let S=j(y);s.push(`${d}<${y.tagName.toLowerCase()}${z(y)}>${S?`${S}</${y.tagName.toLowerCase()}>`:""}`)}e.children.length>6&&s.push(`${d}\u2026 (${e.children.length-6} more children)`),s.push(`${p}</${l}>`)}for(let d=n.length-1;d>=0;d--){let f=" ".repeat(d),y=n[d];y&&s.push(`${f}</${y.tagName.toLowerCase()}>`)}return s.join(`
|
|
2
|
+
`)},X=e=>{let n=e;for(;n;){if(n instanceof HTMLElement&&n.dataset.ynsFeedbackUi==="true")return!0;n=n.parentElement}return!1},Z=new Set(["HTML","HEAD","SCRIPT","STYLE","NOSCRIPT"]),ee=1e4,te=e=>{let n=new Date(e),r=n.getTime()-Date.now(),s=n.toLocaleString(void 0,{weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"});if(r<=0)return`Any moment now (estimated by ${s})`;let c=Math.ceil(r/6e4);if(c<60)return`~${c} minutes (by ${s})`;let p=Math.round(r/36e5);if(p<24)return`~${p} ${p===1?"hour":"hours"} (by ${s})`;let l=Math.round(r/864e5);return`~${l} ${l===1?"day":"days"} (by ${s})`};function ne(){let[e,n]=h(null),[r,s]=h(!0),[c,p]=h(!1),[l,a]=h(null),[d,f]=h(null),[y,S]=h(!1),[T,k]=h(!1),b=U(null);P(()=>{if(b.current=q(),!b.current){s(!1);return}let t=!1,o=new AbortController,m=async()=>{try{let v=await fetch(`${b.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include",signal:o.signal});if(t)return;if(!v.ok){n(null);return}let w=await v.json();if(t)return;n(w),w.sessionStatus==="done"&&window.location.reload()}catch{t||n(null)}finally{t||s(!1)}};m();let u=window.setInterval(m,ee);return()=>{t=!0,o.abort(),window.clearInterval(u)}},[]),P(()=>{!e||e.canComment||(p(!1),a(null),S(!1),f(null))},[e]),P(()=>{if(c)return document.body.style.cursor="crosshair",()=>{document.body.style.cursor=""}},[c]),P(()=>{if(!c)return;let t=document.createElement("div");t.dataset.ynsFeedbackUi="true",t.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483644","border: 2px dashed #10b981","background: rgba(16, 185, 129, 0.08)","border-radius: 3px","display: none","transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s"].join(";");let o=document.createElement("div");o.dataset.ynsFeedbackUi="true",o.textContent="Click to comment",o.style.cssText=["position: fixed","pointer-events: none","z-index: 2147483645","background: #059669","color: #fff","font-size: 11px","font-family: ui-sans-serif, system-ui, sans-serif","padding: 3px 8px","border-radius: 4px","white-space: nowrap","display: none","box-shadow: 0 2px 6px rgba(0,0,0,0.15)"].join(";"),document.documentElement.appendChild(t),document.documentElement.appendChild(o);let m=null,u=w=>{let E=document.elementFromPoint(w.clientX,w.clientY);if(!E||Z.has(E.tagName)||X(E)){t.style.display="none",o.style.display="none",m=null;return}m=E,requestAnimationFrame(()=>{if(m!==E)return;let C=E.getBoundingClientRect();t.style.top=`${C.top}px`,t.style.left=`${C.left}px`,t.style.width=`${C.width}px`,t.style.height=`${C.height}px`,t.style.display="block",o.style.left=`${w.clientX+14}px`,o.style.top=`${w.clientY+14}px`,o.style.display="block"})},v=()=>{t.style.display="none",o.style.display="none",m=null};return document.addEventListener("mousemove",u,!0),document.addEventListener("mouseleave",v),()=>{document.removeEventListener("mousemove",u,!0),document.removeEventListener("mouseleave",v),t.remove(),o.remove()}},[c]),P(()=>{if(!c)return;let t=o=>{let m=o.target;if(!(m instanceof Element)||X(m))return;o.preventDefault(),o.stopPropagation();let u=m.getBoundingClientRect(),v=u.width>0?(o.clientX-u.left)/u.width:.5,w=u.height>0?(o.clientY-u.top)/u.height:.5;a({cssSelector:J(m),pagePath:window.location.pathname,surroundingHtml:Q(m),rect:{top:u.top+window.scrollY,left:u.left+window.scrollX,width:u.width,height:u.height},clickX:o.clientX+window.scrollX,clickY:o.clientY+window.scrollY,offsetXRatio:Math.min(1,Math.max(0,v)),offsetYRatio:Math.min(1,Math.max(0,w))}),p(!1)};return document.addEventListener("click",t,{capture:!0}),()=>document.removeEventListener("click",t,{capture:!0})},[c]);let R=async()=>{if(!b.current)return;let t=await fetch(`${b.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,{credentials:"include"});if(!t.ok)return;let o=await t.json();n(o)},x=async(t,o)=>{!b.current||!e||!l||!(await fetch(`${b.current}/api/feedback-comments`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({feedbackSessionId:e.feedbackSessionId,content:t,pagePath:l.pagePath,cssSelector:l.cssSelector,surroundingHtml:l.surroundingHtml,offsetXRatio:l.offsetXRatio,offsetYRatio:l.offsetYRatio,...o.length>0?{attachments:o}:{}})})).ok||(a(null),p(!0),await R())},$=async(t,o,m)=>{!b.current||!(await fetch(`${b.current}/api/feedback-comments/${t}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:o,attachments:m})})).ok||(f(null),await R())},I=async t=>{!b.current||!(await fetch(`${b.current}/api/feedback-comments/${t}`,{method:"DELETE",credentials:"include"})).ok||await R()},_=async()=>{if(!(!b.current||!e)&&window.confirm("Finalize this feedback session? You won't be able to add more comments.")){k(!0);try{let t=await fetch(`${b.current}/api/feedback-sessions/${e.feedbackSessionId}/finalize`,{method:"POST",credentials:"include"});if(!t.ok)return;let m=(await t.json().catch(()=>null))?.status??"processing";n(u=>u&&{...u,canComment:!1,sessionStatus:m})}finally{k(!1)}}},B=t=>{if(t.pagePath!==window.location.pathname){window.location.assign(t.pagePath);return}let o=null;try{o=document.querySelector(t.cssSelector)}catch{o=null}o&&(o.scrollIntoView({behavior:"smooth",block:"center"}),f(t.id))};if(r||!e)return null;if(!e.canComment)return e.sessionStatus!=="processing"&&e.sessionStatus!=="in_review"?null:i("div",{"data-yns-feedback-ui":"true",children:i(oe,{progress:e.progress,eta:e.eta,status:e.sessionStatus})});let A=e.comments.filter(t=>t.pagePath===window.location.pathname&&t.status!=="done");return g("div",{"data-yns-feedback-ui":"true",children:[A.map((t,o)=>i(se,{pin:t,number:o+1,editing:d===t.id,feedbackSessionId:e.feedbackSessionId,apiBase:b.current,onStartEdit:()=>f(t.id),onCancelEdit:()=>f(null),onSave:(m,u)=>$(t.id,m,u),onRemove:()=>I(t.id)},t.id)),l&&i(ae,{pending:l,feedbackSessionId:e.feedbackSessionId,apiBase:b.current,onCancel:()=>{a(null),p(!0)},onSave:(t,o)=>x(t,o)}),y&&i(le,{comments:e.comments,currentPath:window.location.pathname,onClose:()=>S(!1),onSelect:B}),g("div",{style:pe,children:[i("button",{type:"button",onClick:()=>p(t=>!t),style:c?me:O,children:c?"Cancel":"Add comment"}),i("button",{type:"button",onClick:()=>S(t=>!t),style:fe,children:y?"Hide list":`List (${e.comments.length})`}),i("button",{type:"button",onClick:_,disabled:T,style:be,children:T?"Finalizing\u2026":"Finalize"}),i("span",{style:ge,children:c?"Click any element to comment":`${A.length} on this page`})]})]})}function oe({progress:e,eta:n,status:r}){let[,s]=h(0);P(()=>{let a=window.setInterval(()=>s(d=>d+1),6e4);return()=>window.clearInterval(a)},[]);let c=te(n);return g("div",{style:Ue,children:[i("style",{children:ie}),g("div",{style:je,children:[i(re,{}),i("span",{style:Xe,children:"Submitted"}),i("strong",{style:{fontSize:13},children:r==="in_review"?"Feedback under review":"Applying feedback"})]}),g("div",{style:He,children:[i("span",{children:"Review progress"}),i("span",{style:{opacity:.7},children:e.label})]}),i("div",{style:Ye,children:i("div",{style:{...Oe,width:`${e.fillPct}%`}})}),g("p",{style:De,children:["Estimated delivery: ",i("span",{style:{fontWeight:600},children:c})]})]})}var ie="@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }";function re({size:e=14}){return i("span",{"aria-hidden":!0,style:{display:"inline-block",width:e,height:e,border:"2px solid rgba(16, 185, 129, 0.25)",borderTopColor:"#10b981",borderRadius:999,animation:"yns-feedback-spin 0.9s linear infinite"}})}function se({pin:e,number:n,editing:r,feedbackSessionId:s,apiBase:c,onStartEdit:p,onCancelEdit:l,onSave:a,onRemove:d}){let f=ue(e.cssSelector,e.offsetXRatio,e.offsetYRatio);return f?g("div",{style:{position:"absolute",top:f.top,left:f.left,zIndex:2147483600,pointerEvents:"auto"},children:[i("button",{type:"button",onClick:p,style:D,title:e.content,children:n}),r&&i(Y,{initial:e.content,initialAttachments:e.attachments,feedbackSessionId:s,apiBase:c,onCancel:l,onSave:a,onRemove:d})]}):null}function ae({pending:e,feedbackSessionId:n,apiBase:r,onCancel:s,onSave:c}){return g(V,{children:[i("div",{style:{position:"absolute",top:e.rect.top+e.rect.height*e.offsetYRatio-12,left:e.rect.left+e.rect.width*e.offsetXRatio-12,zIndex:2147483600,pointerEvents:"none"},children:i("div",{style:D,children:"\u2022"})}),i("div",{style:{position:"absolute",top:e.clickY+10,left:e.clickX+10,zIndex:2147483647},children:i(Y,{initial:"",initialAttachments:[],feedbackSessionId:n,apiBase:r,onCancel:s,onSave:c})})]})}function le({comments:e,currentPath:n,onClose:r,onSelect:s}){let c=new Map;for(let l of e){let a=c.get(l.pagePath)??[];a.push(l),c.set(l.pagePath,a)}let p=Array.from(c.keys()).sort((l,a)=>l===n?-1:a===n?1:l.localeCompare(a));return g("div",{style:$e,children:[g("div",{style:Ae,children:[g("strong",{style:{fontSize:14},children:["Comments (",e.length,")"]}),i("button",{type:"button",onClick:r,style:W,children:"Close"})]}),i("div",{style:Le,children:e.length===0?i("div",{style:Fe,children:"No comments yet. Click \u201CAdd comment\u201D to leave one."}):p.map(l=>g("div",{style:{marginBottom:12},children:[i("div",{style:_e,children:l===n?`${l} \xB7 current`:l}),(c.get(l)??[]).map((a,d)=>g("button",{type:"button",onClick:()=>s(a),style:Be,disabled:a.status==="done"&&!1,children:[i("span",{style:Ne,children:d+1}),i("span",{style:ze,children:a.content.length>140?`${a.content.slice(0,140)}\u2026`:a.content}),a.status==="done"&&i("span",{style:Me,children:"done"})]},a.id))]},l))})]})}var M=5,ce=5*1024*1024,de=e=>new Promise(n=>{let r=URL.createObjectURL(e),s=new window.Image;s.onload=()=>{URL.revokeObjectURL(r),n({width:s.naturalWidth,height:s.naturalHeight})},s.onerror=()=>{URL.revokeObjectURL(r),n(null)},s.src=r});function Y({initial:e,initialAttachments:n,feedbackSessionId:r,apiBase:s,onCancel:c,onSave:p,onRemove:l}){let[a,d]=h(e),[f,y]=h(n),[S,T]=h(!1),[k,b]=h(!1),[R,x]=h(null),$=U(null),I=U(null);P(()=>{let t=$.current;if(!t)return;t.focus();let o=t.value.length;t.setSelectionRange(o,o)},[]);let _=async t=>{t.preventDefault();let o=a.trim();if(o){T(!0);try{await p(o,f)}finally{T(!1)}}},B=async t=>{if(!t||t.length===0||!s)return;x(null);let o=M-f.length;if(o<=0){x(`Max ${M} images per comment`);return}let m=Array.from(t).slice(0,o);for(let u of m){if(!u.type.startsWith("image/")){x(`"${u.name}" is not an image`);return}if(u.size>ce){x(`"${u.name}" exceeds 5 MB`);return}}b(!0);try{let u=await Promise.all(m.map(de)),v=new FormData;m.forEach((C,L)=>{v.append("file",C),v.append("width",u[L]?.width?String(u[L]?.width):""),v.append("height",u[L]?.height?String(u[L]?.height):"")});let w=await fetch(`${s}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(r)}`,{method:"POST",credentials:"include",body:v});if(!w.ok){let C=await w.json().catch(()=>null);x(C?.error??"Upload failed");return}let E=await w.json();y(C=>[...C,...E.uploads])}catch{x("Upload failed")}finally{b(!1),I.current&&(I.current.value="")}},A=t=>{y(o=>o.filter(m=>m.url!==t))};return g("form",{onSubmit:_,style:he,children:[i("textarea",{ref:$,value:a,onChange:t=>d(t.target.value),placeholder:"Leave a comment\u2026",style:ye,rows:3}),f.length>0&&i("div",{style:Ce,children:f.map(t=>g("div",{style:ke,children:[i("img",{src:t.url,alt:"",style:xe}),i("button",{type:"button",onClick:()=>A(t.url),style:Ee,disabled:S||k,"aria-label":"Remove attachment",children:"\xD7"})]},t.url))}),R&&i("div",{style:Pe,children:R}),i("input",{ref:I,type:"file",accept:"image/*",multiple:!0,onChange:t=>void B(t.target.files),style:{display:"none"}}),g("div",{style:Se,children:[l&&i("button",{type:"button",onClick:()=>l(),style:ve,disabled:S,children:"Delete"}),i("button",{type:"button",onClick:()=>I.current?.click(),style:Re,disabled:S||k||f.length>=M,"aria-label":k?"Uploading\u2026":"Attach image",title:k?"Uploading\u2026":"Attach image",children:k?i(Te,{}):i(Ie,{})}),i("div",{style:{flex:1}}),i("button",{type:"button",onClick:c,style:W,disabled:S,children:"Cancel"}),i("button",{type:"submit",style:we,disabled:S||k||!a.trim(),children:S?"Saving\u2026":"Save"})]})]})}function ue(e,n,r){let[s,c]=h(null);return P(()=>{let p=()=>{let a=null;try{a=document.querySelector(e)}catch{a=null}if(!a){c(null);return}let d=a.getBoundingClientRect();c({top:d.top+window.scrollY+d.height*r-12,left:d.left+window.scrollX+d.width*n-12})};p();let l=new ResizeObserver(p);try{let a=document.querySelector(e);a&&l.observe(a)}catch{}return window.addEventListener("scroll",p,!0),window.addEventListener("resize",p),()=>{l.disconnect(),window.removeEventListener("scroll",p,!0),window.removeEventListener("resize",p)}},[e,n,r]),s}var pe={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"rgba(17, 17, 17, 0.92)",color:"white",borderRadius:999,boxShadow:"0 8px 24px rgba(0,0,0,0.25)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',fontSize:14,pointerEvents:"auto"},O={border:"none",background:"white",color:"#111",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},me={...O,background:"#ef4444",color:"white"},fe={border:"1px solid rgba(255,255,255,0.4)",background:"transparent",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:500,fontSize:13},be={border:"none",background:"#10b981",color:"white",padding:"6px 12px",borderRadius:999,cursor:"pointer",fontWeight:600,fontSize:13},ge={opacity:.8,fontSize:12},D={width:24,height:24,borderRadius:999,background:"#10b981",color:"white",border:"2px solid white",cursor:"pointer",fontSize:12,fontWeight:700,boxShadow:"0 2px 6px rgba(0,0,0,0.3)",display:"inline-flex",alignItems:"center",justifyContent:"center",padding:0},he={background:"white",border:"1px solid #e5e7eb",borderRadius:8,padding:12,width:280,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',display:"flex",flexDirection:"column",gap:8,color:"#111"},ye={width:"100%",border:"1px solid #d1d5db",borderRadius:6,padding:8,fontSize:14,resize:"vertical",fontFamily:"inherit",color:"#111",background:"white",boxSizing:"border-box"},Se={display:"flex",alignItems:"center",gap:6},F={border:"1px solid transparent",borderRadius:6,padding:"6px 10px",fontSize:13,cursor:"pointer",fontWeight:500},we={...F,background:"#111",color:"white"},W={...F,background:"transparent",color:"#374151",borderColor:"#d1d5db"},ve={...F,background:"transparent",color:"#b91c1c",borderColor:"#fecaca"},Ce={display:"flex",flexWrap:"wrap",gap:6},ke={position:"relative",width:56,height:56,borderRadius:6,overflow:"hidden",border:"1px solid #e5e7eb",background:"#f9fafb"},xe={width:"100%",height:"100%",objectFit:"cover",display:"block"},Ee={position:"absolute",top:2,right:2,width:18,height:18,borderRadius:999,border:"none",background:"rgba(17, 17, 17, 0.85)",color:"white",fontSize:13,lineHeight:"16px",cursor:"pointer",padding:0,display:"inline-flex",alignItems:"center",justifyContent:"center"},Pe={color:"#b91c1c",fontSize:12,margin:0},Re={...F,background:"transparent",color:"#374151",borderColor:"#d1d5db",width:30,height:30,padding:0,display:"inline-flex",alignItems:"center",justifyContent:"center",flexShrink:0};function Ie(){return g("svg",{role:"img","aria-label":"Attach image",width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i("title",{children:"Attach image"}),i("path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.83l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"})]})}function Te(){return g(V,{children:[i("style",{children:"@keyframes yns-attach-spin { to { transform: rotate(360deg); } }"}),i("span",{"aria-hidden":!0,style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(55, 65, 81, 0.25)",borderTopColor:"#374151",borderRadius:999,animation:"yns-attach-spin 0.9s linear infinite"}})]})}var $e={position:"fixed",top:0,right:0,bottom:0,width:360,maxWidth:"92vw",background:"white",borderLeft:"1px solid #e5e7eb",boxShadow:"-12px 0 32px rgba(0,0,0,0.12)",zIndex:2147483646,display:"flex",flexDirection:"column",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111"},Ae={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e5e7eb"},Le={flex:1,overflow:"auto",padding:"12px 12px 24px"},Fe={color:"#6b7280",fontSize:13,padding:"24px 8px",textAlign:"center"},_e={fontSize:11,textTransform:"uppercase",letterSpacing:.5,color:"#6b7280",padding:"4px 4px 6px",wordBreak:"break-all"},Be={display:"flex",alignItems:"flex-start",gap:8,width:"100%",textAlign:"left",background:"white",border:"1px solid #e5e7eb",borderRadius:6,padding:"8px 10px",cursor:"pointer",fontSize:13,marginBottom:6,color:"#111"},Ne={flexShrink:0,width:22,height:22,borderRadius:999,background:"#10b981",color:"white",fontWeight:700,fontSize:11,display:"inline-flex",alignItems:"center",justifyContent:"center"},ze={flex:1,whiteSpace:"pre-wrap",wordBreak:"break-word"},Me={flexShrink:0,background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:600,padding:"2px 6px",borderRadius:4,alignSelf:"center"},Ue={position:"fixed",bottom:16,left:"50%",transform:"translateX(-50%)",zIndex:2147483646,display:"flex",flexDirection:"column",gap:8,padding:"12px 16px",width:"min(420px, calc(100vw - 32px))",background:"white",border:"1px solid #e5e7eb",borderRadius:12,boxShadow:"0 12px 32px rgba(0,0,0,0.18)",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',color:"#111",pointerEvents:"auto"},je={display:"flex",alignItems:"center",gap:8},Xe={background:"#d1fae5",color:"#065f46",fontSize:11,fontWeight:700,padding:"2px 8px",borderRadius:999,textTransform:"uppercase",letterSpacing:.5},He={display:"flex",justifyContent:"space-between",fontSize:12},Ye={width:"100%",height:6,background:"#f3f4f6",borderRadius:999,overflow:"hidden"},Oe={height:"100%",background:"#10b981",borderRadius:999,transition:"width 500ms"},De={margin:0,fontSize:12,color:"#6b7280"};function H(){if(console.log("[YNS Feedback Toolbar] mountFeedbackToolbar() called",{isWindow:typeof window<"u",vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,alreadyMounted:typeof document<"u"&&!!document.getElementById(N)}),typeof window>"u")return null;if(process.env.NEXT_PUBLIC_VERCEL_ENV!=="preview")return console.log("[YNS Feedback Toolbar] gate failed \u2014 NEXT_PUBLIC_VERCEL_ENV is",process.env.NEXT_PUBLIC_VERCEL_ENV),null;if(document.getElementById(N))return null;let e=document.createElement("div");e.id=N,e.dataset.ynsFeedbackUi="true",document.body.appendChild(e);let n=G(e);return n.render(i(ne,{})),{unmount:()=>{n.unmount(),e.remove()}}}typeof window<"u"&&console.log("[YNS Feedback Toolbar] module evaluated",{vercelEnv:process.env.NEXT_PUBLIC_VERCEL_ENV,readyState:document.readyState,willAutoMount:process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"});typeof window<"u"&&process.env.NEXT_PUBLIC_VERCEL_ENV==="preview"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{H()}):H());export{H as mountFeedbackToolbar};
|
|
3
3
|
//# sourceMappingURL=feedback-toolbar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/feedback-toolbar.tsx"],"sourcesContent":["/**\n * Feedback session toolbar — side-effect entry.\n *\n * Importing `commerce-kit/feedback-toolbar` (or `commerce-kit/browser` for the\n * combined entry) mounts a floating toolbar onto the page that lets reviewers\n * leave click-anchored comments. Comments persist via YNS API endpoints,\n * authenticated with the `better-auth` session cookie sent through\n * `credentials: \"include\"`.\n *\n * Gated on `process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\"` — only Vercel\n * preview deploys get the toolbar. Sandbox dev (env undefined) and production\n * (env === \"production\") skip it.\n */\n\nimport { type CSSProperties, type FormEvent, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst MOUNT_NODE_ID = \"yns-feedback-toolbar-root\";\n\ninterface FeedbackAttachment {\n\turl: string;\n\tcontentType: string;\n\twidth?: number;\n\theight?: number;\n}\n\ninterface FeedbackComment {\n\tid: string;\n\tpagePath: string;\n\tcssSelector: string;\n\tcontent: string;\n\tstatus: \"todo\" | \"done\";\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n\tattachments: FeedbackAttachment[];\n}\n\ntype SessionStatus = \"created\" | \"in_progress\" | \"processing\" | \"in_review\" | \"done\";\n\ninterface ReviewProgress {\n\tfillPct: number;\n\tlabel: string;\n}\n\ninterface ActiveSession {\n\tfeedbackSessionId: string;\n\tcomments: FeedbackComment[];\n\tcanComment: boolean;\n\tsessionStatus: SessionStatus;\n\tcommentTotal: number;\n\tcommentDone: number;\n\teta: string;\n\tprogress: ReviewProgress;\n}\n\ninterface PendingPin {\n\tcssSelector: string;\n\tpagePath: string;\n\tsurroundingHtml: string;\n\trect: { top: number; left: number; width: number; height: number };\n\tclickX: number;\n\tclickY: number;\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n}\n\nconst buildApiBase = (): string | null => {\n\tif (typeof window === \"undefined\") return null;\n\tconst override = (process.env.NEXT_PUBLIC_YNS_API_BASE ?? \"\").trim();\n\tif (override) return override.replace(/\\/$/, \"\");\n\n\t// Toolbar runs on a per-store preview deploy (mystore-preview.yns.{store|cx})\n\t// served by the merchant's Vercel project — that host has no YNS API. Aim\n\t// at the apex `yns.store` / `yns.cx`, which yns-app serves and where the\n\t// API lives. Cookies scoped to `.yns.store` / `.yns.cx` travel along.\n\tconst host = window.location.hostname;\n\tconst protocol = window.location.protocol;\n\tif (host.endsWith(\".yns.store\")) return `${protocol}//yns.store`;\n\tif (host.endsWith(\".yns.cx\")) return `${protocol}//yns.cx`;\n\treturn window.location.origin;\n};\n\nconst computeCssSelector = (el: Element): string => {\n\tif (el.id) return `#${CSS.escape(el.id)}`;\n\tconst path: string[] = [];\n\tlet node: Element | null = el;\n\twhile (node && node.nodeType === Node.ELEMENT_NODE && path.length < 6) {\n\t\tlet part = node.tagName.toLowerCase();\n\t\tconst className = node.getAttribute(\"class\");\n\t\tif (className) {\n\t\t\tconst classes = className\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.slice(0, 2)\n\t\t\t\t.map((c) => `.${CSS.escape(c)}`)\n\t\t\t\t.join(\"\");\n\t\t\tpart += classes;\n\t\t}\n\t\tconst parent: Element | null = node.parentElement;\n\t\tconst tag = node.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings: Element[] = Array.from(parent.children).filter((c) => c.tagName === tag);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst idx = siblings.indexOf(node) + 1;\n\t\t\t\tpart += `:nth-of-type(${idx})`;\n\t\t\t}\n\t\t}\n\t\tpath.unshift(part);\n\t\tnode = parent;\n\t}\n\treturn path.join(\" > \");\n};\n\nconst ATTRS_OF_INTEREST = [\"id\", \"class\", \"data-testid\", \"aria-label\", \"role\", \"name\", \"href\", \"src\"];\n\nconst formatAttrs = (el: Element): string => {\n\tconst parts: string[] = [];\n\tfor (const attr of ATTRS_OF_INTEREST) {\n\t\tconst v = el.getAttribute(attr);\n\t\tif (!v) continue;\n\t\tconst trimmed = v.length > 80 ? `${v.slice(0, 80)}…` : v;\n\t\tparts.push(` ${attr}=\"${trimmed.replace(/\"/g, \""\")}\"`);\n\t}\n\treturn parts.join(\"\");\n};\n\nconst formatTextContent = (el: Element): string => {\n\tconst text = (el.textContent ?? \"\").replace(/\\s+/g, \" \").trim();\n\tif (!text) return \"\";\n\treturn text.length > 100 ? `${text.slice(0, 100)}…` : text;\n};\n\nconst buildSurroundingHtml = (target: Element): string => {\n\tconst ancestors: Element[] = [];\n\tlet cur: Element | null = target;\n\twhile (cur && cur !== document.documentElement && ancestors.length < 5) {\n\t\tconst parent: Element | null = cur.parentElement;\n\t\tif (!parent) break;\n\t\tancestors.unshift(parent);\n\t\tcur = parent;\n\t}\n\n\tconst lines: string[] = [];\n\tlet depth = 0;\n\tfor (const ancestor of ancestors) {\n\t\tconst indent = \" \".repeat(depth);\n\t\tlines.push(`${indent}<${ancestor.tagName.toLowerCase()}${formatAttrs(ancestor)}>`);\n\t\tdepth++;\n\t}\n\n\tconst targetIndent = \" \".repeat(depth);\n\tconst targetTag = target.tagName.toLowerCase();\n\tconst targetText = formatTextContent(target);\n\tlines.push(\n\t\t`${targetIndent}<${targetTag}${formatAttrs(target)}>${\n\t\t\ttargetText ? `${targetText}</${targetTag}>` : \"\"\n\t\t} ← TARGET`,\n\t);\n\n\tif (!targetText) {\n\t\tconst childIndent = \" \".repeat(depth + 1);\n\t\tconst children = Array.from(target.children).slice(0, 6);\n\t\tfor (const child of children) {\n\t\t\tconst childText = formatTextContent(child);\n\t\t\tlines.push(\n\t\t\t\t`${childIndent}<${child.tagName.toLowerCase()}${formatAttrs(child)}>${\n\t\t\t\t\tchildText ? `${childText}</${child.tagName.toLowerCase()}>` : \"\"\n\t\t\t\t}`,\n\t\t\t);\n\t\t}\n\t\tif (target.children.length > 6) {\n\t\t\tlines.push(`${childIndent}… (${target.children.length - 6} more children)`);\n\t\t}\n\t\tlines.push(`${targetIndent}</${targetTag}>`);\n\t}\n\n\tfor (let i = ancestors.length - 1; i >= 0; i--) {\n\t\tconst indent = \" \".repeat(i);\n\t\tconst ancestor = ancestors[i];\n\t\tif (!ancestor) continue;\n\t\tlines.push(`${indent}</${ancestor.tagName.toLowerCase()}>`);\n\t}\n\n\treturn lines.join(\"\\n\");\n};\n\nconst isInsideToolbar = (el: Element | null): boolean => {\n\tlet node = el;\n\twhile (node) {\n\t\tif (node instanceof HTMLElement && node.dataset.ynsFeedbackUi === \"true\") return true;\n\t\tnode = node.parentElement;\n\t}\n\treturn false;\n};\n\nconst IGNORED_TAGS = new Set([\"HTML\", \"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\"]);\n\nconst POLL_INTERVAL_MS = 10_000;\n\n// Format a server-computed ETA as remaining + absolute label. The deadline\n// math itself lives in yns-app's `lib/feedback-comments-api.ts` so the toolbar\n// and YNS lock screen stay in sync.\nconst formatEta = (etaIso: string): string => {\n\tconst eta = new Date(etaIso);\n\tconst remainingMs = eta.getTime() - Date.now();\n\tconst dateLabel = eta.toLocaleString(undefined, {\n\t\tweekday: \"short\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t});\n\n\tif (remainingMs <= 0) return `Any moment now (estimated by ${dateLabel})`;\n\tconst remainingMin = Math.ceil(remainingMs / 60_000);\n\tif (remainingMin < 60) return `~${remainingMin} minutes (by ${dateLabel})`;\n\tconst remainingHours = Math.round(remainingMs / 3_600_000);\n\tif (remainingHours < 24) {\n\t\treturn `~${remainingHours} ${remainingHours === 1 ? \"hour\" : \"hours\"} (by ${dateLabel})`;\n\t}\n\tconst remainingDays = Math.round(remainingMs / 86_400_000);\n\treturn `~${remainingDays} ${remainingDays === 1 ? \"day\" : \"days\"} (by ${dateLabel})`;\n};\n\nfunction FeedbackToolbar() {\n\tconst [session, setSession] = useState<ActiveSession | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [pinMode, setPinMode] = useState(false);\n\tconst [pending, setPending] = useState<PendingPin | null>(null);\n\tconst [editingId, setEditingId] = useState<string | null>(null);\n\tconst [sidebarOpen, setSidebarOpen] = useState(false);\n\tconst [finalizing, setFinalizing] = useState(false);\n\tconst apiBase = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tapiBase.current = buildApiBase();\n\t\tif (!apiBase.current) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet cancelled = false;\n\t\tconst controller = new AbortController();\n\n\t\tconst fetchOnce = async () => {\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(\n\t\t\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t\t\t{ credentials: \"include\", signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tsetSession(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = (await res.json()) as ActiveSession;\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetSession(data);\n\t\t\t\t// Hard-refresh once the AI run lands and the preview is up to date.\n\t\t\t\t// Out of scope to diff anything smarter — the new deploy replaces\n\t\t\t\t// the page entirely so a full reload is appropriate here.\n\t\t\t\tif (data.sessionStatus === \"done\") {\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tif (!cancelled) setSession(null);\n\t\t\t} finally {\n\t\t\t\tif (!cancelled) setLoading(false);\n\t\t\t}\n\t\t};\n\n\t\tvoid fetchOnce();\n\t\tconst interval = window.setInterval(fetchOnce, POLL_INTERVAL_MS);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tcontroller.abort();\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, []);\n\n\t// When the session stops accepting comments (finalized → in_review, or\n\t// closed), tear down any in-flight UI so the hover overlay/popover/sidebar\n\t// don't linger after the toolbar's main render returns null.\n\tuseEffect(() => {\n\t\tif (!session || session.canComment) return;\n\t\tsetPinMode(false);\n\t\tsetPending(null);\n\t\tsetSidebarOpen(false);\n\t\tsetEditingId(null);\n\t}, [session]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\treturn () => {\n\t\t\tdocument.body.style.cursor = \"\";\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.dataset.ynsFeedbackUi = \"true\";\n\t\toverlay.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483644\",\n\t\t\t\"border: 2px dashed #10b981\",\n\t\t\t\"background: rgba(16, 185, 129, 0.08)\",\n\t\t\t\"border-radius: 3px\",\n\t\t\t\"display: none\",\n\t\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t\t].join(\";\");\n\n\t\tconst label = document.createElement(\"div\");\n\t\tlabel.dataset.ynsFeedbackUi = \"true\";\n\t\tlabel.textContent = \"Click to comment\";\n\t\tlabel.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483645\",\n\t\t\t\"background: #059669\",\n\t\t\t\"color: #fff\",\n\t\t\t\"font-size: 11px\",\n\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\"padding: 3px 8px\",\n\t\t\t\"border-radius: 4px\",\n\t\t\t\"white-space: nowrap\",\n\t\t\t\"display: none\",\n\t\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t\t].join(\";\");\n\n\t\tdocument.documentElement.appendChild(overlay);\n\t\tdocument.documentElement.appendChild(label);\n\n\t\tlet hovered: Element | null = null;\n\t\tconst handleMove = (e: MouseEvent) => {\n\t\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\t\tif (!el || IGNORED_TAGS.has(el.tagName) || isInsideToolbar(el)) {\n\t\t\t\toverlay.style.display = \"none\";\n\t\t\t\tlabel.style.display = \"none\";\n\t\t\t\thovered = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thovered = el;\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (hovered !== el) return;\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\t\t\t\tlabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tlabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tlabel.style.display = \"block\";\n\t\t\t});\n\t\t};\n\n\t\tconst handleLeave = () => {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thovered = null;\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleLeave);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMove, true);\n\t\t\tdocument.removeEventListener(\"mouseleave\", handleLeave);\n\t\t\toverlay.remove();\n\t\t\tlabel.remove();\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tconst handleClick = (event: MouseEvent) => {\n\t\t\tconst target = event.target;\n\t\t\tif (!(target instanceof Element)) return;\n\t\t\tif (isInsideToolbar(target)) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\tconst offsetXRatio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5;\n\t\t\tconst offsetYRatio = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;\n\t\t\tsetPending({\n\t\t\t\tcssSelector: computeCssSelector(target),\n\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\tsurroundingHtml: buildSurroundingHtml(target),\n\t\t\t\trect: {\n\t\t\t\t\ttop: rect.top + window.scrollY,\n\t\t\t\t\tleft: rect.left + window.scrollX,\n\t\t\t\t\twidth: rect.width,\n\t\t\t\t\theight: rect.height,\n\t\t\t\t},\n\t\t\t\tclickX: event.clientX + window.scrollX,\n\t\t\t\tclickY: event.clientY + window.scrollY,\n\t\t\t\toffsetXRatio: Math.min(1, Math.max(0, offsetXRatio)),\n\t\t\t\toffsetYRatio: Math.min(1, Math.max(0, offsetYRatio)),\n\t\t\t});\n\t\t\tsetPinMode(false);\n\t\t};\n\t\tdocument.addEventListener(\"click\", handleClick, { capture: true });\n\t\treturn () => document.removeEventListener(\"click\", handleClick, { capture: true });\n\t}, [pinMode]);\n\n\tconst refreshComments = async () => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(\n\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t{ credentials: \"include\" },\n\t\t);\n\t\tif (!res.ok) return;\n\t\tconst data = (await res.json()) as ActiveSession;\n\t\tsetSession(data);\n\t};\n\n\tconst submitNewComment = async (content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current || !session || !pending) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tfeedbackSessionId: session.feedbackSessionId,\n\t\t\t\tcontent,\n\t\t\t\tpagePath: pending.pagePath,\n\t\t\t\tcssSelector: pending.cssSelector,\n\t\t\t\tsurroundingHtml: pending.surroundingHtml,\n\t\t\t\toffsetXRatio: pending.offsetXRatio,\n\t\t\t\toffsetYRatio: pending.offsetYRatio,\n\t\t\t\t...(attachments.length > 0 ? { attachments } : {}),\n\t\t\t}),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetPending(null);\n\t\tsetPinMode(true);\n\t\tawait refreshComments();\n\t};\n\n\tconst updateComment = async (id: string, content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"PATCH\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ content, attachments }),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetEditingId(null);\n\t\tawait refreshComments();\n\t};\n\n\tconst removeComment = async (id: string) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"DELETE\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) return;\n\t\tawait refreshComments();\n\t};\n\n\tconst finalizeSession = async () => {\n\t\tif (!apiBase.current || !session) return;\n\t\tif (!window.confirm(\"Finalize this feedback session? You won't be able to add more comments.\")) return;\n\t\tsetFinalizing(true);\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase.current}/api/feedback-sessions/${session.feedbackSessionId}/finalize`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\" },\n\t\t\t);\n\t\t\tif (!res.ok) return;\n\t\t\t// Flip BOTH canComment and sessionStatus locally. Without the status\n\t\t\t// flip, the render guard sees `canComment=false` + `status=in_progress`\n\t\t\t// (a state the SubmittedPanel branch returns null for) and the toolbar\n\t\t\t// disappears for the ~10s gap until the next poll catches up.\n\t\t\tconst body = (await res.json().catch(() => null)) as { status?: SessionStatus } | null;\n\t\t\tconst nextStatus: SessionStatus = body?.status ?? \"processing\";\n\t\t\tsetSession((prev) => (prev ? { ...prev, canComment: false, sessionStatus: nextStatus } : prev));\n\t\t} finally {\n\t\t\tsetFinalizing(false);\n\t\t}\n\t};\n\n\tconst scrollToComment = (comment: FeedbackComment) => {\n\t\tif (comment.pagePath !== window.location.pathname) {\n\t\t\twindow.location.assign(comment.pagePath);\n\t\t\treturn;\n\t\t}\n\t\tlet el: Element | null = null;\n\t\ttry {\n\t\t\tel = document.querySelector(comment.cssSelector);\n\t\t} catch {\n\t\t\tel = null;\n\t\t}\n\t\tif (!el) return;\n\t\tel.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n\t\tsetEditingId(comment.id);\n\t};\n\n\tif (loading || !session) return null;\n\n\t// Session has been finalized for AI review. Show a small status panel with\n\t// the same progress + ETA shown in YNS, but no commenting controls. Polling\n\t// continues in the background; on `done` we hard-reload to pick up the\n\t// fresh deploy.\n\tif (!session.canComment) {\n\t\tif (session.sessionStatus !== \"processing\" && session.sessionStatus !== \"in_review\") return null;\n\t\treturn (\n\t\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t\t<SubmittedPanel progress={session.progress} eta={session.eta} status={session.sessionStatus} />\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst visiblePins = session.comments.filter(\n\t\t(c) => c.pagePath === window.location.pathname && c.status !== \"done\",\n\t);\n\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t{visiblePins.map((pin, idx) => (\n\t\t\t\t<PinOverlay\n\t\t\t\t\tkey={pin.id}\n\t\t\t\t\tpin={pin}\n\t\t\t\t\tnumber={idx + 1}\n\t\t\t\t\tediting={editingId === pin.id}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonStartEdit={() => setEditingId(pin.id)}\n\t\t\t\t\tonCancelEdit={() => setEditingId(null)}\n\t\t\t\t\tonSave={(content, attachments) => updateComment(pin.id, content, attachments)}\n\t\t\t\t\tonRemove={() => removeComment(pin.id)}\n\t\t\t\t/>\n\t\t\t))}\n\n\t\t\t{pending && (\n\t\t\t\t<PendingCommentPopover\n\t\t\t\t\tpending={pending}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonCancel={() => {\n\t\t\t\t\t\tsetPending(null);\n\t\t\t\t\t\tsetPinMode(true);\n\t\t\t\t\t}}\n\t\t\t\t\tonSave={(content, attachments) => submitNewComment(content, attachments)}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{sidebarOpen && (\n\t\t\t\t<CommentsSidebar\n\t\t\t\t\tcomments={session.comments}\n\t\t\t\t\tcurrentPath={window.location.pathname}\n\t\t\t\t\tonClose={() => setSidebarOpen(false)}\n\t\t\t\t\tonSelect={scrollToComment}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => setPinMode((v) => !v)}\n\t\t\t\t\tstyle={pinMode ? toolbarButtonActiveStyle : toolbarButtonStyle}\n\t\t\t\t>\n\t\t\t\t\t{pinMode ? \"Cancel\" : \"Add comment\"}\n\t\t\t\t</button>\n\t\t\t\t<button type=\"button\" onClick={() => setSidebarOpen((v) => !v)} style={toolbarButtonGhostStyle}>\n\t\t\t\t\t{sidebarOpen ? \"Hide list\" : `List (${session.comments.length})`}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={finalizeSession}\n\t\t\t\t\tdisabled={finalizing}\n\t\t\t\t\tstyle={toolbarButtonFinalizeStyle}\n\t\t\t\t>\n\t\t\t\t\t{finalizing ? \"Finalizing…\" : \"Finalize\"}\n\t\t\t\t</button>\n\t\t\t\t<span style={toolbarHintStyle}>\n\t\t\t\t\t{pinMode ? \"Click any element to comment\" : `${visiblePins.length} on this page`}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SubmittedPanel({\n\tprogress,\n\teta: etaIso,\n\tstatus,\n}: {\n\tprogress: ReviewProgress;\n\teta: string;\n\tstatus: SessionStatus;\n}) {\n\t// Re-render every minute so the relative ETA label stays fresh between\n\t// the 10s data polls (which only re-render when the API payload changes).\n\tconst [, setTick] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = window.setInterval(() => setTick((t) => t + 1), 60_000);\n\t\treturn () => window.clearInterval(id);\n\t}, []);\n\n\tconst eta = formatEta(etaIso);\n\tconst isInReview = status === \"in_review\";\n\tconst headline = isInReview ? \"Feedback under review\" : \"Applying feedback\";\n\n\treturn (\n\t\t<div style={submittedPanelStyle}>\n\t\t\t<style>{spinnerKeyframes}</style>\n\t\t\t<div style={submittedHeaderStyle}>\n\t\t\t\t<Spinner />\n\t\t\t\t<span style={submittedBadgeStyle}>Submitted</span>\n\t\t\t\t<strong style={{ fontSize: 13 }}>{headline}</strong>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressLabelStyle}>\n\t\t\t\t<span>Review progress</span>\n\t\t\t\t<span style={{ opacity: 0.7 }}>{progress.label}</span>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressTrackStyle}>\n\t\t\t\t<div style={{ ...submittedProgressFillStyle, width: `${progress.fillPct}%` }} />\n\t\t\t</div>\n\t\t\t<p style={submittedEtaStyle}>\n\t\t\t\tEstimated delivery: <span style={{ fontWeight: 600 }}>{eta}</span>\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n\n// Inline keyframes — toolbar avoids relying on a global stylesheet.\nconst spinnerKeyframes = `@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }`;\n\nfunction Spinner({ size = 14 }: { size?: number }) {\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborder: \"2px solid rgba(16, 185, 129, 0.25)\",\n\t\t\t\tborderTopColor: \"#10b981\",\n\t\t\t\tborderRadius: 999,\n\t\t\t\tanimation: \"yns-feedback-spin 0.9s linear infinite\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction PinOverlay({\n\tpin,\n\tnumber,\n\tediting,\n\tfeedbackSessionId,\n\tapiBase,\n\tonStartEdit,\n\tonCancelEdit,\n\tonSave,\n\tonRemove,\n}: {\n\tpin: FeedbackComment;\n\tnumber: number;\n\tediting: boolean;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonStartEdit: () => void;\n\tonCancelEdit: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove: () => Promise<void>;\n}) {\n\tconst target = useTargetPosition(pin.cssSelector, pin.offsetXRatio, pin.offsetYRatio);\n\tif (!target) return null;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tposition: \"absolute\",\n\t\t\t\ttop: target.top,\n\t\t\t\tleft: target.left,\n\t\t\t\tzIndex: 2147483600,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t>\n\t\t\t<button type=\"button\" onClick={onStartEdit} style={pinDotStyle} title={pin.content}>\n\t\t\t\t{number}\n\t\t\t</button>\n\t\t\t{editing && (\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial={pin.content}\n\t\t\t\t\tinitialAttachments={pin.attachments}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancelEdit}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t\tonRemove={onRemove}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction PendingCommentPopover({\n\tpending,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n}: {\n\tpending: PendingPin;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n}) {\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.rect.top + pending.rect.height * pending.offsetYRatio - 12,\n\t\t\t\t\tleft: pending.rect.left + pending.rect.width * pending.offsetXRatio - 12,\n\t\t\t\t\tzIndex: 2147483600,\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={pinDotStyle}>•</div>\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.clickY + 10,\n\t\t\t\t\tleft: pending.clickX + 10,\n\t\t\t\t\tzIndex: 2147483647,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial=\"\"\n\t\t\t\t\tinitialAttachments={[]}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancel}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n\nfunction CommentsSidebar({\n\tcomments,\n\tcurrentPath,\n\tonClose,\n\tonSelect,\n}: {\n\tcomments: FeedbackComment[];\n\tcurrentPath: string;\n\tonClose: () => void;\n\tonSelect: (comment: FeedbackComment) => void;\n}) {\n\tconst groups = new Map<string, FeedbackComment[]>();\n\tfor (const c of comments) {\n\t\tconst list = groups.get(c.pagePath) ?? [];\n\t\tlist.push(c);\n\t\tgroups.set(c.pagePath, list);\n\t}\n\tconst paths = Array.from(groups.keys()).sort((a, b) => {\n\t\tif (a === currentPath) return -1;\n\t\tif (b === currentPath) return 1;\n\t\treturn a.localeCompare(b);\n\t});\n\n\treturn (\n\t\t<div style={sidebarStyle}>\n\t\t\t<div style={sidebarHeaderStyle}>\n\t\t\t\t<strong style={{ fontSize: 14 }}>Comments ({comments.length})</strong>\n\t\t\t\t<button type=\"button\" onClick={onClose} style={ghostButtonStyle}>\n\t\t\t\t\tClose\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div style={sidebarScrollStyle}>\n\t\t\t\t{comments.length === 0 ? (\n\t\t\t\t\t<div style={sidebarEmptyStyle}>No comments yet. Click “Add comment” to leave one.</div>\n\t\t\t\t) : (\n\t\t\t\t\tpaths.map((path) => (\n\t\t\t\t\t\t<div key={path} style={{ marginBottom: 12 }}>\n\t\t\t\t\t\t\t<div style={sidebarPathStyle}>{path === currentPath ? `${path} · current` : path}</div>\n\t\t\t\t\t\t\t{(groups.get(path) ?? []).map((c, idx) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tkey={c.id}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => onSelect(c)}\n\t\t\t\t\t\t\t\t\tstyle={sidebarItemStyle}\n\t\t\t\t\t\t\t\t\tdisabled={c.status === \"done\" && false}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemIndexStyle}>{idx + 1}</span>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemTextStyle}>\n\t\t\t\t\t\t\t\t\t\t{c.content.length > 140 ? `${c.content.slice(0, 140)}…` : c.content}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{c.status === \"done\" && <span style={sidebarItemDoneStyle}>done</span>}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nconst MAX_ATTACHMENTS = 5;\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nconst readImageDimensions = (file: File): Promise<{ width: number; height: number } | null> =>\n\tnew Promise((resolve) => {\n\t\tconst url = URL.createObjectURL(file);\n\t\tconst img = new window.Image();\n\t\timg.onload = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve({ width: img.naturalWidth, height: img.naturalHeight });\n\t\t};\n\t\timg.onerror = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve(null);\n\t\t};\n\t\timg.src = url;\n\t});\n\nfunction EditPopover({\n\tinitial,\n\tinitialAttachments,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n\tonRemove,\n}: {\n\tinitial: string;\n\tinitialAttachments: FeedbackAttachment[];\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove?: () => Promise<void>;\n}) {\n\tconst [value, setValue] = useState(initial);\n\tconst [attachments, setAttachments] = useState<FeedbackAttachment[]>(initialAttachments);\n\tconst [saving, setSaving] = useState(false);\n\tconst [uploading, setUploading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst textareaRef = useRef<HTMLTextAreaElement | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement | null>(null);\n\n\tuseEffect(() => {\n\t\tconst el = textareaRef.current;\n\t\tif (!el) return;\n\t\tel.focus();\n\t\tconst len = el.value.length;\n\t\tel.setSelectionRange(len, len);\n\t}, []);\n\n\tconst handleSubmit = async (e: FormEvent) => {\n\t\te.preventDefault();\n\t\tconst content = value.trim();\n\t\tif (!content) return;\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tawait onSave(content, attachments);\n\t\t} finally {\n\t\t\tsetSaving(false);\n\t\t}\n\t};\n\n\tconst handleFiles = async (files: FileList | null) => {\n\t\tif (!files || files.length === 0 || !apiBase) return;\n\t\tsetError(null);\n\t\tconst slots = MAX_ATTACHMENTS - attachments.length;\n\t\tif (slots <= 0) {\n\t\t\tsetError(`Max ${MAX_ATTACHMENTS} images per comment`);\n\t\t\treturn;\n\t\t}\n\t\tconst picked = Array.from(files).slice(0, slots);\n\t\tfor (const f of picked) {\n\t\t\tif (!f.type.startsWith(\"image/\")) {\n\t\t\t\tsetError(`\"${f.name}\" is not an image`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (f.size > MAX_ATTACHMENT_BYTES) {\n\t\t\t\tsetError(`\"${f.name}\" exceeds 5 MB`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tsetUploading(true);\n\t\ttry {\n\t\t\tconst dims = await Promise.all(picked.map(readImageDimensions));\n\t\t\tconst fd = new FormData();\n\t\t\tpicked.forEach((file, i) => {\n\t\t\t\tfd.append(\"file\", file);\n\t\t\t\tfd.append(\"width\", dims[i]?.width ? String(dims[i]?.width) : \"\");\n\t\t\t\tfd.append(\"height\", dims[i]?.height ? String(dims[i]?.height) : \"\");\n\t\t\t});\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(feedbackSessionId)}`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\", body: fd },\n\t\t\t);\n\t\t\tif (!res.ok) {\n\t\t\t\tconst body = (await res.json().catch(() => null)) as { error?: string } | null;\n\t\t\t\tsetError(body?.error ?? \"Upload failed\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst body = (await res.json()) as { uploads: FeedbackAttachment[] };\n\t\t\tsetAttachments((prev) => [...prev, ...body.uploads]);\n\t\t} catch {\n\t\t\tsetError(\"Upload failed\");\n\t\t} finally {\n\t\t\tsetUploading(false);\n\t\t\tif (fileInputRef.current) fileInputRef.current.value = \"\";\n\t\t}\n\t};\n\n\tconst removeAttachment = (url: string) => {\n\t\tsetAttachments((prev) => prev.filter((a) => a.url !== url));\n\t};\n\n\treturn (\n\t\t<form onSubmit={handleSubmit} style={popoverStyle}>\n\t\t\t<textarea\n\t\t\t\tref={textareaRef}\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => setValue(e.target.value)}\n\t\t\t\tplaceholder=\"Leave a comment…\"\n\t\t\t\tstyle={textareaStyle}\n\t\t\t\trows={3}\n\t\t\t/>\n\t\t\t{attachments.length > 0 && (\n\t\t\t\t<div style={attachmentRowStyle}>\n\t\t\t\t\t{attachments.map((att) => (\n\t\t\t\t\t\t<div key={att.url} style={attachmentThumbWrapStyle}>\n\t\t\t\t\t\t\t<img src={att.url} alt=\"\" style={attachmentThumbStyle} />\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => removeAttachment(att.url)}\n\t\t\t\t\t\t\t\tstyle={attachmentRemoveStyle}\n\t\t\t\t\t\t\t\tdisabled={saving || uploading}\n\t\t\t\t\t\t\t\taria-label=\"Remove attachment\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{error && <div style={errorTextStyle}>{error}</div>}\n\t\t\t<input\n\t\t\t\tref={fileInputRef}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept=\"image/*\"\n\t\t\t\tmultiple\n\t\t\t\tonChange={(e) => void handleFiles(e.target.files)}\n\t\t\t\tstyle={{ display: \"none\" }}\n\t\t\t/>\n\t\t\t<div style={popoverActionsStyle}>\n\t\t\t\t{onRemove && (\n\t\t\t\t\t<button type=\"button\" onClick={() => onRemove()} style={dangerButtonStyle} disabled={saving}>\n\t\t\t\t\t\tDelete\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\tstyle={ghostButtonStyle}\n\t\t\t\t\tdisabled={saving || uploading || attachments.length >= MAX_ATTACHMENTS}\n\t\t\t\t>\n\t\t\t\t\t{uploading\n\t\t\t\t\t\t? \"Uploading…\"\n\t\t\t\t\t\t: `Attach image${attachments.length > 0 ? ` (${attachments.length})` : \"\"}`}\n\t\t\t\t</button>\n\t\t\t\t<div style={{ flex: 1 }} />\n\t\t\t\t<button type=\"button\" onClick={onCancel} style={ghostButtonStyle} disabled={saving}>\n\t\t\t\t\tCancel\n\t\t\t\t</button>\n\t\t\t\t<button type=\"submit\" style={primaryButtonStyle} disabled={saving || uploading || !value.trim()}>\n\t\t\t\t\t{saving ? \"Saving…\" : \"Save\"}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</form>\n\t);\n}\n\nfunction useTargetPosition(selector: string, offsetXRatio: number, offsetYRatio: number) {\n\tconst [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n\n\tuseEffect(() => {\n\t\tconst update = () => {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) {\n\t\t\t\tsetPos(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tsetPos({\n\t\t\t\ttop: r.top + window.scrollY + r.height * offsetYRatio - 12,\n\t\t\t\tleft: r.left + window.scrollX + r.width * offsetXRatio - 12,\n\t\t\t});\n\t\t};\n\n\t\tupdate();\n\t\tconst ro = new ResizeObserver(update);\n\t\ttry {\n\t\t\tconst el = document.querySelector(selector);\n\t\t\tif (el) ro.observe(el);\n\t\t} catch {\n\t\t\t// swallow invalid selector\n\t\t}\n\t\twindow.addEventListener(\"scroll\", update, true);\n\t\twindow.addEventListener(\"resize\", update);\n\t\treturn () => {\n\t\t\tro.disconnect();\n\t\t\twindow.removeEventListener(\"scroll\", update, true);\n\t\t\twindow.removeEventListener(\"resize\", update);\n\t\t};\n\t}, [selector, offsetXRatio, offsetYRatio]);\n\n\treturn pos;\n}\n\n// Inline styles — toolbar is injected into arbitrary stores, so we avoid relying\n// on any CSS framework being present.\nconst toolbarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n\tpadding: \"8px 12px\",\n\tbackground: \"rgba(17, 17, 17, 0.92)\",\n\tcolor: \"white\",\n\tborderRadius: 999,\n\tboxShadow: \"0 8px 24px rgba(0,0,0,0.25)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tfontSize: 14,\n\tpointerEvents: \"auto\",\n};\n\nconst toolbarButtonStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarButtonActiveStyle: CSSProperties = {\n\t...toolbarButtonStyle,\n\tbackground: \"#ef4444\",\n\tcolor: \"white\",\n};\n\nconst toolbarButtonGhostStyle: CSSProperties = {\n\tborder: \"1px solid rgba(255,255,255,0.4)\",\n\tbackground: \"transparent\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n\tfontSize: 13,\n};\n\nconst toolbarButtonFinalizeStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarHintStyle: CSSProperties = {\n\topacity: 0.8,\n\tfontSize: 12,\n};\n\nconst pinDotStyle: CSSProperties = {\n\twidth: 24,\n\theight: 24,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tborder: \"2px solid white\",\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 700,\n\tboxShadow: \"0 2px 6px rgba(0,0,0,0.3)\",\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tpadding: 0,\n};\n\nconst popoverStyle: CSSProperties = {\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tpadding: 12,\n\twidth: 280,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tcolor: \"#111\",\n};\n\nconst textareaStyle: CSSProperties = {\n\twidth: \"100%\",\n\tborder: \"1px solid #d1d5db\",\n\tborderRadius: 6,\n\tpadding: 8,\n\tfontSize: 14,\n\tresize: \"vertical\",\n\tfontFamily: \"inherit\",\n\tcolor: \"#111\",\n\tbackground: \"white\",\n\tboxSizing: \"border-box\",\n};\n\nconst popoverActionsStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 6,\n};\n\nconst baseButton: CSSProperties = {\n\tborder: \"1px solid transparent\",\n\tborderRadius: 6,\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n};\n\nconst primaryButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"#111\",\n\tcolor: \"white\",\n};\n\nconst ghostButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n};\n\nconst dangerButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#b91c1c\",\n\tborderColor: \"#fecaca\",\n};\n\nconst attachmentRowStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tflexWrap: \"wrap\",\n\tgap: 6,\n};\n\nconst attachmentThumbWrapStyle: CSSProperties = {\n\tposition: \"relative\",\n\twidth: 56,\n\theight: 56,\n\tborderRadius: 6,\n\toverflow: \"hidden\",\n\tborder: \"1px solid #e5e7eb\",\n\tbackground: \"#f9fafb\",\n};\n\nconst attachmentThumbStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: \"100%\",\n\tobjectFit: \"cover\",\n\tdisplay: \"block\",\n};\n\nconst attachmentRemoveStyle: CSSProperties = {\n\tposition: \"absolute\",\n\ttop: 2,\n\tright: 2,\n\twidth: 18,\n\theight: 18,\n\tborderRadius: 999,\n\tborder: \"none\",\n\tbackground: \"rgba(17, 17, 17, 0.85)\",\n\tcolor: \"white\",\n\tfontSize: 13,\n\tlineHeight: \"16px\",\n\tcursor: \"pointer\",\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst errorTextStyle: CSSProperties = {\n\tcolor: \"#b91c1c\",\n\tfontSize: 12,\n\tmargin: 0,\n};\n\nconst sidebarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\ttop: 0,\n\tright: 0,\n\tbottom: 0,\n\twidth: 360,\n\tmaxWidth: \"92vw\",\n\tbackground: \"white\",\n\tborderLeft: \"1px solid #e5e7eb\",\n\tboxShadow: \"-12px 0 32px rgba(0,0,0,0.12)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n};\n\nconst sidebarHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"space-between\",\n\tpadding: \"12px 16px\",\n\tborderBottom: \"1px solid #e5e7eb\",\n};\n\nconst sidebarScrollStyle: CSSProperties = {\n\tflex: 1,\n\toverflow: \"auto\",\n\tpadding: \"12px 12px 24px\",\n};\n\nconst sidebarEmptyStyle: CSSProperties = {\n\tcolor: \"#6b7280\",\n\tfontSize: 13,\n\tpadding: \"24px 8px\",\n\ttextAlign: \"center\",\n};\n\nconst sidebarPathStyle: CSSProperties = {\n\tfontSize: 11,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n\tcolor: \"#6b7280\",\n\tpadding: \"4px 4px 6px\",\n\twordBreak: \"break-all\",\n};\n\nconst sidebarItemStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"flex-start\",\n\tgap: 8,\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 6,\n\tpadding: \"8px 10px\",\n\tcursor: \"pointer\",\n\tfontSize: 13,\n\tmarginBottom: 6,\n\tcolor: \"#111\",\n};\n\nconst sidebarItemIndexStyle: CSSProperties = {\n\tflexShrink: 0,\n\twidth: 22,\n\theight: 22,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tfontWeight: 700,\n\tfontSize: 11,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst sidebarItemTextStyle: CSSProperties = {\n\tflex: 1,\n\twhiteSpace: \"pre-wrap\",\n\twordBreak: \"break-word\",\n};\n\nconst sidebarItemDoneStyle: CSSProperties = {\n\tflexShrink: 0,\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 600,\n\tpadding: \"2px 6px\",\n\tborderRadius: 4,\n\talignSelf: \"center\",\n};\n\nconst submittedPanelStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tpadding: \"12px 16px\",\n\twidth: \"min(420px, calc(100vw - 32px))\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 12,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n\tpointerEvents: \"auto\",\n};\n\nconst submittedHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n};\n\nconst submittedBadgeStyle: CSSProperties = {\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 700,\n\tpadding: \"2px 8px\",\n\tborderRadius: 999,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n};\n\nconst submittedProgressLabelStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tjustifyContent: \"space-between\",\n\tfontSize: 12,\n};\n\nconst submittedProgressTrackStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: 6,\n\tbackground: \"#f3f4f6\",\n\tborderRadius: 999,\n\toverflow: \"hidden\",\n};\n\nconst submittedProgressFillStyle: CSSProperties = {\n\theight: \"100%\",\n\tbackground: \"#10b981\",\n\tborderRadius: 999,\n\ttransition: \"width 500ms\",\n};\n\nconst submittedEtaStyle: CSSProperties = {\n\tmargin: 0,\n\tfontSize: 12,\n\tcolor: \"#6b7280\",\n};\n\nexport function mountFeedbackToolbar(): { unmount: () => void } | null {\n\tconsole.log(\"[YNS Feedback Toolbar] mountFeedbackToolbar() called\", {\n\t\tisWindow: typeof window !== \"undefined\",\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\talreadyMounted: typeof document !== \"undefined\" && Boolean(document.getElementById(MOUNT_NODE_ID)),\n\t});\n\tif (typeof window === \"undefined\") return null;\n\tif (process.env.NEXT_PUBLIC_VERCEL_ENV !== \"preview\") {\n\t\tconsole.log(\n\t\t\t\"[YNS Feedback Toolbar] gate failed — NEXT_PUBLIC_VERCEL_ENV is\",\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\t);\n\t\treturn null;\n\t}\n\tif (document.getElementById(MOUNT_NODE_ID)) return null;\n\n\tconst container = document.createElement(\"div\");\n\tcontainer.id = MOUNT_NODE_ID;\n\tcontainer.dataset.ynsFeedbackUi = \"true\";\n\tdocument.body.appendChild(container);\n\n\tconst root = createRoot(container);\n\troot.render(<FeedbackToolbar />);\n\n\treturn {\n\t\tunmount: () => {\n\t\t\troot.unmount();\n\t\t\tcontainer.remove();\n\t\t},\n\t};\n}\n\n// Auto-mount on import. Wait for DOM ready.\nif (typeof window !== \"undefined\") {\n\tconsole.log(\"[YNS Feedback Toolbar] module evaluated\", {\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\treadyState: document.readyState,\n\t\twillAutoMount: process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\",\n\t});\n}\nif (typeof window !== \"undefined\" && process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\") {\n\tif (document.readyState === \"loading\") {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\t\tmountFeedbackToolbar();\n\t\t});\n\t} else {\n\t\tmountFeedbackToolbar();\n\t}\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","createRoot","Fragment","jsx","jsxs","MOUNT_NODE_ID","buildApiBase","override","host","protocol","computeCssSelector","el","path","node","part","className","classes","c","parent","tag","siblings","idx","ATTRS_OF_INTEREST","formatAttrs","parts","attr","v","trimmed","formatTextContent","text","buildSurroundingHtml","target","ancestors","cur","lines","depth","ancestor","indent","targetIndent","targetTag","targetText","childIndent","children","child","childText","i","isInsideToolbar","IGNORED_TAGS","POLL_INTERVAL_MS","formatEta","etaIso","eta","remainingMs","dateLabel","remainingMin","remainingHours","remainingDays","FeedbackToolbar","session","setSession","loading","setLoading","pinMode","setPinMode","pending","setPending","editingId","setEditingId","sidebarOpen","setSidebarOpen","finalizing","setFinalizing","apiBase","cancelled","controller","fetchOnce","res","data","interval","overlay","label","hovered","handleMove","e","rect","handleLeave","handleClick","event","offsetXRatio","offsetYRatio","refreshComments","submitNewComment","content","attachments","updateComment","id","removeComment","finalizeSession","nextStatus","prev","scrollToComment","comment","SubmittedPanel","visiblePins","pin","PinOverlay","PendingCommentPopover","CommentsSidebar","toolbarStyle","toolbarButtonActiveStyle","toolbarButtonStyle","toolbarButtonGhostStyle","toolbarButtonFinalizeStyle","toolbarHintStyle","progress","status","setTick","t","submittedPanelStyle","spinnerKeyframes","submittedHeaderStyle","Spinner","submittedBadgeStyle","submittedProgressLabelStyle","submittedProgressTrackStyle","submittedProgressFillStyle","submittedEtaStyle","size","number","editing","feedbackSessionId","onStartEdit","onCancelEdit","onSave","onRemove","useTargetPosition","pinDotStyle","EditPopover","onCancel","comments","currentPath","onClose","onSelect","groups","list","paths","a","b","sidebarStyle","sidebarHeaderStyle","ghostButtonStyle","sidebarScrollStyle","sidebarEmptyStyle","sidebarPathStyle","sidebarItemStyle","sidebarItemIndexStyle","sidebarItemTextStyle","sidebarItemDoneStyle","MAX_ATTACHMENTS","MAX_ATTACHMENT_BYTES","readImageDimensions","file","resolve","url","img","initial","initialAttachments","value","setValue","setAttachments","saving","setSaving","uploading","setUploading","error","setError","textareaRef","fileInputRef","len","handleSubmit","handleFiles","files","slots","picked","f","dims","fd","body","removeAttachment","popoverStyle","textareaStyle","attachmentRowStyle","att","attachmentThumbWrapStyle","attachmentThumbStyle","attachmentRemoveStyle","errorTextStyle","popoverActionsStyle","dangerButtonStyle","primaryButtonStyle","selector","pos","setPos","update","r","ro","baseButton","mountFeedbackToolbar","container","root"]}
|
|
1
|
+
{"version":3,"sources":["../src/feedback-toolbar.tsx"],"sourcesContent":["/**\n * Feedback session toolbar — side-effect entry.\n *\n * Importing `commerce-kit/feedback-toolbar` (or `commerce-kit/browser` for the\n * combined entry) mounts a floating toolbar onto the page that lets reviewers\n * leave click-anchored comments. Comments persist via YNS API endpoints,\n * authenticated with the `better-auth` session cookie sent through\n * `credentials: \"include\"`.\n *\n * Gated on `process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\"` — only Vercel\n * preview deploys get the toolbar. Sandbox dev (env undefined) and production\n * (env === \"production\") skip it.\n */\n\nimport { type CSSProperties, type FormEvent, useEffect, useRef, useState } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst MOUNT_NODE_ID = \"yns-feedback-toolbar-root\";\n\ninterface FeedbackAttachment {\n\turl: string;\n\tcontentType: string;\n\twidth?: number;\n\theight?: number;\n}\n\ninterface FeedbackComment {\n\tid: string;\n\tpagePath: string;\n\tcssSelector: string;\n\tcontent: string;\n\tstatus: \"todo\" | \"done\";\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n\tattachments: FeedbackAttachment[];\n}\n\ntype SessionStatus = \"created\" | \"in_progress\" | \"processing\" | \"in_review\" | \"done\";\n\ninterface ReviewProgress {\n\tfillPct: number;\n\tlabel: string;\n}\n\ninterface ActiveSession {\n\tfeedbackSessionId: string;\n\tcomments: FeedbackComment[];\n\tcanComment: boolean;\n\tsessionStatus: SessionStatus;\n\tcommentTotal: number;\n\tcommentDone: number;\n\teta: string;\n\tprogress: ReviewProgress;\n}\n\ninterface PendingPin {\n\tcssSelector: string;\n\tpagePath: string;\n\tsurroundingHtml: string;\n\trect: { top: number; left: number; width: number; height: number };\n\tclickX: number;\n\tclickY: number;\n\toffsetXRatio: number;\n\toffsetYRatio: number;\n}\n\nconst buildApiBase = (): string | null => {\n\tif (typeof window === \"undefined\") return null;\n\tconst override = (process.env.NEXT_PUBLIC_YNS_API_BASE ?? \"\").trim();\n\tif (override) return override.replace(/\\/$/, \"\");\n\n\t// Toolbar runs on a per-store preview deploy (mystore-preview.yns.{store|cx})\n\t// served by the merchant's Vercel project — that host has no YNS API. Aim\n\t// at the apex `yns.store` / `yns.cx`, which yns-app serves and where the\n\t// API lives. Cookies scoped to `.yns.store` / `.yns.cx` travel along.\n\tconst host = window.location.hostname;\n\tconst protocol = window.location.protocol;\n\tif (host.endsWith(\".yns.store\")) return `${protocol}//yns.store`;\n\tif (host.endsWith(\".yns.cx\")) return `${protocol}//yns.cx`;\n\treturn window.location.origin;\n};\n\nconst computeCssSelector = (el: Element): string => {\n\tif (el.id) return `#${CSS.escape(el.id)}`;\n\tconst path: string[] = [];\n\tlet node: Element | null = el;\n\twhile (node && node.nodeType === Node.ELEMENT_NODE && path.length < 6) {\n\t\tlet part = node.tagName.toLowerCase();\n\t\tconst className = node.getAttribute(\"class\");\n\t\tif (className) {\n\t\t\tconst classes = className\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.slice(0, 2)\n\t\t\t\t.map((c) => `.${CSS.escape(c)}`)\n\t\t\t\t.join(\"\");\n\t\t\tpart += classes;\n\t\t}\n\t\tconst parent: Element | null = node.parentElement;\n\t\tconst tag = node.tagName;\n\t\tif (parent) {\n\t\t\tconst siblings: Element[] = Array.from(parent.children).filter((c) => c.tagName === tag);\n\t\t\tif (siblings.length > 1) {\n\t\t\t\tconst idx = siblings.indexOf(node) + 1;\n\t\t\t\tpart += `:nth-of-type(${idx})`;\n\t\t\t}\n\t\t}\n\t\tpath.unshift(part);\n\t\tnode = parent;\n\t}\n\treturn path.join(\" > \");\n};\n\nconst ATTRS_OF_INTEREST = [\"id\", \"class\", \"data-testid\", \"aria-label\", \"role\", \"name\", \"href\", \"src\"];\n\nconst formatAttrs = (el: Element): string => {\n\tconst parts: string[] = [];\n\tfor (const attr of ATTRS_OF_INTEREST) {\n\t\tconst v = el.getAttribute(attr);\n\t\tif (!v) continue;\n\t\tconst trimmed = v.length > 80 ? `${v.slice(0, 80)}…` : v;\n\t\tparts.push(` ${attr}=\"${trimmed.replace(/\"/g, \""\")}\"`);\n\t}\n\treturn parts.join(\"\");\n};\n\nconst formatTextContent = (el: Element): string => {\n\tconst text = (el.textContent ?? \"\").replace(/\\s+/g, \" \").trim();\n\tif (!text) return \"\";\n\treturn text.length > 100 ? `${text.slice(0, 100)}…` : text;\n};\n\nconst buildSurroundingHtml = (target: Element): string => {\n\tconst ancestors: Element[] = [];\n\tlet cur: Element | null = target;\n\twhile (cur && cur !== document.documentElement && ancestors.length < 5) {\n\t\tconst parent: Element | null = cur.parentElement;\n\t\tif (!parent) break;\n\t\tancestors.unshift(parent);\n\t\tcur = parent;\n\t}\n\n\tconst lines: string[] = [];\n\tlet depth = 0;\n\tfor (const ancestor of ancestors) {\n\t\tconst indent = \" \".repeat(depth);\n\t\tlines.push(`${indent}<${ancestor.tagName.toLowerCase()}${formatAttrs(ancestor)}>`);\n\t\tdepth++;\n\t}\n\n\tconst targetIndent = \" \".repeat(depth);\n\tconst targetTag = target.tagName.toLowerCase();\n\tconst targetText = formatTextContent(target);\n\tlines.push(\n\t\t`${targetIndent}<${targetTag}${formatAttrs(target)}>${\n\t\t\ttargetText ? `${targetText}</${targetTag}>` : \"\"\n\t\t} ← TARGET`,\n\t);\n\n\tif (!targetText) {\n\t\tconst childIndent = \" \".repeat(depth + 1);\n\t\tconst children = Array.from(target.children).slice(0, 6);\n\t\tfor (const child of children) {\n\t\t\tconst childText = formatTextContent(child);\n\t\t\tlines.push(\n\t\t\t\t`${childIndent}<${child.tagName.toLowerCase()}${formatAttrs(child)}>${\n\t\t\t\t\tchildText ? `${childText}</${child.tagName.toLowerCase()}>` : \"\"\n\t\t\t\t}`,\n\t\t\t);\n\t\t}\n\t\tif (target.children.length > 6) {\n\t\t\tlines.push(`${childIndent}… (${target.children.length - 6} more children)`);\n\t\t}\n\t\tlines.push(`${targetIndent}</${targetTag}>`);\n\t}\n\n\tfor (let i = ancestors.length - 1; i >= 0; i--) {\n\t\tconst indent = \" \".repeat(i);\n\t\tconst ancestor = ancestors[i];\n\t\tif (!ancestor) continue;\n\t\tlines.push(`${indent}</${ancestor.tagName.toLowerCase()}>`);\n\t}\n\n\treturn lines.join(\"\\n\");\n};\n\nconst isInsideToolbar = (el: Element | null): boolean => {\n\tlet node = el;\n\twhile (node) {\n\t\tif (node instanceof HTMLElement && node.dataset.ynsFeedbackUi === \"true\") return true;\n\t\tnode = node.parentElement;\n\t}\n\treturn false;\n};\n\nconst IGNORED_TAGS = new Set([\"HTML\", \"HEAD\", \"SCRIPT\", \"STYLE\", \"NOSCRIPT\"]);\n\nconst POLL_INTERVAL_MS = 10_000;\n\n// Format a server-computed ETA as remaining + absolute label. The deadline\n// math itself lives in yns-app's `lib/feedback-comments-api.ts` so the toolbar\n// and YNS lock screen stay in sync.\nconst formatEta = (etaIso: string): string => {\n\tconst eta = new Date(etaIso);\n\tconst remainingMs = eta.getTime() - Date.now();\n\tconst dateLabel = eta.toLocaleString(undefined, {\n\t\tweekday: \"short\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t});\n\n\tif (remainingMs <= 0) return `Any moment now (estimated by ${dateLabel})`;\n\tconst remainingMin = Math.ceil(remainingMs / 60_000);\n\tif (remainingMin < 60) return `~${remainingMin} minutes (by ${dateLabel})`;\n\tconst remainingHours = Math.round(remainingMs / 3_600_000);\n\tif (remainingHours < 24) {\n\t\treturn `~${remainingHours} ${remainingHours === 1 ? \"hour\" : \"hours\"} (by ${dateLabel})`;\n\t}\n\tconst remainingDays = Math.round(remainingMs / 86_400_000);\n\treturn `~${remainingDays} ${remainingDays === 1 ? \"day\" : \"days\"} (by ${dateLabel})`;\n};\n\nfunction FeedbackToolbar() {\n\tconst [session, setSession] = useState<ActiveSession | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\tconst [pinMode, setPinMode] = useState(false);\n\tconst [pending, setPending] = useState<PendingPin | null>(null);\n\tconst [editingId, setEditingId] = useState<string | null>(null);\n\tconst [sidebarOpen, setSidebarOpen] = useState(false);\n\tconst [finalizing, setFinalizing] = useState(false);\n\tconst apiBase = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tapiBase.current = buildApiBase();\n\t\tif (!apiBase.current) {\n\t\t\tsetLoading(false);\n\t\t\treturn;\n\t\t}\n\n\t\tlet cancelled = false;\n\t\tconst controller = new AbortController();\n\n\t\tconst fetchOnce = async () => {\n\t\t\ttry {\n\t\t\t\tconst res = await fetch(\n\t\t\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t\t\t{ credentials: \"include\", signal: controller.signal },\n\t\t\t\t);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!res.ok) {\n\t\t\t\t\tsetSession(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst data = (await res.json()) as ActiveSession;\n\t\t\t\tif (cancelled) return;\n\t\t\t\tsetSession(data);\n\t\t\t\t// Hard-refresh once the AI run lands and the preview is up to date.\n\t\t\t\t// Out of scope to diff anything smarter — the new deploy replaces\n\t\t\t\t// the page entirely so a full reload is appropriate here.\n\t\t\t\tif (data.sessionStatus === \"done\") {\n\t\t\t\t\twindow.location.reload();\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tif (!cancelled) setSession(null);\n\t\t\t} finally {\n\t\t\t\tif (!cancelled) setLoading(false);\n\t\t\t}\n\t\t};\n\n\t\tvoid fetchOnce();\n\t\tconst interval = window.setInterval(fetchOnce, POLL_INTERVAL_MS);\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tcontroller.abort();\n\t\t\twindow.clearInterval(interval);\n\t\t};\n\t}, []);\n\n\t// When the session stops accepting comments (finalized → in_review, or\n\t// closed), tear down any in-flight UI so the hover overlay/popover/sidebar\n\t// don't linger after the toolbar's main render returns null.\n\tuseEffect(() => {\n\t\tif (!session || session.canComment) return;\n\t\tsetPinMode(false);\n\t\tsetPending(null);\n\t\tsetSidebarOpen(false);\n\t\tsetEditingId(null);\n\t}, [session]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tdocument.body.style.cursor = \"crosshair\";\n\t\treturn () => {\n\t\t\tdocument.body.style.cursor = \"\";\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\n\t\tconst overlay = document.createElement(\"div\");\n\t\toverlay.dataset.ynsFeedbackUi = \"true\";\n\t\toverlay.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483644\",\n\t\t\t\"border: 2px dashed #10b981\",\n\t\t\t\"background: rgba(16, 185, 129, 0.08)\",\n\t\t\t\"border-radius: 3px\",\n\t\t\t\"display: none\",\n\t\t\t\"transition: top 0.05s, left 0.05s, width 0.05s, height 0.05s\",\n\t\t].join(\";\");\n\n\t\tconst label = document.createElement(\"div\");\n\t\tlabel.dataset.ynsFeedbackUi = \"true\";\n\t\tlabel.textContent = \"Click to comment\";\n\t\tlabel.style.cssText = [\n\t\t\t\"position: fixed\",\n\t\t\t\"pointer-events: none\",\n\t\t\t\"z-index: 2147483645\",\n\t\t\t\"background: #059669\",\n\t\t\t\"color: #fff\",\n\t\t\t\"font-size: 11px\",\n\t\t\t\"font-family: ui-sans-serif, system-ui, sans-serif\",\n\t\t\t\"padding: 3px 8px\",\n\t\t\t\"border-radius: 4px\",\n\t\t\t\"white-space: nowrap\",\n\t\t\t\"display: none\",\n\t\t\t\"box-shadow: 0 2px 6px rgba(0,0,0,0.15)\",\n\t\t].join(\";\");\n\n\t\tdocument.documentElement.appendChild(overlay);\n\t\tdocument.documentElement.appendChild(label);\n\n\t\tlet hovered: Element | null = null;\n\t\tconst handleMove = (e: MouseEvent) => {\n\t\t\tconst el = document.elementFromPoint(e.clientX, e.clientY);\n\t\t\tif (!el || IGNORED_TAGS.has(el.tagName) || isInsideToolbar(el)) {\n\t\t\t\toverlay.style.display = \"none\";\n\t\t\t\tlabel.style.display = \"none\";\n\t\t\t\thovered = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\thovered = el;\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\tif (hovered !== el) return;\n\t\t\t\tconst rect = el.getBoundingClientRect();\n\t\t\t\toverlay.style.top = `${rect.top}px`;\n\t\t\t\toverlay.style.left = `${rect.left}px`;\n\t\t\t\toverlay.style.width = `${rect.width}px`;\n\t\t\t\toverlay.style.height = `${rect.height}px`;\n\t\t\t\toverlay.style.display = \"block\";\n\t\t\t\tlabel.style.left = `${e.clientX + 14}px`;\n\t\t\t\tlabel.style.top = `${e.clientY + 14}px`;\n\t\t\t\tlabel.style.display = \"block\";\n\t\t\t});\n\t\t};\n\n\t\tconst handleLeave = () => {\n\t\t\toverlay.style.display = \"none\";\n\t\t\tlabel.style.display = \"none\";\n\t\t\thovered = null;\n\t\t};\n\n\t\tdocument.addEventListener(\"mousemove\", handleMove, true);\n\t\tdocument.addEventListener(\"mouseleave\", handleLeave);\n\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousemove\", handleMove, true);\n\t\t\tdocument.removeEventListener(\"mouseleave\", handleLeave);\n\t\t\toverlay.remove();\n\t\t\tlabel.remove();\n\t\t};\n\t}, [pinMode]);\n\n\tuseEffect(() => {\n\t\tif (!pinMode) return;\n\t\tconst handleClick = (event: MouseEvent) => {\n\t\t\tconst target = event.target;\n\t\t\tif (!(target instanceof Element)) return;\n\t\t\tif (isInsideToolbar(target)) return;\n\n\t\t\tevent.preventDefault();\n\t\t\tevent.stopPropagation();\n\n\t\t\tconst rect = target.getBoundingClientRect();\n\t\t\tconst offsetXRatio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0.5;\n\t\t\tconst offsetYRatio = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5;\n\t\t\tsetPending({\n\t\t\t\tcssSelector: computeCssSelector(target),\n\t\t\t\tpagePath: window.location.pathname,\n\t\t\t\tsurroundingHtml: buildSurroundingHtml(target),\n\t\t\t\trect: {\n\t\t\t\t\ttop: rect.top + window.scrollY,\n\t\t\t\t\tleft: rect.left + window.scrollX,\n\t\t\t\t\twidth: rect.width,\n\t\t\t\t\theight: rect.height,\n\t\t\t\t},\n\t\t\t\tclickX: event.clientX + window.scrollX,\n\t\t\t\tclickY: event.clientY + window.scrollY,\n\t\t\t\toffsetXRatio: Math.min(1, Math.max(0, offsetXRatio)),\n\t\t\t\toffsetYRatio: Math.min(1, Math.max(0, offsetYRatio)),\n\t\t\t});\n\t\t\tsetPinMode(false);\n\t\t};\n\t\tdocument.addEventListener(\"click\", handleClick, { capture: true });\n\t\treturn () => document.removeEventListener(\"click\", handleClick, { capture: true });\n\t}, [pinMode]);\n\n\tconst refreshComments = async () => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(\n\t\t\t`${apiBase.current}/api/feedback-comments?host=${encodeURIComponent(window.location.host)}`,\n\t\t\t{ credentials: \"include\" },\n\t\t);\n\t\tif (!res.ok) return;\n\t\tconst data = (await res.json()) as ActiveSession;\n\t\tsetSession(data);\n\t};\n\n\tconst submitNewComment = async (content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current || !session || !pending) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments`, {\n\t\t\tmethod: \"POST\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tfeedbackSessionId: session.feedbackSessionId,\n\t\t\t\tcontent,\n\t\t\t\tpagePath: pending.pagePath,\n\t\t\t\tcssSelector: pending.cssSelector,\n\t\t\t\tsurroundingHtml: pending.surroundingHtml,\n\t\t\t\toffsetXRatio: pending.offsetXRatio,\n\t\t\t\toffsetYRatio: pending.offsetYRatio,\n\t\t\t\t...(attachments.length > 0 ? { attachments } : {}),\n\t\t\t}),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetPending(null);\n\t\tsetPinMode(true);\n\t\tawait refreshComments();\n\t};\n\n\tconst updateComment = async (id: string, content: string, attachments: FeedbackAttachment[]) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"PATCH\",\n\t\t\tcredentials: \"include\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ content, attachments }),\n\t\t});\n\t\tif (!res.ok) return;\n\t\tsetEditingId(null);\n\t\tawait refreshComments();\n\t};\n\n\tconst removeComment = async (id: string) => {\n\t\tif (!apiBase.current) return;\n\t\tconst res = await fetch(`${apiBase.current}/api/feedback-comments/${id}`, {\n\t\t\tmethod: \"DELETE\",\n\t\t\tcredentials: \"include\",\n\t\t});\n\t\tif (!res.ok) return;\n\t\tawait refreshComments();\n\t};\n\n\tconst finalizeSession = async () => {\n\t\tif (!apiBase.current || !session) return;\n\t\tif (!window.confirm(\"Finalize this feedback session? You won't be able to add more comments.\")) return;\n\t\tsetFinalizing(true);\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase.current}/api/feedback-sessions/${session.feedbackSessionId}/finalize`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\" },\n\t\t\t);\n\t\t\tif (!res.ok) return;\n\t\t\t// Flip BOTH canComment and sessionStatus locally. Without the status\n\t\t\t// flip, the render guard sees `canComment=false` + `status=in_progress`\n\t\t\t// (a state the SubmittedPanel branch returns null for) and the toolbar\n\t\t\t// disappears for the ~10s gap until the next poll catches up.\n\t\t\tconst body = (await res.json().catch(() => null)) as { status?: SessionStatus } | null;\n\t\t\tconst nextStatus: SessionStatus = body?.status ?? \"processing\";\n\t\t\tsetSession((prev) => (prev ? { ...prev, canComment: false, sessionStatus: nextStatus } : prev));\n\t\t} finally {\n\t\t\tsetFinalizing(false);\n\t\t}\n\t};\n\n\tconst scrollToComment = (comment: FeedbackComment) => {\n\t\tif (comment.pagePath !== window.location.pathname) {\n\t\t\twindow.location.assign(comment.pagePath);\n\t\t\treturn;\n\t\t}\n\t\tlet el: Element | null = null;\n\t\ttry {\n\t\t\tel = document.querySelector(comment.cssSelector);\n\t\t} catch {\n\t\t\tel = null;\n\t\t}\n\t\tif (!el) return;\n\t\tel.scrollIntoView({ behavior: \"smooth\", block: \"center\" });\n\t\tsetEditingId(comment.id);\n\t};\n\n\tif (loading || !session) return null;\n\n\t// Session has been finalized for AI review. Show a small status panel with\n\t// the same progress + ETA shown in YNS, but no commenting controls. Polling\n\t// continues in the background; on `done` we hard-reload to pick up the\n\t// fresh deploy.\n\tif (!session.canComment) {\n\t\tif (session.sessionStatus !== \"processing\" && session.sessionStatus !== \"in_review\") return null;\n\t\treturn (\n\t\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t\t<SubmittedPanel progress={session.progress} eta={session.eta} status={session.sessionStatus} />\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst visiblePins = session.comments.filter(\n\t\t(c) => c.pagePath === window.location.pathname && c.status !== \"done\",\n\t);\n\n\treturn (\n\t\t<div data-yns-feedback-ui=\"true\">\n\t\t\t{visiblePins.map((pin, idx) => (\n\t\t\t\t<PinOverlay\n\t\t\t\t\tkey={pin.id}\n\t\t\t\t\tpin={pin}\n\t\t\t\t\tnumber={idx + 1}\n\t\t\t\t\tediting={editingId === pin.id}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonStartEdit={() => setEditingId(pin.id)}\n\t\t\t\t\tonCancelEdit={() => setEditingId(null)}\n\t\t\t\t\tonSave={(content, attachments) => updateComment(pin.id, content, attachments)}\n\t\t\t\t\tonRemove={() => removeComment(pin.id)}\n\t\t\t\t/>\n\t\t\t))}\n\n\t\t\t{pending && (\n\t\t\t\t<PendingCommentPopover\n\t\t\t\t\tpending={pending}\n\t\t\t\t\tfeedbackSessionId={session.feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase.current}\n\t\t\t\t\tonCancel={() => {\n\t\t\t\t\t\tsetPending(null);\n\t\t\t\t\t\tsetPinMode(true);\n\t\t\t\t\t}}\n\t\t\t\t\tonSave={(content, attachments) => submitNewComment(content, attachments)}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t{sidebarOpen && (\n\t\t\t\t<CommentsSidebar\n\t\t\t\t\tcomments={session.comments}\n\t\t\t\t\tcurrentPath={window.location.pathname}\n\t\t\t\t\tonClose={() => setSidebarOpen(false)}\n\t\t\t\t\tonSelect={scrollToComment}\n\t\t\t\t/>\n\t\t\t)}\n\n\t\t\t<div style={toolbarStyle}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => setPinMode((v) => !v)}\n\t\t\t\t\tstyle={pinMode ? toolbarButtonActiveStyle : toolbarButtonStyle}\n\t\t\t\t>\n\t\t\t\t\t{pinMode ? \"Cancel\" : \"Add comment\"}\n\t\t\t\t</button>\n\t\t\t\t<button type=\"button\" onClick={() => setSidebarOpen((v) => !v)} style={toolbarButtonGhostStyle}>\n\t\t\t\t\t{sidebarOpen ? \"Hide list\" : `List (${session.comments.length})`}\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={finalizeSession}\n\t\t\t\t\tdisabled={finalizing}\n\t\t\t\t\tstyle={toolbarButtonFinalizeStyle}\n\t\t\t\t>\n\t\t\t\t\t{finalizing ? \"Finalizing…\" : \"Finalize\"}\n\t\t\t\t</button>\n\t\t\t\t<span style={toolbarHintStyle}>\n\t\t\t\t\t{pinMode ? \"Click any element to comment\" : `${visiblePins.length} on this page`}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nfunction SubmittedPanel({\n\tprogress,\n\teta: etaIso,\n\tstatus,\n}: {\n\tprogress: ReviewProgress;\n\teta: string;\n\tstatus: SessionStatus;\n}) {\n\t// Re-render every minute so the relative ETA label stays fresh between\n\t// the 10s data polls (which only re-render when the API payload changes).\n\tconst [, setTick] = useState(0);\n\tuseEffect(() => {\n\t\tconst id = window.setInterval(() => setTick((t) => t + 1), 60_000);\n\t\treturn () => window.clearInterval(id);\n\t}, []);\n\n\tconst eta = formatEta(etaIso);\n\tconst isInReview = status === \"in_review\";\n\tconst headline = isInReview ? \"Feedback under review\" : \"Applying feedback\";\n\n\treturn (\n\t\t<div style={submittedPanelStyle}>\n\t\t\t<style>{spinnerKeyframes}</style>\n\t\t\t<div style={submittedHeaderStyle}>\n\t\t\t\t<Spinner />\n\t\t\t\t<span style={submittedBadgeStyle}>Submitted</span>\n\t\t\t\t<strong style={{ fontSize: 13 }}>{headline}</strong>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressLabelStyle}>\n\t\t\t\t<span>Review progress</span>\n\t\t\t\t<span style={{ opacity: 0.7 }}>{progress.label}</span>\n\t\t\t</div>\n\t\t\t<div style={submittedProgressTrackStyle}>\n\t\t\t\t<div style={{ ...submittedProgressFillStyle, width: `${progress.fillPct}%` }} />\n\t\t\t</div>\n\t\t\t<p style={submittedEtaStyle}>\n\t\t\t\tEstimated delivery: <span style={{ fontWeight: 600 }}>{eta}</span>\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n\n// Inline keyframes — toolbar avoids relying on a global stylesheet.\nconst spinnerKeyframes = `@keyframes yns-feedback-spin { to { transform: rotate(360deg); } }`;\n\nfunction Spinner({ size = 14 }: { size?: number }) {\n\treturn (\n\t\t<span\n\t\t\taria-hidden\n\t\t\tstyle={{\n\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\twidth: size,\n\t\t\t\theight: size,\n\t\t\t\tborder: \"2px solid rgba(16, 185, 129, 0.25)\",\n\t\t\t\tborderTopColor: \"#10b981\",\n\t\t\t\tborderRadius: 999,\n\t\t\t\tanimation: \"yns-feedback-spin 0.9s linear infinite\",\n\t\t\t}}\n\t\t/>\n\t);\n}\n\nfunction PinOverlay({\n\tpin,\n\tnumber,\n\tediting,\n\tfeedbackSessionId,\n\tapiBase,\n\tonStartEdit,\n\tonCancelEdit,\n\tonSave,\n\tonRemove,\n}: {\n\tpin: FeedbackComment;\n\tnumber: number;\n\tediting: boolean;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonStartEdit: () => void;\n\tonCancelEdit: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove: () => Promise<void>;\n}) {\n\tconst target = useTargetPosition(pin.cssSelector, pin.offsetXRatio, pin.offsetYRatio);\n\tif (!target) return null;\n\n\treturn (\n\t\t<div\n\t\t\tstyle={{\n\t\t\t\tposition: \"absolute\",\n\t\t\t\ttop: target.top,\n\t\t\t\tleft: target.left,\n\t\t\t\tzIndex: 2147483600,\n\t\t\t\tpointerEvents: \"auto\",\n\t\t\t}}\n\t\t>\n\t\t\t<button type=\"button\" onClick={onStartEdit} style={pinDotStyle} title={pin.content}>\n\t\t\t\t{number}\n\t\t\t</button>\n\t\t\t{editing && (\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial={pin.content}\n\t\t\t\t\tinitialAttachments={pin.attachments}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancelEdit}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t\tonRemove={onRemove}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\nfunction PendingCommentPopover({\n\tpending,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n}: {\n\tpending: PendingPin;\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n}) {\n\treturn (\n\t\t<>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.rect.top + pending.rect.height * pending.offsetYRatio - 12,\n\t\t\t\t\tleft: pending.rect.left + pending.rect.width * pending.offsetXRatio - 12,\n\t\t\t\t\tzIndex: 2147483600,\n\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div style={pinDotStyle}>•</div>\n\t\t\t</div>\n\t\t\t<div\n\t\t\t\tstyle={{\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\ttop: pending.clickY + 10,\n\t\t\t\t\tleft: pending.clickX + 10,\n\t\t\t\t\tzIndex: 2147483647,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<EditPopover\n\t\t\t\t\tinitial=\"\"\n\t\t\t\t\tinitialAttachments={[]}\n\t\t\t\t\tfeedbackSessionId={feedbackSessionId}\n\t\t\t\t\tapiBase={apiBase}\n\t\t\t\t\tonCancel={onCancel}\n\t\t\t\t\tonSave={onSave}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n\nfunction CommentsSidebar({\n\tcomments,\n\tcurrentPath,\n\tonClose,\n\tonSelect,\n}: {\n\tcomments: FeedbackComment[];\n\tcurrentPath: string;\n\tonClose: () => void;\n\tonSelect: (comment: FeedbackComment) => void;\n}) {\n\tconst groups = new Map<string, FeedbackComment[]>();\n\tfor (const c of comments) {\n\t\tconst list = groups.get(c.pagePath) ?? [];\n\t\tlist.push(c);\n\t\tgroups.set(c.pagePath, list);\n\t}\n\tconst paths = Array.from(groups.keys()).sort((a, b) => {\n\t\tif (a === currentPath) return -1;\n\t\tif (b === currentPath) return 1;\n\t\treturn a.localeCompare(b);\n\t});\n\n\treturn (\n\t\t<div style={sidebarStyle}>\n\t\t\t<div style={sidebarHeaderStyle}>\n\t\t\t\t<strong style={{ fontSize: 14 }}>Comments ({comments.length})</strong>\n\t\t\t\t<button type=\"button\" onClick={onClose} style={ghostButtonStyle}>\n\t\t\t\t\tClose\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div style={sidebarScrollStyle}>\n\t\t\t\t{comments.length === 0 ? (\n\t\t\t\t\t<div style={sidebarEmptyStyle}>No comments yet. Click “Add comment” to leave one.</div>\n\t\t\t\t) : (\n\t\t\t\t\tpaths.map((path) => (\n\t\t\t\t\t\t<div key={path} style={{ marginBottom: 12 }}>\n\t\t\t\t\t\t\t<div style={sidebarPathStyle}>{path === currentPath ? `${path} · current` : path}</div>\n\t\t\t\t\t\t\t{(groups.get(path) ?? []).map((c, idx) => (\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\tkey={c.id}\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={() => onSelect(c)}\n\t\t\t\t\t\t\t\t\tstyle={sidebarItemStyle}\n\t\t\t\t\t\t\t\t\tdisabled={c.status === \"done\" && false}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemIndexStyle}>{idx + 1}</span>\n\t\t\t\t\t\t\t\t\t<span style={sidebarItemTextStyle}>\n\t\t\t\t\t\t\t\t\t\t{c.content.length > 140 ? `${c.content.slice(0, 140)}…` : c.content}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{c.status === \"done\" && <span style={sidebarItemDoneStyle}>done</span>}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nconst MAX_ATTACHMENTS = 5;\nconst MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nconst readImageDimensions = (file: File): Promise<{ width: number; height: number } | null> =>\n\tnew Promise((resolve) => {\n\t\tconst url = URL.createObjectURL(file);\n\t\tconst img = new window.Image();\n\t\timg.onload = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve({ width: img.naturalWidth, height: img.naturalHeight });\n\t\t};\n\t\timg.onerror = () => {\n\t\t\tURL.revokeObjectURL(url);\n\t\t\tresolve(null);\n\t\t};\n\t\timg.src = url;\n\t});\n\nfunction EditPopover({\n\tinitial,\n\tinitialAttachments,\n\tfeedbackSessionId,\n\tapiBase,\n\tonCancel,\n\tonSave,\n\tonRemove,\n}: {\n\tinitial: string;\n\tinitialAttachments: FeedbackAttachment[];\n\tfeedbackSessionId: string;\n\tapiBase: string | null;\n\tonCancel: () => void;\n\tonSave: (content: string, attachments: FeedbackAttachment[]) => Promise<void>;\n\tonRemove?: () => Promise<void>;\n}) {\n\tconst [value, setValue] = useState(initial);\n\tconst [attachments, setAttachments] = useState<FeedbackAttachment[]>(initialAttachments);\n\tconst [saving, setSaving] = useState(false);\n\tconst [uploading, setUploading] = useState(false);\n\tconst [error, setError] = useState<string | null>(null);\n\tconst textareaRef = useRef<HTMLTextAreaElement | null>(null);\n\tconst fileInputRef = useRef<HTMLInputElement | null>(null);\n\n\tuseEffect(() => {\n\t\tconst el = textareaRef.current;\n\t\tif (!el) return;\n\t\tel.focus();\n\t\tconst len = el.value.length;\n\t\tel.setSelectionRange(len, len);\n\t}, []);\n\n\tconst handleSubmit = async (e: FormEvent) => {\n\t\te.preventDefault();\n\t\tconst content = value.trim();\n\t\tif (!content) return;\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tawait onSave(content, attachments);\n\t\t} finally {\n\t\t\tsetSaving(false);\n\t\t}\n\t};\n\n\tconst handleFiles = async (files: FileList | null) => {\n\t\tif (!files || files.length === 0 || !apiBase) return;\n\t\tsetError(null);\n\t\tconst slots = MAX_ATTACHMENTS - attachments.length;\n\t\tif (slots <= 0) {\n\t\t\tsetError(`Max ${MAX_ATTACHMENTS} images per comment`);\n\t\t\treturn;\n\t\t}\n\t\tconst picked = Array.from(files).slice(0, slots);\n\t\tfor (const f of picked) {\n\t\t\tif (!f.type.startsWith(\"image/\")) {\n\t\t\t\tsetError(`\"${f.name}\" is not an image`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (f.size > MAX_ATTACHMENT_BYTES) {\n\t\t\t\tsetError(`\"${f.name}\" exceeds 5 MB`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tsetUploading(true);\n\t\ttry {\n\t\t\tconst dims = await Promise.all(picked.map(readImageDimensions));\n\t\t\tconst fd = new FormData();\n\t\t\tpicked.forEach((file, i) => {\n\t\t\t\tfd.append(\"file\", file);\n\t\t\t\tfd.append(\"width\", dims[i]?.width ? String(dims[i]?.width) : \"\");\n\t\t\t\tfd.append(\"height\", dims[i]?.height ? String(dims[i]?.height) : \"\");\n\t\t\t});\n\t\t\tconst res = await fetch(\n\t\t\t\t`${apiBase}/api/feedback-comments/uploads?feedbackSessionId=${encodeURIComponent(feedbackSessionId)}`,\n\t\t\t\t{ method: \"POST\", credentials: \"include\", body: fd },\n\t\t\t);\n\t\t\tif (!res.ok) {\n\t\t\t\tconst body = (await res.json().catch(() => null)) as { error?: string } | null;\n\t\t\t\tsetError(body?.error ?? \"Upload failed\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst body = (await res.json()) as { uploads: FeedbackAttachment[] };\n\t\t\tsetAttachments((prev) => [...prev, ...body.uploads]);\n\t\t} catch {\n\t\t\tsetError(\"Upload failed\");\n\t\t} finally {\n\t\t\tsetUploading(false);\n\t\t\tif (fileInputRef.current) fileInputRef.current.value = \"\";\n\t\t}\n\t};\n\n\tconst removeAttachment = (url: string) => {\n\t\tsetAttachments((prev) => prev.filter((a) => a.url !== url));\n\t};\n\n\treturn (\n\t\t<form onSubmit={handleSubmit} style={popoverStyle}>\n\t\t\t<textarea\n\t\t\t\tref={textareaRef}\n\t\t\t\tvalue={value}\n\t\t\t\tonChange={(e) => setValue(e.target.value)}\n\t\t\t\tplaceholder=\"Leave a comment…\"\n\t\t\t\tstyle={textareaStyle}\n\t\t\t\trows={3}\n\t\t\t/>\n\t\t\t{attachments.length > 0 && (\n\t\t\t\t<div style={attachmentRowStyle}>\n\t\t\t\t\t{attachments.map((att) => (\n\t\t\t\t\t\t<div key={att.url} style={attachmentThumbWrapStyle}>\n\t\t\t\t\t\t\t<img src={att.url} alt=\"\" style={attachmentThumbStyle} />\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tonClick={() => removeAttachment(att.url)}\n\t\t\t\t\t\t\t\tstyle={attachmentRemoveStyle}\n\t\t\t\t\t\t\t\tdisabled={saving || uploading}\n\t\t\t\t\t\t\t\taria-label=\"Remove attachment\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{error && <div style={errorTextStyle}>{error}</div>}\n\t\t\t<input\n\t\t\t\tref={fileInputRef}\n\t\t\t\ttype=\"file\"\n\t\t\t\taccept=\"image/*\"\n\t\t\t\tmultiple\n\t\t\t\tonChange={(e) => void handleFiles(e.target.files)}\n\t\t\t\tstyle={{ display: \"none\" }}\n\t\t\t/>\n\t\t\t<div style={popoverActionsStyle}>\n\t\t\t\t{onRemove && (\n\t\t\t\t\t<button type=\"button\" onClick={() => onRemove()} style={dangerButtonStyle} disabled={saving}>\n\t\t\t\t\t\tDelete\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={() => fileInputRef.current?.click()}\n\t\t\t\t\tstyle={attachIconButtonStyle}\n\t\t\t\t\tdisabled={saving || uploading || attachments.length >= MAX_ATTACHMENTS}\n\t\t\t\t\taria-label={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t\ttitle={uploading ? \"Uploading…\" : \"Attach image\"}\n\t\t\t\t>\n\t\t\t\t\t{uploading ? <SpinnerGlyph /> : <PaperclipGlyph />}\n\t\t\t\t</button>\n\t\t\t\t<div style={{ flex: 1 }} />\n\t\t\t\t<button type=\"button\" onClick={onCancel} style={ghostButtonStyle} disabled={saving}>\n\t\t\t\t\tCancel\n\t\t\t\t</button>\n\t\t\t\t<button type=\"submit\" style={primaryButtonStyle} disabled={saving || uploading || !value.trim()}>\n\t\t\t\t\t{saving ? \"Saving…\" : \"Save\"}\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</form>\n\t);\n}\n\nfunction useTargetPosition(selector: string, offsetXRatio: number, offsetYRatio: number) {\n\tconst [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n\n\tuseEffect(() => {\n\t\tconst update = () => {\n\t\t\tlet el: Element | null = null;\n\t\t\ttry {\n\t\t\t\tel = document.querySelector(selector);\n\t\t\t} catch {\n\t\t\t\tel = null;\n\t\t\t}\n\t\t\tif (!el) {\n\t\t\t\tsetPos(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst r = el.getBoundingClientRect();\n\t\t\tsetPos({\n\t\t\t\ttop: r.top + window.scrollY + r.height * offsetYRatio - 12,\n\t\t\t\tleft: r.left + window.scrollX + r.width * offsetXRatio - 12,\n\t\t\t});\n\t\t};\n\n\t\tupdate();\n\t\tconst ro = new ResizeObserver(update);\n\t\ttry {\n\t\t\tconst el = document.querySelector(selector);\n\t\t\tif (el) ro.observe(el);\n\t\t} catch {\n\t\t\t// swallow invalid selector\n\t\t}\n\t\twindow.addEventListener(\"scroll\", update, true);\n\t\twindow.addEventListener(\"resize\", update);\n\t\treturn () => {\n\t\t\tro.disconnect();\n\t\t\twindow.removeEventListener(\"scroll\", update, true);\n\t\t\twindow.removeEventListener(\"resize\", update);\n\t\t};\n\t}, [selector, offsetXRatio, offsetYRatio]);\n\n\treturn pos;\n}\n\n// Inline styles — toolbar is injected into arbitrary stores, so we avoid relying\n// on any CSS framework being present.\nconst toolbarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n\tpadding: \"8px 12px\",\n\tbackground: \"rgba(17, 17, 17, 0.92)\",\n\tcolor: \"white\",\n\tborderRadius: 999,\n\tboxShadow: \"0 8px 24px rgba(0,0,0,0.25)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tfontSize: 14,\n\tpointerEvents: \"auto\",\n};\n\nconst toolbarButtonStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"white\",\n\tcolor: \"#111\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarButtonActiveStyle: CSSProperties = {\n\t...toolbarButtonStyle,\n\tbackground: \"#ef4444\",\n\tcolor: \"white\",\n};\n\nconst toolbarButtonGhostStyle: CSSProperties = {\n\tborder: \"1px solid rgba(255,255,255,0.4)\",\n\tbackground: \"transparent\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n\tfontSize: 13,\n};\n\nconst toolbarButtonFinalizeStyle: CSSProperties = {\n\tborder: \"none\",\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tpadding: \"6px 12px\",\n\tborderRadius: 999,\n\tcursor: \"pointer\",\n\tfontWeight: 600,\n\tfontSize: 13,\n};\n\nconst toolbarHintStyle: CSSProperties = {\n\topacity: 0.8,\n\tfontSize: 12,\n};\n\nconst pinDotStyle: CSSProperties = {\n\twidth: 24,\n\theight: 24,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tborder: \"2px solid white\",\n\tcursor: \"pointer\",\n\tfontSize: 12,\n\tfontWeight: 700,\n\tboxShadow: \"0 2px 6px rgba(0,0,0,0.3)\",\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tpadding: 0,\n};\n\nconst popoverStyle: CSSProperties = {\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 8,\n\tpadding: 12,\n\twidth: 280,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tcolor: \"#111\",\n};\n\nconst textareaStyle: CSSProperties = {\n\twidth: \"100%\",\n\tborder: \"1px solid #d1d5db\",\n\tborderRadius: 6,\n\tpadding: 8,\n\tfontSize: 14,\n\tresize: \"vertical\",\n\tfontFamily: \"inherit\",\n\tcolor: \"#111\",\n\tbackground: \"white\",\n\tboxSizing: \"border-box\",\n};\n\nconst popoverActionsStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 6,\n};\n\nconst baseButton: CSSProperties = {\n\tborder: \"1px solid transparent\",\n\tborderRadius: 6,\n\tpadding: \"6px 10px\",\n\tfontSize: 13,\n\tcursor: \"pointer\",\n\tfontWeight: 500,\n};\n\nconst primaryButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"#111\",\n\tcolor: \"white\",\n};\n\nconst ghostButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n};\n\nconst dangerButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#b91c1c\",\n\tborderColor: \"#fecaca\",\n};\n\nconst attachmentRowStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tflexWrap: \"wrap\",\n\tgap: 6,\n};\n\nconst attachmentThumbWrapStyle: CSSProperties = {\n\tposition: \"relative\",\n\twidth: 56,\n\theight: 56,\n\tborderRadius: 6,\n\toverflow: \"hidden\",\n\tborder: \"1px solid #e5e7eb\",\n\tbackground: \"#f9fafb\",\n};\n\nconst attachmentThumbStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: \"100%\",\n\tobjectFit: \"cover\",\n\tdisplay: \"block\",\n};\n\nconst attachmentRemoveStyle: CSSProperties = {\n\tposition: \"absolute\",\n\ttop: 2,\n\tright: 2,\n\twidth: 18,\n\theight: 18,\n\tborderRadius: 999,\n\tborder: \"none\",\n\tbackground: \"rgba(17, 17, 17, 0.85)\",\n\tcolor: \"white\",\n\tfontSize: 13,\n\tlineHeight: \"16px\",\n\tcursor: \"pointer\",\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst errorTextStyle: CSSProperties = {\n\tcolor: \"#b91c1c\",\n\tfontSize: 12,\n\tmargin: 0,\n};\n\nconst attachIconButtonStyle: CSSProperties = {\n\t...baseButton,\n\tbackground: \"transparent\",\n\tcolor: \"#374151\",\n\tborderColor: \"#d1d5db\",\n\twidth: 30,\n\theight: 30,\n\tpadding: 0,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n\tflexShrink: 0,\n};\n\nfunction PaperclipGlyph() {\n\treturn (\n\t\t<svg\n\t\t\trole=\"img\"\n\t\t\taria-label=\"Attach image\"\n\t\t\twidth=\"14\"\n\t\t\theight=\"14\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t>\n\t\t\t<title>Attach image</title>\n\t\t\t<path d=\"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.83l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48\" />\n\t\t</svg>\n\t);\n}\n\nfunction SpinnerGlyph() {\n\treturn (\n\t\t<>\n\t\t\t<style>{`@keyframes yns-attach-spin { to { transform: rotate(360deg); } }`}</style>\n\t\t\t<span\n\t\t\t\taria-hidden\n\t\t\t\tstyle={{\n\t\t\t\t\tdisplay: \"inline-block\",\n\t\t\t\t\twidth: 12,\n\t\t\t\t\theight: 12,\n\t\t\t\t\tborder: \"2px solid rgba(55, 65, 81, 0.25)\",\n\t\t\t\t\tborderTopColor: \"#374151\",\n\t\t\t\t\tborderRadius: 999,\n\t\t\t\t\tanimation: \"yns-attach-spin 0.9s linear infinite\",\n\t\t\t\t}}\n\t\t\t/>\n\t\t</>\n\t);\n}\n\nconst sidebarStyle: CSSProperties = {\n\tposition: \"fixed\",\n\ttop: 0,\n\tright: 0,\n\tbottom: 0,\n\twidth: 360,\n\tmaxWidth: \"92vw\",\n\tbackground: \"white\",\n\tborderLeft: \"1px solid #e5e7eb\",\n\tboxShadow: \"-12px 0 32px rgba(0,0,0,0.12)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n};\n\nconst sidebarHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"space-between\",\n\tpadding: \"12px 16px\",\n\tborderBottom: \"1px solid #e5e7eb\",\n};\n\nconst sidebarScrollStyle: CSSProperties = {\n\tflex: 1,\n\toverflow: \"auto\",\n\tpadding: \"12px 12px 24px\",\n};\n\nconst sidebarEmptyStyle: CSSProperties = {\n\tcolor: \"#6b7280\",\n\tfontSize: 13,\n\tpadding: \"24px 8px\",\n\ttextAlign: \"center\",\n};\n\nconst sidebarPathStyle: CSSProperties = {\n\tfontSize: 11,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n\tcolor: \"#6b7280\",\n\tpadding: \"4px 4px 6px\",\n\twordBreak: \"break-all\",\n};\n\nconst sidebarItemStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"flex-start\",\n\tgap: 8,\n\twidth: \"100%\",\n\ttextAlign: \"left\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 6,\n\tpadding: \"8px 10px\",\n\tcursor: \"pointer\",\n\tfontSize: 13,\n\tmarginBottom: 6,\n\tcolor: \"#111\",\n};\n\nconst sidebarItemIndexStyle: CSSProperties = {\n\tflexShrink: 0,\n\twidth: 22,\n\theight: 22,\n\tborderRadius: 999,\n\tbackground: \"#10b981\",\n\tcolor: \"white\",\n\tfontWeight: 700,\n\tfontSize: 11,\n\tdisplay: \"inline-flex\",\n\talignItems: \"center\",\n\tjustifyContent: \"center\",\n};\n\nconst sidebarItemTextStyle: CSSProperties = {\n\tflex: 1,\n\twhiteSpace: \"pre-wrap\",\n\twordBreak: \"break-word\",\n};\n\nconst sidebarItemDoneStyle: CSSProperties = {\n\tflexShrink: 0,\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 600,\n\tpadding: \"2px 6px\",\n\tborderRadius: 4,\n\talignSelf: \"center\",\n};\n\nconst submittedPanelStyle: CSSProperties = {\n\tposition: \"fixed\",\n\tbottom: 16,\n\tleft: \"50%\",\n\ttransform: \"translateX(-50%)\",\n\tzIndex: 2147483646,\n\tdisplay: \"flex\",\n\tflexDirection: \"column\",\n\tgap: 8,\n\tpadding: \"12px 16px\",\n\twidth: \"min(420px, calc(100vw - 32px))\",\n\tbackground: \"white\",\n\tborder: \"1px solid #e5e7eb\",\n\tborderRadius: 12,\n\tboxShadow: \"0 12px 32px rgba(0,0,0,0.18)\",\n\tfontFamily:\n\t\t'-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"',\n\tcolor: \"#111\",\n\tpointerEvents: \"auto\",\n};\n\nconst submittedHeaderStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\talignItems: \"center\",\n\tgap: 8,\n};\n\nconst submittedBadgeStyle: CSSProperties = {\n\tbackground: \"#d1fae5\",\n\tcolor: \"#065f46\",\n\tfontSize: 11,\n\tfontWeight: 700,\n\tpadding: \"2px 8px\",\n\tborderRadius: 999,\n\ttextTransform: \"uppercase\",\n\tletterSpacing: 0.5,\n};\n\nconst submittedProgressLabelStyle: CSSProperties = {\n\tdisplay: \"flex\",\n\tjustifyContent: \"space-between\",\n\tfontSize: 12,\n};\n\nconst submittedProgressTrackStyle: CSSProperties = {\n\twidth: \"100%\",\n\theight: 6,\n\tbackground: \"#f3f4f6\",\n\tborderRadius: 999,\n\toverflow: \"hidden\",\n};\n\nconst submittedProgressFillStyle: CSSProperties = {\n\theight: \"100%\",\n\tbackground: \"#10b981\",\n\tborderRadius: 999,\n\ttransition: \"width 500ms\",\n};\n\nconst submittedEtaStyle: CSSProperties = {\n\tmargin: 0,\n\tfontSize: 12,\n\tcolor: \"#6b7280\",\n};\n\nexport function mountFeedbackToolbar(): { unmount: () => void } | null {\n\tconsole.log(\"[YNS Feedback Toolbar] mountFeedbackToolbar() called\", {\n\t\tisWindow: typeof window !== \"undefined\",\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\talreadyMounted: typeof document !== \"undefined\" && Boolean(document.getElementById(MOUNT_NODE_ID)),\n\t});\n\tif (typeof window === \"undefined\") return null;\n\tif (process.env.NEXT_PUBLIC_VERCEL_ENV !== \"preview\") {\n\t\tconsole.log(\n\t\t\t\"[YNS Feedback Toolbar] gate failed — NEXT_PUBLIC_VERCEL_ENV is\",\n\t\t\tprocess.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\t);\n\t\treturn null;\n\t}\n\tif (document.getElementById(MOUNT_NODE_ID)) return null;\n\n\tconst container = document.createElement(\"div\");\n\tcontainer.id = MOUNT_NODE_ID;\n\tcontainer.dataset.ynsFeedbackUi = \"true\";\n\tdocument.body.appendChild(container);\n\n\tconst root = createRoot(container);\n\troot.render(<FeedbackToolbar />);\n\n\treturn {\n\t\tunmount: () => {\n\t\t\troot.unmount();\n\t\t\tcontainer.remove();\n\t\t},\n\t};\n}\n\n// Auto-mount on import. Wait for DOM ready.\nif (typeof window !== \"undefined\") {\n\tconsole.log(\"[YNS Feedback Toolbar] module evaluated\", {\n\t\tvercelEnv: process.env.NEXT_PUBLIC_VERCEL_ENV,\n\t\treadyState: document.readyState,\n\t\twillAutoMount: process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\",\n\t});\n}\nif (typeof window !== \"undefined\" && process.env.NEXT_PUBLIC_VERCEL_ENV === \"preview\") {\n\tif (document.readyState === \"loading\") {\n\t\tdocument.addEventListener(\"DOMContentLoaded\", () => {\n\t\t\tmountFeedbackToolbar();\n\t\t});\n\t} else {\n\t\tmountFeedbackToolbar();\n\t}\n}\n"],"mappings":"AAcA,OAA6C,aAAAA,EAAW,UAAAC,EAAQ,YAAAC,MAAgB,QAChF,OAAS,cAAAC,MAAkB,mBAsfvB,OA4MF,YAAAC,EA5ME,OAAAC,EAgDD,QAAAC,MAhDC,oBApfJ,IAAMC,EAAgB,4BAiDhBC,EAAe,IAAqB,CACzC,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,IAAMC,GAAY,QAAQ,IAAI,0BAA4B,IAAI,KAAK,EACnE,GAAIA,EAAU,OAAOA,EAAS,QAAQ,MAAO,EAAE,EAM/C,IAAMC,EAAO,OAAO,SAAS,SACvBC,EAAW,OAAO,SAAS,SACjC,OAAID,EAAK,SAAS,YAAY,EAAU,GAAGC,CAAQ,cAC/CD,EAAK,SAAS,SAAS,EAAU,GAAGC,CAAQ,WACzC,OAAO,SAAS,MACxB,EAEMC,EAAsBC,GAAwB,CACnD,GAAIA,EAAG,GAAI,MAAO,IAAI,IAAI,OAAOA,EAAG,EAAE,CAAC,GACvC,IAAMC,EAAiB,CAAC,EACpBC,EAAuBF,EAC3B,KAAOE,GAAQA,EAAK,WAAa,KAAK,cAAgBD,EAAK,OAAS,GAAG,CACtE,IAAIE,EAAOD,EAAK,QAAQ,YAAY,EAC9BE,EAAYF,EAAK,aAAa,OAAO,EAC3C,GAAIE,EAAW,CACd,IAAMC,EAAUD,EACd,MAAM,KAAK,EACX,OAAO,OAAO,EACd,MAAM,EAAG,CAAC,EACV,IAAKE,GAAM,IAAI,IAAI,OAAOA,CAAC,CAAC,EAAE,EAC9B,KAAK,EAAE,EACTH,GAAQE,CACT,CACA,IAAME,EAAyBL,EAAK,cAC9BM,EAAMN,EAAK,QACjB,GAAIK,EAAQ,CACX,IAAME,EAAsB,MAAM,KAAKF,EAAO,QAAQ,EAAE,OAAQD,GAAMA,EAAE,UAAYE,CAAG,EACvF,GAAIC,EAAS,OAAS,EAAG,CACxB,IAAMC,EAAMD,EAAS,QAAQP,CAAI,EAAI,EACrCC,GAAQ,gBAAgBO,CAAG,GAC5B,CACD,CACAT,EAAK,QAAQE,CAAI,EACjBD,EAAOK,CACR,CACA,OAAON,EAAK,KAAK,KAAK,CACvB,EAEMU,EAAoB,CAAC,KAAM,QAAS,cAAe,aAAc,OAAQ,OAAQ,OAAQ,KAAK,EAE9FC,EAAeZ,GAAwB,CAC5C,IAAMa,EAAkB,CAAC,EACzB,QAAWC,KAAQH,EAAmB,CACrC,IAAMI,EAAIf,EAAG,aAAac,CAAI,EAC9B,GAAI,CAACC,EAAG,SACR,IAAMC,EAAUD,EAAE,OAAS,GAAK,GAAGA,EAAE,MAAM,EAAG,EAAE,CAAC,SAAMA,EACvDF,EAAM,KAAK,IAAIC,CAAI,KAAKE,EAAQ,QAAQ,KAAM,QAAQ,CAAC,GAAG,CAC3D,CACA,OAAOH,EAAM,KAAK,EAAE,CACrB,EAEMI,EAAqBjB,GAAwB,CAClD,IAAMkB,GAAQlB,EAAG,aAAe,IAAI,QAAQ,OAAQ,GAAG,EAAE,KAAK,EAC9D,OAAKkB,EACEA,EAAK,OAAS,IAAM,GAAGA,EAAK,MAAM,EAAG,GAAG,CAAC,SAAMA,EADpC,EAEnB,EAEMC,EAAwBC,GAA4B,CACzD,IAAMC,EAAuB,CAAC,EAC1BC,EAAsBF,EAC1B,KAAOE,GAAOA,IAAQ,SAAS,iBAAmBD,EAAU,OAAS,GAAG,CACvE,IAAMd,EAAyBe,EAAI,cACnC,GAAI,CAACf,EAAQ,MACbc,EAAU,QAAQd,CAAM,EACxBe,EAAMf,CACP,CAEA,IAAMgB,EAAkB,CAAC,EACrBC,EAAQ,EACZ,QAAWC,KAAYJ,EAAW,CACjC,IAAMK,EAAS,KAAK,OAAOF,CAAK,EAChCD,EAAM,KAAK,GAAGG,CAAM,IAAID,EAAS,QAAQ,YAAY,CAAC,GAAGb,EAAYa,CAAQ,CAAC,GAAG,EACjFD,GACD,CAEA,IAAMG,EAAe,KAAK,OAAOH,CAAK,EAChCI,EAAYR,EAAO,QAAQ,YAAY,EACvCS,EAAaZ,EAAkBG,CAAM,EAO3C,GANAG,EAAM,KACL,GAAGI,CAAY,IAAIC,CAAS,GAAGhB,EAAYQ,CAAM,CAAC,IACjDS,EAAa,GAAGA,CAAU,KAAKD,CAAS,IAAM,EAC/C,iBACD,EAEI,CAACC,EAAY,CAChB,IAAMC,EAAc,KAAK,OAAON,EAAQ,CAAC,EACnCO,EAAW,MAAM,KAAKX,EAAO,QAAQ,EAAE,MAAM,EAAG,CAAC,EACvD,QAAWY,KAASD,EAAU,CAC7B,IAAME,EAAYhB,EAAkBe,CAAK,EACzCT,EAAM,KACL,GAAGO,CAAW,IAAIE,EAAM,QAAQ,YAAY,CAAC,GAAGpB,EAAYoB,CAAK,CAAC,IACjEC,EAAY,GAAGA,CAAS,KAAKD,EAAM,QAAQ,YAAY,CAAC,IAAM,EAC/D,EACD,CACD,CACIZ,EAAO,SAAS,OAAS,GAC5BG,EAAM,KAAK,GAAGO,CAAW,WAAMV,EAAO,SAAS,OAAS,CAAC,iBAAiB,EAE3EG,EAAM,KAAK,GAAGI,CAAY,KAAKC,CAAS,GAAG,CAC5C,CAEA,QAASM,EAAIb,EAAU,OAAS,EAAGa,GAAK,EAAGA,IAAK,CAC/C,IAAMR,EAAS,KAAK,OAAOQ,CAAC,EACtBT,EAAWJ,EAAUa,CAAC,EACvBT,GACLF,EAAM,KAAK,GAAGG,CAAM,KAAKD,EAAS,QAAQ,YAAY,CAAC,GAAG,CAC3D,CAEA,OAAOF,EAAM,KAAK;AAAA,CAAI,CACvB,EAEMY,EAAmBnC,GAAgC,CACxD,IAAIE,EAAOF,EACX,KAAOE,GAAM,CACZ,GAAIA,aAAgB,aAAeA,EAAK,QAAQ,gBAAkB,OAAQ,MAAO,GACjFA,EAAOA,EAAK,aACb,CACA,MAAO,EACR,EAEMkC,EAAe,IAAI,IAAI,CAAC,OAAQ,OAAQ,SAAU,QAAS,UAAU,CAAC,EAEtEC,GAAmB,IAKnBC,GAAaC,GAA2B,CAC7C,IAAMC,EAAM,IAAI,KAAKD,CAAM,EACrBE,EAAcD,EAAI,QAAQ,EAAI,KAAK,IAAI,EACvCE,EAAYF,EAAI,eAAe,OAAW,CAC/C,QAAS,QACT,MAAO,QACP,IAAK,UACL,KAAM,UACN,OAAQ,SACT,CAAC,EAED,GAAIC,GAAe,EAAG,MAAO,gCAAgCC,CAAS,IACtE,IAAMC,EAAe,KAAK,KAAKF,EAAc,GAAM,EACnD,GAAIE,EAAe,GAAI,MAAO,IAAIA,CAAY,gBAAgBD,CAAS,IACvE,IAAME,EAAiB,KAAK,MAAMH,EAAc,IAAS,EACzD,GAAIG,EAAiB,GACpB,MAAO,IAAIA,CAAc,IAAIA,IAAmB,EAAI,OAAS,OAAO,QAAQF,CAAS,IAEtF,IAAMG,EAAgB,KAAK,MAAMJ,EAAc,KAAU,EACzD,MAAO,IAAII,CAAa,IAAIA,IAAkB,EAAI,MAAQ,MAAM,QAAQH,CAAS,GAClF,EAEA,SAASI,IAAkB,CAC1B,GAAM,CAACC,EAASC,CAAU,EAAI3D,EAA+B,IAAI,EAC3D,CAAC4D,EAASC,CAAU,EAAI7D,EAAS,EAAI,EACrC,CAAC8D,EAASC,CAAU,EAAI/D,EAAS,EAAK,EACtC,CAACgE,EAASC,CAAU,EAAIjE,EAA4B,IAAI,EACxD,CAACkE,EAAWC,CAAY,EAAInE,EAAwB,IAAI,EACxD,CAACoE,EAAaC,CAAc,EAAIrE,EAAS,EAAK,EAC9C,CAACsE,EAAYC,CAAa,EAAIvE,EAAS,EAAK,EAC5CwE,EAAUzE,EAAsB,IAAI,EAE1CD,EAAU,IAAM,CAEf,GADA0E,EAAQ,QAAUlE,EAAa,EAC3B,CAACkE,EAAQ,QAAS,CACrBX,EAAW,EAAK,EAChB,MACD,CAEA,IAAIY,EAAY,GACVC,EAAa,IAAI,gBAEjBC,EAAY,SAAY,CAC7B,GAAI,CACH,IAAMC,EAAM,MAAM,MACjB,GAAGJ,EAAQ,OAAO,+BAA+B,mBAAmB,OAAO,SAAS,IAAI,CAAC,GACzF,CAAE,YAAa,UAAW,OAAQE,EAAW,MAAO,CACrD,EACA,GAAID,EAAW,OACf,GAAI,CAACG,EAAI,GAAI,CACZjB,EAAW,IAAI,EACf,MACD,CACA,IAAMkB,EAAQ,MAAMD,EAAI,KAAK,EAC7B,GAAIH,EAAW,OACfd,EAAWkB,CAAI,EAIXA,EAAK,gBAAkB,QAC1B,OAAO,SAAS,OAAO,CAEzB,MAAQ,CACFJ,GAAWd,EAAW,IAAI,CAChC,QAAE,CACIc,GAAWZ,EAAW,EAAK,CACjC,CACD,EAEKc,EAAU,EACf,IAAMG,EAAW,OAAO,YAAYH,EAAW3B,EAAgB,EAE/D,MAAO,IAAM,CACZyB,EAAY,GACZC,EAAW,MAAM,EACjB,OAAO,cAAcI,CAAQ,CAC9B,CACD,EAAG,CAAC,CAAC,EAKLhF,EAAU,IAAM,CACX,CAAC4D,GAAWA,EAAQ,aACxBK,EAAW,EAAK,EAChBE,EAAW,IAAI,EACfI,EAAe,EAAK,EACpBF,EAAa,IAAI,EAClB,EAAG,CAACT,CAAO,CAAC,EAEZ5D,EAAU,IAAM,CACf,GAAKgE,EACL,gBAAS,KAAK,MAAM,OAAS,YACtB,IAAM,CACZ,SAAS,KAAK,MAAM,OAAS,EAC9B,CACD,EAAG,CAACA,CAAO,CAAC,EAEZhE,EAAU,IAAM,CACf,GAAI,CAACgE,EAAS,OAEd,IAAMiB,EAAU,SAAS,cAAc,KAAK,EAC5CA,EAAQ,QAAQ,cAAgB,OAChCA,EAAQ,MAAM,QAAU,CACvB,kBACA,uBACA,sBACA,6BACA,uCACA,qBACA,gBACA,8DACD,EAAE,KAAK,GAAG,EAEV,IAAMC,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,QAAQ,cAAgB,OAC9BA,EAAM,YAAc,mBACpBA,EAAM,MAAM,QAAU,CACrB,kBACA,uBACA,sBACA,sBACA,cACA,kBACA,oDACA,mBACA,qBACA,sBACA,gBACA,wCACD,EAAE,KAAK,GAAG,EAEV,SAAS,gBAAgB,YAAYD,CAAO,EAC5C,SAAS,gBAAgB,YAAYC,CAAK,EAE1C,IAAIC,EAA0B,KACxBC,EAAcC,GAAkB,CACrC,IAAMxE,EAAK,SAAS,iBAAiBwE,EAAE,QAASA,EAAE,OAAO,EACzD,GAAI,CAACxE,GAAMoC,EAAa,IAAIpC,EAAG,OAAO,GAAKmC,EAAgBnC,CAAE,EAAG,CAC/DoE,EAAQ,MAAM,QAAU,OACxBC,EAAM,MAAM,QAAU,OACtBC,EAAU,KACV,MACD,CACAA,EAAUtE,EACV,sBAAsB,IAAM,CAC3B,GAAIsE,IAAYtE,EAAI,OACpB,IAAMyE,EAAOzE,EAAG,sBAAsB,EACtCoE,EAAQ,MAAM,IAAM,GAAGK,EAAK,GAAG,KAC/BL,EAAQ,MAAM,KAAO,GAAGK,EAAK,IAAI,KACjCL,EAAQ,MAAM,MAAQ,GAAGK,EAAK,KAAK,KACnCL,EAAQ,MAAM,OAAS,GAAGK,EAAK,MAAM,KACrCL,EAAQ,MAAM,QAAU,QACxBC,EAAM,MAAM,KAAO,GAAGG,EAAE,QAAU,EAAE,KACpCH,EAAM,MAAM,IAAM,GAAGG,EAAE,QAAU,EAAE,KACnCH,EAAM,MAAM,QAAU,OACvB,CAAC,CACF,EAEMK,EAAc,IAAM,CACzBN,EAAQ,MAAM,QAAU,OACxBC,EAAM,MAAM,QAAU,OACtBC,EAAU,IACX,EAEA,gBAAS,iBAAiB,YAAaC,EAAY,EAAI,EACvD,SAAS,iBAAiB,aAAcG,CAAW,EAE5C,IAAM,CACZ,SAAS,oBAAoB,YAAaH,EAAY,EAAI,EAC1D,SAAS,oBAAoB,aAAcG,CAAW,EACtDN,EAAQ,OAAO,EACfC,EAAM,OAAO,CACd,CACD,EAAG,CAAClB,CAAO,CAAC,EAEZhE,EAAU,IAAM,CACf,GAAI,CAACgE,EAAS,OACd,IAAMwB,EAAeC,GAAsB,CAC1C,IAAMxD,EAASwD,EAAM,OAErB,GADI,EAAExD,aAAkB,UACpBe,EAAgBf,CAAM,EAAG,OAE7BwD,EAAM,eAAe,EACrBA,EAAM,gBAAgB,EAEtB,IAAMH,EAAOrD,EAAO,sBAAsB,EACpCyD,EAAeJ,EAAK,MAAQ,GAAKG,EAAM,QAAUH,EAAK,MAAQA,EAAK,MAAQ,GAC3EK,EAAeL,EAAK,OAAS,GAAKG,EAAM,QAAUH,EAAK,KAAOA,EAAK,OAAS,GAClFnB,EAAW,CACV,YAAavD,EAAmBqB,CAAM,EACtC,SAAU,OAAO,SAAS,SAC1B,gBAAiBD,EAAqBC,CAAM,EAC5C,KAAM,CACL,IAAKqD,EAAK,IAAM,OAAO,QACvB,KAAMA,EAAK,KAAO,OAAO,QACzB,MAAOA,EAAK,MACZ,OAAQA,EAAK,MACd,EACA,OAAQG,EAAM,QAAU,OAAO,QAC/B,OAAQA,EAAM,QAAU,OAAO,QAC/B,aAAc,KAAK,IAAI,EAAG,KAAK,IAAI,EAAGC,CAAY,CAAC,EACnD,aAAc,KAAK,IAAI,EAAG,KAAK,IAAI,EAAGC,CAAY,CAAC,CACpD,CAAC,EACD1B,EAAW,EAAK,CACjB,EACA,gBAAS,iBAAiB,QAASuB,EAAa,CAAE,QAAS,EAAK,CAAC,EAC1D,IAAM,SAAS,oBAAoB,QAASA,EAAa,CAAE,QAAS,EAAK,CAAC,CAClF,EAAG,CAACxB,CAAO,CAAC,EAEZ,IAAM4B,EAAkB,SAAY,CACnC,GAAI,CAAClB,EAAQ,QAAS,OACtB,IAAMI,EAAM,MAAM,MACjB,GAAGJ,EAAQ,OAAO,+BAA+B,mBAAmB,OAAO,SAAS,IAAI,CAAC,GACzF,CAAE,YAAa,SAAU,CAC1B,EACA,GAAI,CAACI,EAAI,GAAI,OACb,IAAMC,EAAQ,MAAMD,EAAI,KAAK,EAC7BjB,EAAWkB,CAAI,CAChB,EAEMc,EAAmB,MAAOC,EAAiBC,IAAsC,CAClF,CAACrB,EAAQ,SAAW,CAACd,GAAW,CAACM,GAgBjC,EAfQ,MAAM,MAAM,GAAGQ,EAAQ,OAAO,yBAA0B,CACnE,OAAQ,OACR,YAAa,UACb,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CACpB,kBAAmBd,EAAQ,kBAC3B,QAAAkC,EACA,SAAU5B,EAAQ,SAClB,YAAaA,EAAQ,YACrB,gBAAiBA,EAAQ,gBACzB,aAAcA,EAAQ,aACtB,aAAcA,EAAQ,aACtB,GAAI6B,EAAY,OAAS,EAAI,CAAE,YAAAA,CAAY,EAAI,CAAC,CACjD,CAAC,CACF,CAAC,GACQ,KACT5B,EAAW,IAAI,EACfF,EAAW,EAAI,EACf,MAAM2B,EAAgB,EACvB,EAEMI,EAAgB,MAAOC,EAAYH,EAAiBC,IAAsC,CAC3F,CAACrB,EAAQ,SAOT,EANQ,MAAM,MAAM,GAAGA,EAAQ,OAAO,0BAA0BuB,CAAE,GAAI,CACzE,OAAQ,QACR,YAAa,UACb,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CAAE,QAAAH,EAAS,YAAAC,CAAY,CAAC,CAC9C,CAAC,GACQ,KACT1B,EAAa,IAAI,EACjB,MAAMuB,EAAgB,EACvB,EAEMM,EAAgB,MAAOD,GAAe,CACvC,CAACvB,EAAQ,SAKT,EAJQ,MAAM,MAAM,GAAGA,EAAQ,OAAO,0BAA0BuB,CAAE,GAAI,CACzE,OAAQ,SACR,YAAa,SACd,CAAC,GACQ,IACT,MAAML,EAAgB,CACvB,EAEMO,EAAkB,SAAY,CACnC,GAAI,GAACzB,EAAQ,SAAW,CAACd,IACpB,OAAO,QAAQ,yEAAyE,EAC7F,CAAAa,EAAc,EAAI,EAClB,GAAI,CACH,IAAMK,EAAM,MAAM,MACjB,GAAGJ,EAAQ,OAAO,0BAA0Bd,EAAQ,iBAAiB,YACrE,CAAE,OAAQ,OAAQ,YAAa,SAAU,CAC1C,EACA,GAAI,CAACkB,EAAI,GAAI,OAMb,IAAMsB,GADQ,MAAMtB,EAAI,KAAK,EAAE,MAAM,IAAM,IAAI,IACP,QAAU,aAClDjB,EAAYwC,GAAUA,GAAO,CAAE,GAAGA,EAAM,WAAY,GAAO,cAAeD,CAAW,CAAS,CAC/F,QAAE,CACD3B,EAAc,EAAK,CACpB,EACD,EAEM6B,EAAmBC,GAA6B,CACrD,GAAIA,EAAQ,WAAa,OAAO,SAAS,SAAU,CAClD,OAAO,SAAS,OAAOA,EAAQ,QAAQ,EACvC,MACD,CACA,IAAI1F,EAAqB,KACzB,GAAI,CACHA,EAAK,SAAS,cAAc0F,EAAQ,WAAW,CAChD,MAAQ,CACP1F,EAAK,IACN,CACKA,IACLA,EAAG,eAAe,CAAE,SAAU,SAAU,MAAO,QAAS,CAAC,EACzDwD,EAAakC,EAAQ,EAAE,EACxB,EAEA,GAAIzC,GAAW,CAACF,EAAS,OAAO,KAMhC,GAAI,CAACA,EAAQ,WACZ,OAAIA,EAAQ,gBAAkB,cAAgBA,EAAQ,gBAAkB,YAAoB,KAE3FvD,EAAC,OAAI,uBAAqB,OACzB,SAAAA,EAACmG,GAAA,CAAe,SAAU5C,EAAQ,SAAU,IAAKA,EAAQ,IAAK,OAAQA,EAAQ,cAAe,EAC9F,EAIF,IAAM6C,EAAc7C,EAAQ,SAAS,OACnCzC,GAAMA,EAAE,WAAa,OAAO,SAAS,UAAYA,EAAE,SAAW,MAChE,EAEA,OACCb,EAAC,OAAI,uBAAqB,OACxB,UAAAmG,EAAY,IAAI,CAACC,EAAKnF,IACtBlB,EAACsG,GAAA,CAEA,IAAKD,EACL,OAAQnF,EAAM,EACd,QAAS6C,IAAcsC,EAAI,GAC3B,kBAAmB9C,EAAQ,kBAC3B,QAASc,EAAQ,QACjB,YAAa,IAAML,EAAaqC,EAAI,EAAE,EACtC,aAAc,IAAMrC,EAAa,IAAI,EACrC,OAAQ,CAACyB,EAASC,IAAgBC,EAAcU,EAAI,GAAIZ,EAASC,CAAW,EAC5E,SAAU,IAAMG,EAAcQ,EAAI,EAAE,GAT/BA,EAAI,EAUV,CACA,EAEAxC,GACA7D,EAACuG,GAAA,CACA,QAAS1C,EACT,kBAAmBN,EAAQ,kBAC3B,QAASc,EAAQ,QACjB,SAAU,IAAM,CACfP,EAAW,IAAI,EACfF,EAAW,EAAI,CAChB,EACA,OAAQ,CAAC6B,EAASC,IAAgBF,EAAiBC,EAASC,CAAW,EACxE,EAGAzB,GACAjE,EAACwG,GAAA,CACA,SAAUjD,EAAQ,SAClB,YAAa,OAAO,SAAS,SAC7B,QAAS,IAAMW,EAAe,EAAK,EACnC,SAAU+B,EACX,EAGDhG,EAAC,OAAI,MAAOwG,GACX,UAAAzG,EAAC,UACA,KAAK,SACL,QAAS,IAAM4D,EAAYrC,GAAM,CAACA,CAAC,EACnC,MAAOoC,EAAU+C,GAA2BC,EAE3C,SAAAhD,EAAU,SAAW,cACvB,EACA3D,EAAC,UAAO,KAAK,SAAS,QAAS,IAAMkE,EAAgB3C,GAAM,CAACA,CAAC,EAAG,MAAOqF,GACrE,SAAA3C,EAAc,YAAc,SAASV,EAAQ,SAAS,MAAM,IAC9D,EACAvD,EAAC,UACA,KAAK,SACL,QAAS8F,EACT,SAAU3B,EACV,MAAO0C,GAEN,SAAA1C,EAAa,mBAAgB,WAC/B,EACAnE,EAAC,QAAK,MAAO8G,GACX,SAAAnD,EAAU,+BAAiC,GAAGyC,EAAY,MAAM,gBAClE,GACD,GACD,CAEF,CAEA,SAASD,GAAe,CACvB,SAAAY,EACA,IAAKhE,EACL,OAAAiE,CACD,EAIG,CAGF,GAAM,CAAC,CAAEC,CAAO,EAAIpH,EAAS,CAAC,EAC9BF,EAAU,IAAM,CACf,IAAMiG,EAAK,OAAO,YAAY,IAAMqB,EAASC,GAAMA,EAAI,CAAC,EAAG,GAAM,EACjE,MAAO,IAAM,OAAO,cAActB,CAAE,CACrC,EAAG,CAAC,CAAC,EAEL,IAAM5C,EAAMF,GAAUC,CAAM,EAI5B,OACC9C,EAAC,OAAI,MAAOkH,GACX,UAAAnH,EAAC,SAAO,SAAAoH,GAAiB,EACzBnH,EAAC,OAAI,MAAOoH,GACX,UAAArH,EAACsH,GAAA,EAAQ,EACTtH,EAAC,QAAK,MAAOuH,GAAqB,qBAAS,EAC3CvH,EAAC,UAAO,MAAO,CAAE,SAAU,EAAG,EAAI,SATlBgH,IAAW,YACA,wBAA0B,oBAQV,GAC5C,EACA/G,EAAC,OAAI,MAAOuH,GACX,UAAAxH,EAAC,QAAK,2BAAe,EACrBA,EAAC,QAAK,MAAO,CAAE,QAAS,EAAI,EAAI,SAAA+G,EAAS,MAAM,GAChD,EACA/G,EAAC,OAAI,MAAOyH,GACX,SAAAzH,EAAC,OAAI,MAAO,CAAE,GAAG0H,GAA4B,MAAO,GAAGX,EAAS,OAAO,GAAI,EAAG,EAC/E,EACA9G,EAAC,KAAE,MAAO0H,GAAmB,iCACR3H,EAAC,QAAK,MAAO,CAAE,WAAY,GAAI,EAAI,SAAAgD,EAAI,GAC5D,GACD,CAEF,CAGA,IAAMoE,GAAmB,qEAEzB,SAASE,GAAQ,CAAE,KAAAM,EAAO,EAAG,EAAsB,CAClD,OACC5H,EAAC,QACA,cAAW,GACX,MAAO,CACN,QAAS,eACT,MAAO4H,EACP,OAAQA,EACR,OAAQ,qCACR,eAAgB,UAChB,aAAc,IACd,UAAW,wCACZ,EACD,CAEF,CAEA,SAAStB,GAAW,CACnB,IAAAD,EACA,OAAAwB,EACA,QAAAC,EACA,kBAAAC,EACA,QAAA1D,EACA,YAAA2D,EACA,aAAAC,EACA,OAAAC,EACA,SAAAC,CACD,EAUG,CACF,IAAMvG,EAASwG,GAAkB/B,EAAI,YAAaA,EAAI,aAAcA,EAAI,YAAY,EACpF,OAAKzE,EAGJ3B,EAAC,OACA,MAAO,CACN,SAAU,WACV,IAAK2B,EAAO,IACZ,KAAMA,EAAO,KACb,OAAQ,WACR,cAAe,MAChB,EAEA,UAAA5B,EAAC,UAAO,KAAK,SAAS,QAASgI,EAAa,MAAOK,EAAa,MAAOhC,EAAI,QACzE,SAAAwB,EACF,EACCC,GACA9H,EAACsI,EAAA,CACA,QAASjC,EAAI,QACb,mBAAoBA,EAAI,YACxB,kBAAmB0B,EACnB,QAAS1D,EACT,SAAU4D,EACV,OAAQC,EACR,SAAUC,EACX,GAEF,EA1BmB,IA4BrB,CAEA,SAAS5B,GAAsB,CAC9B,QAAA1C,EACA,kBAAAkE,EACA,QAAA1D,EACA,SAAAkE,EACA,OAAAL,CACD,EAMG,CACF,OACCjI,EAAAF,EAAA,CACC,UAAAC,EAAC,OACA,MAAO,CACN,SAAU,WACV,IAAK6D,EAAQ,KAAK,IAAMA,EAAQ,KAAK,OAASA,EAAQ,aAAe,GACrE,KAAMA,EAAQ,KAAK,KAAOA,EAAQ,KAAK,MAAQA,EAAQ,aAAe,GACtE,OAAQ,WACR,cAAe,MAChB,EAEA,SAAA7D,EAAC,OAAI,MAAOqI,EAAa,kBAAC,EAC3B,EACArI,EAAC,OACA,MAAO,CACN,SAAU,WACV,IAAK6D,EAAQ,OAAS,GACtB,KAAMA,EAAQ,OAAS,GACvB,OAAQ,UACT,EAEA,SAAA7D,EAACsI,EAAA,CACA,QAAQ,GACR,mBAAoB,CAAC,EACrB,kBAAmBP,EACnB,QAAS1D,EACT,SAAUkE,EACV,OAAQL,EACT,EACD,GACD,CAEF,CAEA,SAAS1B,GAAgB,CACxB,SAAAgC,EACA,YAAAC,EACA,QAAAC,EACA,SAAAC,CACD,EAKG,CACF,IAAMC,EAAS,IAAI,IACnB,QAAW9H,KAAK0H,EAAU,CACzB,IAAMK,EAAOD,EAAO,IAAI9H,EAAE,QAAQ,GAAK,CAAC,EACxC+H,EAAK,KAAK/H,CAAC,EACX8H,EAAO,IAAI9H,EAAE,SAAU+H,CAAI,CAC5B,CACA,IAAMC,EAAQ,MAAM,KAAKF,EAAO,KAAK,CAAC,EAAE,KAAK,CAACG,EAAGC,IAC5CD,IAAMN,EAAoB,GAC1BO,IAAMP,EAAoB,EACvBM,EAAE,cAAcC,CAAC,CACxB,EAED,OACC/I,EAAC,OAAI,MAAOgJ,GACX,UAAAhJ,EAAC,OAAI,MAAOiJ,GACX,UAAAjJ,EAAC,UAAO,MAAO,CAAE,SAAU,EAAG,EAAG,uBAAWuI,EAAS,OAAO,KAAC,EAC7DxI,EAAC,UAAO,KAAK,SAAS,QAAS0I,EAAS,MAAOS,EAAkB,iBAEjE,GACD,EACAnJ,EAAC,OAAI,MAAOoJ,GACV,SAAAZ,EAAS,SAAW,EACpBxI,EAAC,OAAI,MAAOqJ,GAAmB,wEAAkD,EAEjFP,EAAM,IAAKrI,GACVR,EAAC,OAAe,MAAO,CAAE,aAAc,EAAG,EACzC,UAAAD,EAAC,OAAI,MAAOsJ,GAAmB,SAAA7I,IAASgI,EAAc,GAAGhI,CAAI,gBAAeA,EAAK,GAC/EmI,EAAO,IAAInI,CAAI,GAAK,CAAC,GAAG,IAAI,CAACK,EAAGI,IACjCjB,EAAC,UAEA,KAAK,SACL,QAAS,IAAM0I,EAAS7H,CAAC,EACzB,MAAOyI,GACP,SAAUzI,EAAE,SAAW,QAAU,GAEjC,UAAAd,EAAC,QAAK,MAAOwJ,GAAwB,SAAAtI,EAAM,EAAE,EAC7ClB,EAAC,QAAK,MAAOyJ,GACX,SAAA3I,EAAE,QAAQ,OAAS,IAAM,GAAGA,EAAE,QAAQ,MAAM,EAAG,GAAG,CAAC,SAAMA,EAAE,QAC7D,EACCA,EAAE,SAAW,QAAUd,EAAC,QAAK,MAAO0J,GAAsB,gBAAI,IAV1D5I,EAAE,EAWR,CACA,IAhBQL,CAiBV,CACA,EAEH,GACD,CAEF,CAEA,IAAMkJ,EAAkB,EAClBC,GAAuB,EAAI,KAAO,KAElCC,GAAuBC,GAC5B,IAAI,QAASC,GAAY,CACxB,IAAMC,EAAM,IAAI,gBAAgBF,CAAI,EAC9BG,EAAM,IAAI,OAAO,MACvBA,EAAI,OAAS,IAAM,CAClB,IAAI,gBAAgBD,CAAG,EACvBD,EAAQ,CAAE,MAAOE,EAAI,aAAc,OAAQA,EAAI,aAAc,CAAC,CAC/D,EACAA,EAAI,QAAU,IAAM,CACnB,IAAI,gBAAgBD,CAAG,EACvBD,EAAQ,IAAI,CACb,EACAE,EAAI,IAAMD,CACX,CAAC,EAEF,SAAS1B,EAAY,CACpB,QAAA4B,EACA,mBAAAC,EACA,kBAAApC,EACA,QAAA1D,EACA,SAAAkE,EACA,OAAAL,EACA,SAAAC,CACD,EAQG,CACF,GAAM,CAACiC,EAAOC,CAAQ,EAAIxK,EAASqK,CAAO,EACpC,CAACxE,EAAa4E,CAAc,EAAIzK,EAA+BsK,CAAkB,EACjF,CAACI,EAAQC,CAAS,EAAI3K,EAAS,EAAK,EACpC,CAAC4K,EAAWC,CAAY,EAAI7K,EAAS,EAAK,EAC1C,CAAC8K,EAAOC,CAAQ,EAAI/K,EAAwB,IAAI,EAChDgL,EAAcjL,EAAmC,IAAI,EACrDkL,EAAelL,EAAgC,IAAI,EAEzDD,EAAU,IAAM,CACf,IAAMa,EAAKqK,EAAY,QACvB,GAAI,CAACrK,EAAI,OACTA,EAAG,MAAM,EACT,IAAMuK,EAAMvK,EAAG,MAAM,OACrBA,EAAG,kBAAkBuK,EAAKA,CAAG,CAC9B,EAAG,CAAC,CAAC,EAEL,IAAMC,EAAe,MAAOhG,GAAiB,CAC5CA,EAAE,eAAe,EACjB,IAAMS,EAAU2E,EAAM,KAAK,EAC3B,GAAK3E,EACL,CAAA+E,EAAU,EAAI,EACd,GAAI,CACH,MAAMtC,EAAOzC,EAASC,CAAW,CAClC,QAAE,CACD8E,EAAU,EAAK,CAChB,EACD,EAEMS,EAAc,MAAOC,GAA2B,CACrD,GAAI,CAACA,GAASA,EAAM,SAAW,GAAK,CAAC7G,EAAS,OAC9CuG,EAAS,IAAI,EACb,IAAMO,EAAQxB,EAAkBjE,EAAY,OAC5C,GAAIyF,GAAS,EAAG,CACfP,EAAS,OAAOjB,CAAe,qBAAqB,EACpD,MACD,CACA,IAAMyB,EAAS,MAAM,KAAKF,CAAK,EAAE,MAAM,EAAGC,CAAK,EAC/C,QAAWE,KAAKD,EAAQ,CACvB,GAAI,CAACC,EAAE,KAAK,WAAW,QAAQ,EAAG,CACjCT,EAAS,IAAIS,EAAE,IAAI,mBAAmB,EACtC,MACD,CACA,GAAIA,EAAE,KAAOzB,GAAsB,CAClCgB,EAAS,IAAIS,EAAE,IAAI,gBAAgB,EACnC,MACD,CACD,CAEAX,EAAa,EAAI,EACjB,GAAI,CACH,IAAMY,EAAO,MAAM,QAAQ,IAAIF,EAAO,IAAIvB,EAAmB,CAAC,EACxD0B,EAAK,IAAI,SACfH,EAAO,QAAQ,CAACtB,EAAMpH,IAAM,CAC3B6I,EAAG,OAAO,OAAQzB,CAAI,EACtByB,EAAG,OAAO,QAASD,EAAK5I,CAAC,GAAG,MAAQ,OAAO4I,EAAK5I,CAAC,GAAG,KAAK,EAAI,EAAE,EAC/D6I,EAAG,OAAO,SAAUD,EAAK5I,CAAC,GAAG,OAAS,OAAO4I,EAAK5I,CAAC,GAAG,MAAM,EAAI,EAAE,CACnE,CAAC,EACD,IAAM+B,EAAM,MAAM,MACjB,GAAGJ,CAAO,oDAAoD,mBAAmB0D,CAAiB,CAAC,GACnG,CAAE,OAAQ,OAAQ,YAAa,UAAW,KAAMwD,CAAG,CACpD,EACA,GAAI,CAAC9G,EAAI,GAAI,CACZ,IAAM+G,EAAQ,MAAM/G,EAAI,KAAK,EAAE,MAAM,IAAM,IAAI,EAC/CmG,EAASY,GAAM,OAAS,eAAe,EACvC,MACD,CACA,IAAMA,EAAQ,MAAM/G,EAAI,KAAK,EAC7B6F,EAAgBtE,GAAS,CAAC,GAAGA,EAAM,GAAGwF,EAAK,OAAO,CAAC,CACpD,MAAQ,CACPZ,EAAS,eAAe,CACzB,QAAE,CACDF,EAAa,EAAK,EACdI,EAAa,UAASA,EAAa,QAAQ,MAAQ,GACxD,CACD,EAEMW,EAAoBzB,GAAgB,CACzCM,EAAgBtE,GAASA,EAAK,OAAQ+C,GAAMA,EAAE,MAAQiB,CAAG,CAAC,CAC3D,EAEA,OACC/J,EAAC,QAAK,SAAU+K,EAAc,MAAOU,GACpC,UAAA1L,EAAC,YACA,IAAK6K,EACL,MAAOT,EACP,SAAWpF,GAAMqF,EAASrF,EAAE,OAAO,KAAK,EACxC,YAAY,wBACZ,MAAO2G,GACP,KAAM,EACP,EACCjG,EAAY,OAAS,GACrB1F,EAAC,OAAI,MAAO4L,GACV,SAAAlG,EAAY,IAAKmG,GACjB5L,EAAC,OAAkB,MAAO6L,GACzB,UAAA9L,EAAC,OAAI,IAAK6L,EAAI,IAAK,IAAI,GAAG,MAAOE,GAAsB,EACvD/L,EAAC,UACA,KAAK,SACL,QAAS,IAAMyL,EAAiBI,EAAI,GAAG,EACvC,MAAOG,GACP,SAAUzB,GAAUE,EACpB,aAAW,oBACX,gBAED,IAVSoB,EAAI,GAWd,CACA,EACF,EAEAlB,GAAS3K,EAAC,OAAI,MAAOiM,GAAiB,SAAAtB,EAAM,EAC7C3K,EAAC,SACA,IAAK8K,EACL,KAAK,OACL,OAAO,UACP,SAAQ,GACR,SAAW9F,GAAM,KAAKiG,EAAYjG,EAAE,OAAO,KAAK,EAChD,MAAO,CAAE,QAAS,MAAO,EAC1B,EACA/E,EAAC,OAAI,MAAOiM,GACV,UAAA/D,GACAnI,EAAC,UAAO,KAAK,SAAS,QAAS,IAAMmI,EAAS,EAAG,MAAOgE,GAAmB,SAAU5B,EAAQ,kBAE7F,EAEDvK,EAAC,UACA,KAAK,SACL,QAAS,IAAM8K,EAAa,SAAS,MAAM,EAC3C,MAAOsB,GACP,SAAU7B,GAAUE,GAAa/E,EAAY,QAAUiE,EACvD,aAAYc,EAAY,kBAAe,eACvC,MAAOA,EAAY,kBAAe,eAEjC,SAAAA,EAAYzK,EAACqM,GAAA,EAAa,EAAKrM,EAACsM,GAAA,EAAe,EACjD,EACAtM,EAAC,OAAI,MAAO,CAAE,KAAM,CAAE,EAAG,EACzBA,EAAC,UAAO,KAAK,SAAS,QAASuI,EAAU,MAAOY,EAAkB,SAAUoB,EAAQ,kBAEpF,EACAvK,EAAC,UAAO,KAAK,SAAS,MAAOuM,GAAoB,SAAUhC,GAAUE,GAAa,CAACL,EAAM,KAAK,EAC5F,SAAAG,EAAS,eAAY,OACvB,GACD,GACD,CAEF,CAEA,SAASnC,GAAkBoE,EAAkBnH,EAAsBC,EAAsB,CACxF,GAAM,CAACmH,EAAKC,CAAM,EAAI7M,EAA+C,IAAI,EAEzE,OAAAF,EAAU,IAAM,CACf,IAAMgN,EAAS,IAAM,CACpB,IAAInM,EAAqB,KACzB,GAAI,CACHA,EAAK,SAAS,cAAcgM,CAAQ,CACrC,MAAQ,CACPhM,EAAK,IACN,CACA,GAAI,CAACA,EAAI,CACRkM,EAAO,IAAI,EACX,MACD,CACA,IAAME,EAAIpM,EAAG,sBAAsB,EACnCkM,EAAO,CACN,IAAKE,EAAE,IAAM,OAAO,QAAUA,EAAE,OAAStH,EAAe,GACxD,KAAMsH,EAAE,KAAO,OAAO,QAAUA,EAAE,MAAQvH,EAAe,EAC1D,CAAC,CACF,EAEAsH,EAAO,EACP,IAAME,EAAK,IAAI,eAAeF,CAAM,EACpC,GAAI,CACH,IAAMnM,EAAK,SAAS,cAAcgM,CAAQ,EACtChM,GAAIqM,EAAG,QAAQrM,CAAE,CACtB,MAAQ,CAER,CACA,cAAO,iBAAiB,SAAUmM,EAAQ,EAAI,EAC9C,OAAO,iBAAiB,SAAUA,CAAM,EACjC,IAAM,CACZE,EAAG,WAAW,EACd,OAAO,oBAAoB,SAAUF,EAAQ,EAAI,EACjD,OAAO,oBAAoB,SAAUA,CAAM,CAC5C,CACD,EAAG,CAACH,EAAUnH,EAAcC,CAAY,CAAC,EAElCmH,CACR,CAIA,IAAMhG,GAA8B,CACnC,SAAU,QACV,OAAQ,GACR,KAAM,MACN,UAAW,mBACX,OAAQ,WACR,QAAS,OACT,WAAY,SACZ,IAAK,EACL,QAAS,WACT,WAAY,yBACZ,MAAO,QACP,aAAc,IACd,UAAW,8BACX,WACC,6HACD,SAAU,GACV,cAAe,MAChB,EAEME,EAAoC,CACzC,OAAQ,OACR,WAAY,QACZ,MAAO,OACP,QAAS,WACT,aAAc,IACd,OAAQ,UACR,WAAY,IACZ,SAAU,EACX,EAEMD,GAA0C,CAC/C,GAAGC,EACH,WAAY,UACZ,MAAO,OACR,EAEMC,GAAyC,CAC9C,OAAQ,kCACR,WAAY,cACZ,MAAO,QACP,QAAS,WACT,aAAc,IACd,OAAQ,UACR,WAAY,IACZ,SAAU,EACX,EAEMC,GAA4C,CACjD,OAAQ,OACR,WAAY,UACZ,MAAO,QACP,QAAS,WACT,aAAc,IACd,OAAQ,UACR,WAAY,IACZ,SAAU,EACX,EAEMC,GAAkC,CACvC,QAAS,GACT,SAAU,EACX,EAEMuB,EAA6B,CAClC,MAAO,GACP,OAAQ,GACR,aAAc,IACd,WAAY,UACZ,MAAO,QACP,OAAQ,kBACR,OAAQ,UACR,SAAU,GACV,WAAY,IACZ,UAAW,4BACX,QAAS,cACT,WAAY,SACZ,eAAgB,SAChB,QAAS,CACV,EAEMqD,GAA8B,CACnC,WAAY,QACZ,OAAQ,oBACR,aAAc,EACd,QAAS,GACT,MAAO,IACP,UAAW,+BACX,WACC,6HACD,QAAS,OACT,cAAe,SACf,IAAK,EACL,MAAO,MACR,EAEMC,GAA+B,CACpC,MAAO,OACP,OAAQ,oBACR,aAAc,EACd,QAAS,EACT,SAAU,GACV,OAAQ,WACR,WAAY,UACZ,MAAO,OACP,WAAY,QACZ,UAAW,YACZ,EAEMO,GAAqC,CAC1C,QAAS,OACT,WAAY,SACZ,IAAK,CACN,EAEMY,EAA4B,CACjC,OAAQ,wBACR,aAAc,EACd,QAAS,WACT,SAAU,GACV,OAAQ,UACR,WAAY,GACb,EAEMP,GAAoC,CACzC,GAAGO,EACH,WAAY,OACZ,MAAO,OACR,EAEM3D,EAAkC,CACvC,GAAG2D,EACH,WAAY,cACZ,MAAO,UACP,YAAa,SACd,EAEMX,GAAmC,CACxC,GAAGW,EACH,WAAY,cACZ,MAAO,UACP,YAAa,SACd,EAEMlB,GAAoC,CACzC,QAAS,OACT,SAAU,OACV,IAAK,CACN,EAEME,GAA0C,CAC/C,SAAU,WACV,MAAO,GACP,OAAQ,GACR,aAAc,EACd,SAAU,SACV,OAAQ,oBACR,WAAY,SACb,EAEMC,GAAsC,CAC3C,MAAO,OACP,OAAQ,OACR,UAAW,QACX,QAAS,OACV,EAEMC,GAAuC,CAC5C,SAAU,WACV,IAAK,EACL,MAAO,EACP,MAAO,GACP,OAAQ,GACR,aAAc,IACd,OAAQ,OACR,WAAY,yBACZ,MAAO,QACP,SAAU,GACV,WAAY,OACZ,OAAQ,UACR,QAAS,EACT,QAAS,cACT,WAAY,SACZ,eAAgB,QACjB,EAEMC,GAAgC,CACrC,MAAO,UACP,SAAU,GACV,OAAQ,CACT,EAEMG,GAAuC,CAC5C,GAAGU,EACH,WAAY,cACZ,MAAO,UACP,YAAa,UACb,MAAO,GACP,OAAQ,GACR,QAAS,EACT,QAAS,cACT,WAAY,SACZ,eAAgB,SAChB,WAAY,CACb,EAEA,SAASR,IAAiB,CACzB,OACCrM,EAAC,OACA,KAAK,MACL,aAAW,eACX,MAAM,KACN,OAAO,KACP,QAAQ,YACR,KAAK,OACL,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,QAEf,UAAAD,EAAC,SAAM,wBAAY,EACnBA,EAAC,QAAK,EAAE,qHAAqH,GAC9H,CAEF,CAEA,SAASqM,IAAe,CACvB,OACCpM,EAAAF,EAAA,CACC,UAAAC,EAAC,SAAO,4EAAmE,EAC3EA,EAAC,QACA,cAAW,GACX,MAAO,CACN,QAAS,eACT,MAAO,GACP,OAAQ,GACR,OAAQ,mCACR,eAAgB,UAChB,aAAc,IACd,UAAW,sCACZ,EACD,GACD,CAEF,CAEA,IAAMiJ,GAA8B,CACnC,SAAU,QACV,IAAK,EACL,MAAO,EACP,OAAQ,EACR,MAAO,IACP,SAAU,OACV,WAAY,QACZ,WAAY,oBACZ,UAAW,gCACX,OAAQ,WACR,QAAS,OACT,cAAe,SACf,WACC,6HACD,MAAO,MACR,EAEMC,GAAoC,CACzC,QAAS,OACT,WAAY,SACZ,eAAgB,gBAChB,QAAS,YACT,aAAc,mBACf,EAEME,GAAoC,CACzC,KAAM,EACN,SAAU,OACV,QAAS,gBACV,EAEMC,GAAmC,CACxC,MAAO,UACP,SAAU,GACV,QAAS,WACT,UAAW,QACZ,EAEMC,GAAkC,CACvC,SAAU,GACV,cAAe,YACf,cAAe,GACf,MAAO,UACP,QAAS,cACT,UAAW,WACZ,EAEMC,GAAkC,CACvC,QAAS,OACT,WAAY,aACZ,IAAK,EACL,MAAO,OACP,UAAW,OACX,WAAY,QACZ,OAAQ,oBACR,aAAc,EACd,QAAS,WACT,OAAQ,UACR,SAAU,GACV,aAAc,EACd,MAAO,MACR,EAEMC,GAAuC,CAC5C,WAAY,EACZ,MAAO,GACP,OAAQ,GACR,aAAc,IACd,WAAY,UACZ,MAAO,QACP,WAAY,IACZ,SAAU,GACV,QAAS,cACT,WAAY,SACZ,eAAgB,QACjB,EAEMC,GAAsC,CAC3C,KAAM,EACN,WAAY,WACZ,UAAW,YACZ,EAEMC,GAAsC,CAC3C,WAAY,EACZ,WAAY,UACZ,MAAO,UACP,SAAU,GACV,WAAY,IACZ,QAAS,UACT,aAAc,EACd,UAAW,QACZ,EAEMvC,GAAqC,CAC1C,SAAU,QACV,OAAQ,GACR,KAAM,MACN,UAAW,mBACX,OAAQ,WACR,QAAS,OACT,cAAe,SACf,IAAK,EACL,QAAS,YACT,MAAO,iCACP,WAAY,QACZ,OAAQ,oBACR,aAAc,GACd,UAAW,+BACX,WACC,6HACD,MAAO,OACP,cAAe,MAChB,EAEME,GAAsC,CAC3C,QAAS,OACT,WAAY,SACZ,IAAK,CACN,EAEME,GAAqC,CAC1C,WAAY,UACZ,MAAO,UACP,SAAU,GACV,WAAY,IACZ,QAAS,UACT,aAAc,IACd,cAAe,YACf,cAAe,EAChB,EAEMC,GAA6C,CAClD,QAAS,OACT,eAAgB,gBAChB,SAAU,EACX,EAEMC,GAA6C,CAClD,MAAO,OACP,OAAQ,EACR,WAAY,UACZ,aAAc,IACd,SAAU,QACX,EAEMC,GAA4C,CACjD,OAAQ,OACR,WAAY,UACZ,aAAc,IACd,WAAY,aACb,EAEMC,GAAmC,CACxC,OAAQ,EACR,SAAU,GACV,MAAO,SACR,EAEO,SAASoF,GAAuD,CAMtE,GALA,QAAQ,IAAI,uDAAwD,CACnE,SAAU,OAAO,OAAW,IAC5B,UAAW,QAAQ,IAAI,uBACvB,eAAgB,OAAO,SAAa,KAAe,EAAQ,SAAS,eAAe7M,CAAa,CACjG,CAAC,EACG,OAAO,OAAW,IAAa,OAAO,KAC1C,GAAI,QAAQ,IAAI,yBAA2B,UAC1C,eAAQ,IACP,sEACA,QAAQ,IAAI,sBACb,EACO,KAER,GAAI,SAAS,eAAeA,CAAa,EAAG,OAAO,KAEnD,IAAM8M,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,GAAK9M,EACf8M,EAAU,QAAQ,cAAgB,OAClC,SAAS,KAAK,YAAYA,CAAS,EAEnC,IAAMC,EAAOnN,EAAWkN,CAAS,EACjC,OAAAC,EAAK,OAAOjN,EAACsD,GAAA,EAAgB,CAAE,EAExB,CACN,QAAS,IAAM,CACd2J,EAAK,QAAQ,EACbD,EAAU,OAAO,CAClB,CACD,CACD,CAGI,OAAO,OAAW,KACrB,QAAQ,IAAI,0CAA2C,CACtD,UAAW,QAAQ,IAAI,uBACvB,WAAY,SAAS,WACrB,cAAe,QAAQ,IAAI,yBAA2B,SACvD,CAAC,EAEE,OAAO,OAAW,KAAe,QAAQ,IAAI,yBAA2B,YACvE,SAAS,aAAe,UAC3B,SAAS,iBAAiB,mBAAoB,IAAM,CACnDD,EAAqB,CACtB,CAAC,EAEDA,EAAqB","names":["useEffect","useRef","useState","createRoot","Fragment","jsx","jsxs","MOUNT_NODE_ID","buildApiBase","override","host","protocol","computeCssSelector","el","path","node","part","className","classes","c","parent","tag","siblings","idx","ATTRS_OF_INTEREST","formatAttrs","parts","attr","v","trimmed","formatTextContent","text","buildSurroundingHtml","target","ancestors","cur","lines","depth","ancestor","indent","targetIndent","targetTag","targetText","childIndent","children","child","childText","i","isInsideToolbar","IGNORED_TAGS","POLL_INTERVAL_MS","formatEta","etaIso","eta","remainingMs","dateLabel","remainingMin","remainingHours","remainingDays","FeedbackToolbar","session","setSession","loading","setLoading","pinMode","setPinMode","pending","setPending","editingId","setEditingId","sidebarOpen","setSidebarOpen","finalizing","setFinalizing","apiBase","cancelled","controller","fetchOnce","res","data","interval","overlay","label","hovered","handleMove","e","rect","handleLeave","handleClick","event","offsetXRatio","offsetYRatio","refreshComments","submitNewComment","content","attachments","updateComment","id","removeComment","finalizeSession","nextStatus","prev","scrollToComment","comment","SubmittedPanel","visiblePins","pin","PinOverlay","PendingCommentPopover","CommentsSidebar","toolbarStyle","toolbarButtonActiveStyle","toolbarButtonStyle","toolbarButtonGhostStyle","toolbarButtonFinalizeStyle","toolbarHintStyle","progress","status","setTick","t","submittedPanelStyle","spinnerKeyframes","submittedHeaderStyle","Spinner","submittedBadgeStyle","submittedProgressLabelStyle","submittedProgressTrackStyle","submittedProgressFillStyle","submittedEtaStyle","size","number","editing","feedbackSessionId","onStartEdit","onCancelEdit","onSave","onRemove","useTargetPosition","pinDotStyle","EditPopover","onCancel","comments","currentPath","onClose","onSelect","groups","list","paths","a","b","sidebarStyle","sidebarHeaderStyle","ghostButtonStyle","sidebarScrollStyle","sidebarEmptyStyle","sidebarPathStyle","sidebarItemStyle","sidebarItemIndexStyle","sidebarItemTextStyle","sidebarItemDoneStyle","MAX_ATTACHMENTS","MAX_ATTACHMENT_BYTES","readImageDimensions","file","resolve","url","img","initial","initialAttachments","value","setValue","setAttachments","saving","setSaving","uploading","setUploading","error","setError","textareaRef","fileInputRef","len","handleSubmit","handleFiles","files","slots","picked","f","dims","fd","body","removeAttachment","popoverStyle","textareaStyle","attachmentRowStyle","att","attachmentThumbWrapStyle","attachmentThumbStyle","attachmentRemoveStyle","errorTextStyle","popoverActionsStyle","dangerButtonStyle","attachIconButtonStyle","SpinnerGlyph","PaperclipGlyph","primaryButtonStyle","selector","pos","setPos","update","r","ro","baseButton","mountFeedbackToolbar","container","root"]}
|