@gemboss/ui 0.39.0 → 0.41.0

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/src/chat-plain.js CHANGED
@@ -1,6 +1,6 @@
1
- (function(O){"use strict";var N=O.gembossUI;if(!N){O.console&&O.console.error("@gemboss/ui chat-plain.js: load kit-plain.js first (it defines gembossUI)");return}if(N.chat)return;var b={thread:"Conversation",message:"Message",send:"Send",stop:"Stop",attach:"Attach a file",copy:"Copy",copied:"Copied",copyCommand:"Copy command",originalError:"Original error",stopped:"Stopped",steps:"What it is doing",thought:"Thought",thinking:"Thinking",working:"Working",showLess:"Show less",showAll:"Show all {n} lines",openImage:"Open image"},A="http://www.w3.org/2000/svg",k=function(e){return typeof Node<"u"&&e instanceof Node};function l(e,n,r){var t=document.createElement(e);if(n)for(var a in n){var i=n[a];i==null||i===!1||(a==="text"?t.textContent=String(i):a==="className"?t.className=i:t.setAttribute(a,i===!0?"":String(i)))}return R(t,r),t}function R(e,n){return n==null||n===!1?e:Array.isArray(n)?(n.forEach(function(r){R(e,r)}),e):(e.appendChild(k(n)?n:document.createTextNode(String(n))),e)}function M(e,n,r){var t=document.createElementNS(A,"svg");if(t.setAttribute("viewBox",e),t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false"),n.forEach(function(i){var s=document.createElementNS(A,i.tag||"path");for(var o in i)o!=="tag"&&s.setAttribute(o,i[o]);t.appendChild(s)}),r)for(var a in r)t.setAttribute(a,r[a]);return t}var ae=[{host:"gemwatcher-api-production.up.railway.app",path:"/watch/artifacts/"},{host:"gemmedia-api-production.up.railway.app",path:"/"},{host:"media.gemboss.ai",path:"/v1/theme-media/assets/"},{host:"gemmedia.ops.gemboss.ai",path:"/v1/theme-media/assets/"},{host:"gemmedia.gemboss.ai",path:"/v1/theme-media/assets/"},{host:"cdn.shopify.com",path:"/"}],q=/[\u0000-\u001f\u007f]/g;function C(e){var n=String(e).replace(q,"").trim();return n?n.charAt(0)==="/"?n.charAt(1)==="/"||n.charAt(1)==="\\"?null:{href:n,internal:!0}:/^(https?:\/\/|mailto:)/i.test(n)?{href:n,internal:!1}:null:null}var D="https://same-origin.invalid",ie=/^\/[^/]+(\/[^/]+)*\/$/;function H(e,n){var r=String(e).replace(q,"").trim(),t;if(r.charAt(0)==="/"){var a=(n||[]).filter(function(s){return!s.host&&ie.test(s.path)});if(!a.length||r.charAt(1)==="/"||r.indexOf("\\")>=0||/%(2f|5c|25)|;/i.test(r))return null;try{t=new URL(r,D)}catch{return null}return t.origin!==D||/\.\.|%2e/i.test(t.pathname)?null:a.some(function(s){return t.pathname.indexOf(s.path)===0})?t.href.slice(D.length):null}try{t=new URL(r)}catch{return null}if(t.protocol!=="https:"||t.username||t.password||t.port)return null;var i=t.hostname.toLowerCase();return ae.concat(n||[]).some(function(s){return s.host===i&&t.pathname.indexOf(s.path)===0})?t.href:null}var T=function(e,n){return e.split(n).length-1};function P(e,n){var r=e.length,t,a,i,s,o=T(e,"("),d=T(e,")"),f=T(e,"["),c=T(e,"]");for(n=n!==!1;n&&r>0;)t=e.charAt(r-1),/[.,:;!?_~'"]/.test(t)?r-=1:t===")"&&o<d?(r-=1,d-=1):t==="]"&&f<c?(r-=1,c-=1):n=!1;if(s=e.slice(0,r),a=C(s),!a||a.internal)return null;try{i=new URL(a.href)}catch{return null}return!/^https?:$/.test(i.protocol)||!i.hostname?null:{href:a.href,rest:e.slice(r)}}var le=/(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*\n]+)\*)|(!\[([^\]]*)\]\(([^)\s]+)\))|(\[([^\]]+)\]\(([^)\s]+)\))|(<(https?:\/\/[^\s<>]+)>)|(https?:\/\/[^\s<>`"*\u2000-\u206f\u3000-\u9fff\uff00-\uffef]+)/gi;function F(e,n){n=n||{};for(var r=[],t=function(m){if(m){var v=r[r.length-1];v&&v.t==="text"?v.text+=m:r.push({t:"text",text:m})}},a=new RegExp(le.source,"gi"),i=0,s;(s=a.exec(e))!==null;){if(t(e.slice(i,s.index)),s[2]!=null)r.push({t:"code",text:s[2]});else if(s[4]!=null)r.push({t:"bold",text:s[4]});else if(s[6]!=null)r.push({t:"italic",text:s[6]});else if(s[7]!=null){var o=H(s[9],n.imageHosts),d=s[8].trim();if(o)r.push({t:"image",alt:d,src:o});else{var f=C(s[9]);f&&!f.internal?r.push({t:"link",label:d||"image",href:f.href,internal:!1}):t(d)}}else if(s[10]!=null){var c=s[12],p=n.ownLink&&n.ownLink(c)?{href:c,internal:!0,own:!0}:C(c);p?r.push({t:"link",label:s[11],href:p.href,internal:p.internal}):t(s[11])}else{var h=s[15]!=null&&s.index>0&&/[a-z\d]/i.test(e.charAt(s.index-1)),g=h?null:s[14]!=null?P(s[14],!1):P(s[15]);g?(r.push({t:"link",label:g.href,href:g.href,internal:!1}),t(g.rest)):t(s[0])}i=s.index+s[0].length}return t(e.slice(i)),r}var $=function(e){return/^\s*\|.*\|\s*$/.test(e)},se=function(e){return/^\s*\|?[\s:-]*-[-\s:|]*\|?\s*$/.test(e)&&e.indexOf("-")>=0},U=function(e){return e.trim().replace(/^\||\|$/g,"").split("|").map(function(n){return n.trim()})};function K(e){for(var n=String(e??"").split(`
1
+ (function(O){"use strict";var w=O.gembossUI;if(!w){O.console&&O.console.error("@gemboss/ui chat-plain.js: load kit-plain.js first (it defines gembossUI)");return}if(w.chat)return;var b={thread:"Conversation",message:"Message",send:"Send",stop:"Stop",attach:"Attach a file",copy:"Copy",copied:"Copied",copyCommand:"Copy command",originalError:"Original error",stopped:"Stopped",steps:"What it is doing",thought:"Thought",thinking:"Thinking",working:"Working",showLess:"Show less",showAll:"Show all {n} lines",openImage:"Open image"},A="http://www.w3.org/2000/svg",k=function(e){return typeof Node<"u"&&e instanceof Node};function l(e,n,r){var t=document.createElement(e);if(n)for(var a in n){var i=n[a];i==null||i===!1||(a==="text"?t.textContent=String(i):a==="className"?t.className=i:t.setAttribute(a,i===!0?"":String(i)))}return R(t,r),t}function R(e,n){return n==null||n===!1?e:Array.isArray(n)?(n.forEach(function(r){R(e,r)}),e):(e.appendChild(k(n)?n:document.createTextNode(String(n))),e)}function M(e,n,r){var t=document.createElementNS(A,"svg");if(t.setAttribute("viewBox",e),t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false"),n.forEach(function(i){var s=document.createElementNS(A,i.tag||"path");for(var o in i)o!=="tag"&&s.setAttribute(o,i[o]);t.appendChild(s)}),r)for(var a in r)t.setAttribute(a,r[a]);return t}var ae=[{host:"admin.gemboss.ai",path:"/api/gemboss/market-image/"},{host:"gemmedia-api-production.up.railway.app",path:"/"},{host:"media.gemboss.ai",path:"/v1/theme-media/assets/"},{host:"gemmedia.ops.gemboss.ai",path:"/v1/theme-media/assets/"},{host:"gemmedia.gemboss.ai",path:"/v1/theme-media/assets/"},{host:"cdn.shopify.com",path:"/"}],q=/[\u0000-\u001f\u007f]/g;function C(e){var n=String(e).replace(q,"").trim();return n?n.charAt(0)==="/"?n.charAt(1)==="/"||n.charAt(1)==="\\"?null:{href:n,internal:!0}:/^(https?:\/\/|mailto:)/i.test(n)?{href:n,internal:!1}:null:null}var D="https://same-origin.invalid",ie=/^\/[^/]+(\/[^/]+)*\/$/;function H(e,n){var r=String(e).replace(q,"").trim(),t;if(r.charAt(0)==="/"){var a=(n||[]).filter(function(i){return!i.host&&ie.test(i.path)});if(!a.length||r.charAt(1)==="/"||r.indexOf("\\")>=0||/%(2f|5c|25)|;/i.test(r))return null;try{t=new URL(r,D)}catch{return null}return t.origin!==D||/\.\.|%2e/i.test(t.pathname)?null:a.some(function(i){return t.pathname.indexOf(i.path)===0})?t.href.slice(D.length):null}try{t=new URL(r)}catch{return null}return t.protocol!=="https:"||t.username||t.password||t.port?null:ae.concat(n||[]).some(function(i){return i.host===t.hostname&&t.pathname.indexOf(i.path)===0})?t.href:null}var T=function(e,n){return e.split(n).length-1};function P(e,n){var r=e.length,t,a,i,s,o=T(e,"("),d=T(e,")"),f=T(e,"["),c=T(e,"]");for(n=n!==!1;n&&r>0;)t=e.charAt(r-1),/[.,:;!?_~'"]/.test(t)?r-=1:t===")"&&o<d?(r-=1,d-=1):t==="]"&&f<c?(r-=1,c-=1):n=!1;if(s=e.slice(0,r),a=C(s),!a||a.internal)return null;try{i=new URL(a.href)}catch{return null}return!/^https?:$/.test(i.protocol)||!i.hostname?null:{href:a.href,rest:e.slice(r)}}var le=/(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*\n]+)\*)|(!\[([^\]]*)\]\(([^)\s]+)\))|(\[([^\]]+)\]\(([^)\s]+)\))|(<(https?:\/\/[^\s<>]+)>)|(https?:\/\/[^\s<>`"*\u2000-\u206f\u3000-\u9fff\uff00-\uffef]+)/gi;function F(e,n){n=n||{};for(var r=[],t=function(m){if(m){var v=r[r.length-1];v&&v.t==="text"?v.text+=m:r.push({t:"text",text:m})}},a=new RegExp(le.source,"gi"),i=0,s;(s=a.exec(e))!==null;){if(t(e.slice(i,s.index)),s[2]!=null)r.push({t:"code",text:s[2]});else if(s[4]!=null)r.push({t:"bold",text:s[4]});else if(s[6]!=null)r.push({t:"italic",text:s[6]});else if(s[7]!=null){var o=H(s[9],n.imageHosts),d=s[8].trim();if(o)r.push({t:"image",alt:d,src:o});else{var f=C(s[9]);f&&!f.internal?r.push({t:"link",label:d||"image",href:f.href,internal:!1}):t(d)}}else if(s[10]!=null){var c=s[12],p=n.ownLink&&n.ownLink(c)?{href:c,internal:!0,own:!0}:C(c);p?r.push({t:"link",label:s[11],href:p.href,internal:p.internal}):t(s[11])}else{var h=s[15]!=null&&s.index>0&&/[a-z\d]/i.test(e.charAt(s.index-1)),g=h?null:s[14]!=null?P(s[14],!1):P(s[15]);g?(r.push({t:"link",label:g.href,href:g.href,internal:!1}),t(g.rest)):t(s[0])}i=s.index+s[0].length}return t(e.slice(i)),r}var $=function(e){return/^\s*\|.*\|\s*$/.test(e)},se=function(e){return/^\s*\|?[\s:-]*-[-\s:|]*\|?\s*$/.test(e)&&e.indexOf("-")>=0},U=function(e){return e.trim().replace(/^\||\|$/g,"").split("|").map(function(n){return n.trim()})};function K(e){for(var n=String(e??"").split(`
2
2
  `),r=[],t=0,a=[],i=function(){a.length&&r.push({t:"para",lines:a.slice()}),a.length=0};t<n.length;){var s=n[t],o;if(o=/^\s*```([\w#+.-]*)\s*$/.exec(s)){i();var d=[];for(t+=1;t<n.length&&!/^\s*```\s*$/.test(n[t]);)d.push(n[t]),t+=1;t+=1,r.push({t:"code",lang:o[1]||"",body:d.join(`
3
3
  `)});continue}if(o=/^(#{1,6})\s+(.*)$/.exec(s)){i(),o[2].trim()&&r.push({t:"heading",level:Math.min(o[1].length,3),text:o[2].trim()}),t+=1;continue}if(/^\s*(---|\*\*\*|___)\s*$/.test(s)){i(),r.push({t:"rule"}),t+=1;continue}if($(s)&&t+1<n.length&&se(n[t+1])){i();var f=U(s),c=[];for(t+=2;t<n.length&&$(n[t]);)c.push(U(n[t])),t+=1;r.push({t:"table",head:f,rows:c});continue}if(o=/^\s*([-*+]|\d+[.)])\s+(.*)$/.exec(s)){i();for(var p=/\d/.test(o[1]),h=p?parseInt(o[1],10):void 0,g=[];t<n.length;){var m=/^\s*([-*+]|\d+[.)])\s+(.*)$/.exec(n[t]);if(!m||/\d/.test(m[1])!==p)break;g.push(m[2]),t+=1}var v={t:"list",ordered:p,items:g};h!==void 0&&h!==1&&(v.start=h),r.push(v);continue}if(/^\s*>\s?/.test(s)){i();for(var y=[];t<n.length&&/^\s*>\s?/.test(n[t]);)y.push(n[t].replace(/^\s*>\s?/,"")),t+=1;r.push({t:"quote",lines:y});continue}if(s.trim()===""){i(),t+=1;continue}a.push(s),t+=1}return i(),r}var j=12;function x(e,n,r){return F(e,n).map(function(t){if(t.t==="text")return document.createTextNode(t.text);if(t.t==="code")return l("code",{className:"gemboss-md-code",text:t.text});if(t.t==="bold")return l("strong",0,x(t.text,n,r));if(t.t==="italic")return l("em",0,x(t.text,n,r));if(t.t==="link"){if(n.ownLink&&n.ownLink(t.href)&&n.renderLink)return n.renderLink(t.href,t.label)||document.createTextNode(t.label);var a=l("a",{className:"gemboss-ec-md-link",href:t.href,target:t.internal?null:"_blank",rel:t.internal?null:"noopener noreferrer",text:t.label});return t.internal&&n.onNavigate&&a.addEventListener("click",function(i){i.metaKey||i.ctrlKey||i.shiftKey||(i.preventDefault(),n.onNavigate(t.href))}),a}return l("a",{className:"gemboss-md-imglink",href:t.src,target:"_blank",rel:"noopener noreferrer","aria-label":t.alt?null:r.openImage},l("img",{className:"gemboss-md-img",src:t.src,alt:t.alt,loading:"lazy",referrerpolicy:"no-referrer"}))})}function z(e,n,r){var t=[];return e.forEach(function(a,i){t=t.concat(x(a,n,r)),i<e.length-1&&t.push(l("br"))}),t}function oe(e,n){var r=e.body.split(`
4
4
  `),t=r.length>j,a=!1,i=l("code",{text:t?r.slice(0,j).join(`
5
5
  `):e.body}),s=l("div",{className:"gemboss-md-codeblock"},[e.lang?l("span",{className:"gemboss-md-codelang",text:e.lang}):null,l("pre",null,i)]);if(t){var o=l("button",{type:"button",className:"gemboss-md-codemore","aria-expanded":"false",text:n.showAll.replace("{n}",String(r.length))});o.addEventListener("click",function(){a=!a,i.textContent=a?e.body:r.slice(0,j).join(`
6
- `),o.textContent=a?n.showLess:n.showAll.replace("{n}",String(r.length)),o.setAttribute("aria-expanded",String(a))}),s.appendChild(o)}return s}var ue={1:"h3",2:"h4",3:"h5"};function G(e,n,r){n=n||{};var t=Object.assign({},b,r||n.labels||{}),a=document.createDocumentFragment();return K(e).forEach(function(i){var s;i.t==="heading"?s=l(ue[i.level],{className:"gemboss-md-h gemboss-md-h"+i.level},x(i.text,n,t)):i.t==="code"?s=oe(i,t):i.t==="table"?s=l("div",{className:"gemboss-md-tablewrap"},l("table",{className:"gemboss-md-table"},[l("thead",null,l("tr",null,i.head.map(function(o){return l("th",null,x(o,n,t))}))),l("tbody",null,i.rows.map(function(o){return l("tr",null,i.head.map(function(d,f){return l("td",null,x(o[f]||"",n,t))}))}))])):i.t==="list"?s=l(i.ordered?"ol":"ul",{className:"gemboss-md-list",start:i.ordered&&i.start!==void 0?i.start:null},i.items.map(function(o){return l("li",null,x(o,n,t))})):i.t==="quote"?s=l("blockquote",{className:"gemboss-md-quote"},z(i.lines,n,t)):i.t==="rule"?s=l("hr",{className:"gemboss-md-rule"}):s=l("p",{className:"gemboss-md-p"},z(i.lines,n,t)),a.appendChild(s)}),a}function ce(){for(var e=12,n=1.7,r=.5,t=9.6,a=[],i=function(g){return Math.round(g*100)/100},s=function(g,m){return i(g)+" "+i(m)},o=0;o<8;o++){var d=o*Math.PI/4,f=Math.cos(d),c=Math.sin(d),p=-c,h=f;a.push(["M"+s(e+p*n,e+h*n),"L"+s(e+f*t+p*r,e+c*t+h*r),"Q"+s(e+f*(t+.6),e+c*(t+.6))+" "+s(e+f*t-p*r,e+c*t-h*r),"L"+s(e-p*n,e-h*n),"Z"].join(""))}return a.join(" ")}var V={claude:{path:ce(),label:"Claude Code"},codex:{path:"M12 2.6 20.1 7.3V16.7L12 21.4 3.9 16.7V7.3ZM12 6.9 7.6 9.45v5.1L12 17.1l4.4-2.55v-5.1Z",label:"Codex"}};function Y(e,n){var r=Object.prototype.hasOwnProperty.call(V,e)?V[e]:null;if(!r)return null;n=n||{};var t=document.createElementNS(A,"svg");if(t.setAttribute("class",n.className||"gemboss-engine-mark"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","currentColor"),n.decorative)t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false");else{t.setAttribute("role","img"),t.setAttribute("aria-label",n.title||r.label);var a=document.createElementNS(A,"title");a.textContent=n.title||r.label,t.appendChild(a)}var i=document.createElementNS(A,"path");return i.setAttribute("d",r.path),i.setAttribute("fill-rule","nonzero"),t.appendChild(i),t}function Z(e,n){var r=Object.assign({},b,n||{});return!e||!e.length?null:l("ul",{className:"gemboss-narr","aria-label":r.steps},e.map(function(t){var a=t.state||"ok",i;if(t.kind==="thinking")return i=[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:"\u2733"}),a==="running"||!t.detail?l("span",{className:"gemboss-narr-text gemboss-narr-thinking-live"},t.label||r.thinking):l("details",{className:"gemboss-narr-thinking"},[l("summary",null,t.label||r.thought),l("div",{className:"gemboss-narr-thinking-body",text:t.detail})])],l("li",{className:"gemboss-narr-row thinking "+a},i);if(t.kind==="steer")return l("li",{className:"gemboss-narr-row steer "+a},[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:"\u276F"}),l("span",{className:"gemboss-narr-text gemboss-narr-steer"},t.label)]);if(t.kind==="trace")return l("li",{className:"gemboss-narr-row trace "+a,title:t.error||null},[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:a==="failed"?"\u2715":"\u23FA"}),t.detail?l("details",{className:"gemboss-narr-trace"},[l("summary",null,l("code",null,t.label)),l("div",{className:"gemboss-narr-trace-body"},l("code",{text:t.detail}))]):l("code",{className:"gemboss-narr-text gemboss-narr-trace-call"},t.label)]);var s=t.engine?Y(t.engine):null;return l("li",{className:"gemboss-narr-row "+a,title:t.error||null},[s?l("span",{className:"gemboss-narr-dot gemboss-narr-dot-engine"},s):l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:a==="ok"?"\u2713":a==="failed"?"\u2715":"\xB7"}),l("span",{className:"gemboss-narr-text"},[t.label,t.target?l("span",{className:"gb-chat-target"},[" ",t.target]):null])])}))}function J(e,n){e=e||{};var r=Object.assign({},b,n||{}),t=e.verbs&&e.verbs.length?e.verbs:[r.working],a=l("span",{className:"gemboss-ec-pending-verb","aria-hidden":"true",text:t[0]+"\u2026"}),i=l("span",{className:"gemboss-ec-pending-meta","aria-hidden":"true",text:"0s"}),s=l("div",{className:"gemboss-ec-pending",role:"status"},[l("span",{className:"gemboss-assistant-sronly",text:e.label||t[0]}),l("span",{className:"gemboss-ec-pending-star","aria-hidden":"true",text:"\u2733"}),e.compact?null:a,i]),o=e.since||Date.now(),d=!1,f=-1,c=function(){var h=Math.max(0,Math.floor((Date.now()-o)/1e3));i.textContent=(e.tokens>0?e.tokens+" token"+(e.tokens===1?"":"s")+" \xB7 ":"")+h+"s";var g=t.length>1?Math.floor(h/2)%t.length:0;if(g!==f&&!e.compact){f=g;var m=l("span",{className:"gemboss-ec-pending-verb","aria-hidden":"true",text:t[f]+"\u2026"});a.replaceWith(m),a=m}};c();var p=setInterval(function(){if(s.isConnected)d=!0;else if(d||Date.now()-o>6e5){clearInterval(p);return}c()},250);return s}function I(e,n,r){r=r||{};var t=document.createDocumentFragment();return!e||!e.length||(r.title&&t.appendChild(l("p",{className:"gb-chat-label"},r.title)),t.appendChild(l("div",{className:"gemboss-ec-starters"},e.map(function(a){var i=l("button",{type:"button",className:"gemboss-ec-starter gb-chat-starter"},[a.hint?l("span",{className:"gb-chat-starter-hint",text:a.hint}):null,a.label]);return i.addEventListener("click",function(){n(a)}),i})))),t}function de(e){var n=function(){try{var r=l("textarea",{readonly:!0});r.value=e,r.style.position="fixed",r.style.opacity="0",document.body.appendChild(r),r.select();var t=document.execCommand("copy");return r.remove(),t}catch{return!1}};try{if(navigator.clipboard&&navigator.clipboard.writeText)return navigator.clipboard.writeText(e).then(function(){return!0},n)}catch{}return Promise.resolve(n())}function Q(e,n,r){var t=l("button",{type:"button",className:"ui-btn ui-btn--ghost gb-chat-copy",text:n}),a=0;return t.addEventListener("click",function(){de(e).then(function(i){i&&(t.textContent=r,clearTimeout(a),a=setTimeout(function(){t.textContent=n},1500))})}),t}function X(e,n,r){var t=Object.assign({},b,r||{}),a=(n||[]).filter(function(i){return i!=null&&i!==!1&&i!==""});return!e&&!a.length?null:l("div",{className:"gb-chat-meta"},[e?Q(e,t.copy,t.copied):null].concat(a.map(function(i){return l("span",null,i)})))}function ee(e,n,r){var t=Object.assign({},b,r||{});return n?l("div",{className:"ui-notice ui-notice--danger gb-chat-error",role:"alert"},[l("div",null,n.text),n.command?l("code",{className:"gb-chat-cmd",text:n.command}):null,n.command?l("div",null,Q(n.command,t.copyCommand,t.copied)):null,l("details",{className:"gb-chat-original"},[l("summary",{text:t.originalError}),l("span",null,e)])]):l("p",{className:"ui-notice ui-notice--danger gb-chat-error",role:"alert"},e)}function _(e){return l("p",{className:"gb-chat-note"},e)}var fe={warn:"gb-engine-notice gb-engine-notice--warn",down:"gb-engine-notice gb-engine-notice--down"},he={warn:"gb-engine-dot gb-engine-dot--warn",down:"gb-engine-dot gb-engine-dot--down"};function te(e,n,r,t){if(N.bridgeNotice)return N.bridgeNotice({text:k(e)?void 0:e,node:k(e)?e:void 0,tone:n,action:r,onOpenSetup:t});var a=n==="warn"?"warn":"down",i=l("p",{className:fe[a],role:"status"},[l("span",{className:he[a],"aria-hidden":"true"}),l("span",{className:"gb-engine-notice-text"},e)]);if(t){var s=l("button",{type:"button",className:"gb-engine-notice-link",text:r||"Open setup"});s.addEventListener("click",t),i.appendChild(s)}return i}function B(e,n){return l("div",{className:"gemboss-ec-bubble"},[e,n?l("span",{className:"gemboss-ec-cursor","aria-hidden":"true"}):null])}function ne(e,n){n=n||{};var r=Object.assign({},b,n.labels||{});if(e.role==="note")return e.tone==="warn"||e.tone==="down"?te(e.text,e.tone,e.action&&e.action.label,e.action&&e.action.onClick):_(e.text);if(e.role==="user")return l("div",{className:"gemboss-ec-msg user",lang:e.lang||null},B(e.text));var t=!!e.text;return l("div",{className:"gemboss-ec-msg assistant",lang:e.lang||null},[e.steps&&e.steps.length?Z(e.steps,r):null,t?B(e.markdown===!1?e.text:G(e.text,n,r),e.pending):e.pending?J({verbs:n.pendingVerbs,since:n.since},r):null,n.renderExtra?n.renderExtra(e):null,e.warn?l("p",{className:"ui-notice ui-notice--warning gb-chat-notice"},e.warn):null,e.error?e.bridge?te(e.error,"down",null,n.onOpenSetup):ee(e.error,e.fix,r):null,e.stopped?_(r.stopped):null,!e.pending&&t?X(e.text,e.meta,r):null])}function ge(e){try{return JSON.stringify(e,function(n,r){return typeof r=="function"?"fn":k(r)?"node:"+(r.textContent||""):r})}catch{return String(Math.random())}}function pe(e,n){var r=e.querySelectorAll("details"),t=n.querySelectorAll("details"),a;for(a=0;a<r.length&&a<t.length;a++)r[a].open&&(t[a].open=!0);for(r=e.querySelectorAll(".gemboss-md-codemore"),t=n.querySelectorAll(".gemboss-md-codemore"),a=0;a<r.length&&a<t.length;a++)r[a].getAttribute("aria-expanded")==="true"&&t[a].click()}function me(e,n,r){r=r||{};var t=Object.assign({},b,r.labels||{}),a=e.__gbChat||(e.__gbChat={els:new Map,since:new Map}),i=[],s=new Map;if(!n.length&&(r.intro||r.starters&&r.starters.length)){var o=function(d){r.onStarter&&r.onStarter(d)};i.push(l("div",{className:"gemboss-ec-msg assistant"},[r.intro?B(r.intro):null,r.starters&&r.starters.length?I(r.starters,o,{title:r.startersTitle}):null,r.moreStarters&&r.moreStarters.length?I(r.moreStarters,o,{title:r.moreStartersTitle}):null]))}return n.forEach(function(d,f){var c=d.id!==void 0&&d.id!==null?"id:"+d.id:"at:"+f,p=ge(d),h=a.els.get(c);d.pending&&!d.text&&!a.since.has(c)&&a.since.set(c,Date.now());var g;h&&h.look===p?g=h.el:(g=ne(d,Object.assign({},r,{labels:t,since:a.since.get(c)})),h&&pe(h.el,g)),s.set(c,{el:g,look:p}),i.push(g)}),a.els=s,a.since.forEach(function(d,f){s.has(f)||a.since.delete(f)}),e.replaceChildren.apply(e,i),e}function ve(e,n){n=n||{};var r=Object.assign({},b,n.labels||{});e.classList.add("gemboss-ec-scroll"),e.setAttribute("role","log"),e.hasAttribute("aria-label")||e.setAttribute("aria-label",n.label||r.thread);var t=!0,a=0,i=0,s=0,o=!1,d=null,f=function(){e.scrollTop=e.scrollHeight,d=e.scrollTop},c=function(){!t||a||(a=requestAnimationFrame(function(){a=0,t&&f()}))},p=function(){i=Date.now(),o=!0,t=!1},h=function(){o=!1},g=function(){var u=e.scrollHeight-e.clientHeight-e.scrollTop,w=d!==null&&e.scrollTop===d;d=null,u>24&&!w?t=!1:u<=4&&(!o||Date.now()-i>400)&&(t=!0)},m=function(u){u.deltaY<0?p():u.deltaY>0&&h()},v=function(u){s=u.touches[0]?u.touches[0].clientY:0},y=function(u){var w=u.touches[0]?u.touches[0].clientY:0;w>s+2?p():w<s-2&&h()},L=function(u){u.key==="ArrowUp"||u.key==="PageUp"||u.key==="Home"?p():(u.key==="ArrowDown"||u.key==="PageDown"||u.key==="End")&&h()};e.addEventListener("scroll",g,{passive:!0}),e.addEventListener("wheel",m,{passive:!0}),e.addEventListener("touchstart",v,{passive:!0}),e.addEventListener("touchmove",y,{passive:!0}),e.addEventListener("keydown",L),e.addEventListener("load",c,!0);var E=new MutationObserver(c);E.observe(e,{childList:!0,subtree:!0,characterData:!0});var S=typeof ResizeObserver>"u"?null:new ResizeObserver(c);return S&&S.observe(e),f(),{el:e,setBusy:function(u){u?e.setAttribute("aria-busy","true"):e.removeAttribute("aria-busy")},toEnd:function(){t=!0,f()},destroy:function(){cancelAnimationFrame(a),e.removeEventListener("scroll",g),e.removeEventListener("wheel",m),e.removeEventListener("touchstart",v),e.removeEventListener("touchmove",y),e.removeEventListener("keydown",L),e.removeEventListener("load",c,!0),E.disconnect(),S&&S.disconnect()}}}function be(){return M("0 0 24 24",[{d:"M21.4 11.1l-8.5 8.5a5.5 5.5 0 01-7.8-7.8l8.5-8.5a3.7 3.7 0 015.2 5.2l-8.5 8.5a1.8 1.8 0 01-2.6-2.6l7.8-7.8",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"}])}function ye(e){e=e||{};var n=e.label||b.attach,r=l("input",{type:"file",hidden:!0,accept:e.accept||null,multiple:e.multiple||null,tabindex:"-1"}),t=l("button",{type:"button",className:"gemboss-ec-attach-btn","aria-label":n,title:e.title||n,disabled:e.disabled||null},be());t.addEventListener("click",function(){r.click()}),r.addEventListener("change",function(){var i=Array.prototype.slice.call(r.files||[]);r.value="",i.length&&e.onFiles&&e.onFiles(i)});var a=document.createDocumentFragment();return a.appendChild(t),a.appendChild(r),a}var W=["full","compact","icon","tight"];function we(e){var n=e.querySelector(".gemboss-ec-toolbar-lead"),r=e.querySelector(".gemboss-ec-toolbar");if(n&&n.scrollWidth>n.clientWidth+1||r&&r.scrollWidth>r.clientWidth+1)return!0;var t,a=e.querySelectorAll(".gemboss-ec-chiplabel, .gb-engine-chiplabel");for(t=0;t<a.length;t++)if(a[t].offsetParent!==null&&a[t].scrollWidth>a[t].clientWidth+1)return!0;for(a=e.querySelectorAll(".gemboss-ec-chipctl, .gb-engine-chip"),t=0;t<a.length;t++)if(a[t].offsetParent!==null&&a[t].tagName!=="SELECT"&&a[t].scrollWidth>a[t].clientWidth+1)return!0;return!1}function Ne(e,n){e&&!k(e)&&(n=e,e=null),n=n||{};var r=Object.assign({},b,n.labels||{}),t=!1,a=l("textarea",{rows:n.rows||1,maxlength:n.maxLength||null,"aria-label":n.label||r.message,placeholder:n.placeholder||null}),i=l("div",{className:"gemboss-ec-toolbar-lead"},n.leading||null),s=n.tools?l("div",{className:"gemboss-ec-toolbar-tools"},n.tools):null,o=l("button",{type:"submit",disabled:!0,"aria-label":r.send,title:r.send},M("0 0 20 20",[{d:"M10.53 4.47a.75.75 0 0 0-1.06 0l-4.5 4.5a.75.75 0 0 0 1.06 1.06l3.22-3.22v8.69a.75.75 0 0 0 1.5 0v-8.69l3.22 3.22a.75.75 0 1 0 1.06-1.06l-4.5-4.5Z"}])),d=l("button",{type:"button",className:"gemboss-ec-stop","aria-label":r.stop,title:r.stop},M("0 0 12 12",[{tag:"rect",x:"1",y:"1",width:"10",height:"10",rx:"2"}])),f=l("div",{className:"gemboss-ec-toolbar"},[i,s,n.status||null,o]),c=l("div",{className:"gemboss-ec-input","data-density":"full"},[n.top||null,a,f]);e&&e.appendChild(c);var p=function(){return!a.disabled&&a.value.trim()!==""&&(!t||n.sendWhileBusy||!n.onStop)},h=function(){var u=t&&!!n.onStop,w=u?d:o,re=u?o:d;re.parentNode&&re.replaceWith(w),o.disabled=!p()},g=function(){p()&&(n.onSend&&n.onSend(a.value),n.thread&&n.thread.toEnd&&n.thread.toEnd(),n.keepValue||(a.value="",m(),h()))},m=function(){if(!(a.offsetParent===null||a.scrollHeight===0)){var u=parseFloat(getComputedStyle(a).maxHeight)||84;a.style.height="auto";var w=a.scrollHeight+(a.offsetHeight-a.clientHeight);a.style.height=Math.min(w,u)+"px",a.style.overflowY=w>u?"auto":"hidden"}};a.addEventListener("input",function(){m(),h(),n.onChange&&n.onChange(a.value)}),a.addEventListener("keydown",function(u){n.onKeyDown&&n.onKeyDown(u),!u.defaultPrevented&&u.key==="Enter"&&!u.shiftKey&&!u.isComposing&&u.keyCode!==229&&(u.preventDefault(),g())}),o.addEventListener("click",function(u){u.preventDefault(),g()}),d.addEventListener("click",function(){n.onStop&&n.onStop(),a.focus()});var v=!1,y=function(){if(!(v||c.offsetParent===null)){v=!0;for(var u=0;u<W.length&&(c.setAttribute("data-density",W[u]),!(u===W.length-1||!we(c)));u++);v=!1,n.onDensity&&n.onDensity(c.getAttribute("data-density"))}},L=-1,E=typeof ResizeObserver>"u"?null:new ResizeObserver(function(){c.clientWidth!==L&&(L=c.clientWidth,y(),m())});E&&E.observe(c);var S=new MutationObserver(function(){v||y()});return S.observe(i,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-label","title","aria-label"]}),document.fonts&&document.fonts.ready&&document.fonts.ready.then(y),requestAnimationFrame(function(){y(),m()}),{el:c,textarea:a,value:function(){return a.value},setValue:function(u){a.value=u==null?"":String(u),m(),h()},setBusy:function(u){t=!!u,h()},setDisabled:function(u){a.disabled=!!u,h()},focus:function(){a.focus()},measure:y,destroy:function(){E&&E.disconnect(),S.disconnect(),c.remove()}}}var xe="The bridge on your machine is not responding: it was stopped, or the machine just slept. The panel is checking again; rerun the install command if it does not come back.",Ee="The bridge on your machine rejected this browser's pairing code (it was installed with another code). Pair again in Setup.";function Se(e,n){return e&&typeof e=="object"&&(e.atBridge=!0,n!==void 0&&(e.status=n)),e}function Ae(e,n){return n=n||{},e&&e.name==="AbortError"?{error:null,bridge:!1,stopped:!0}:e&&e.atBridge&&e.status===401?{error:n.rejected||Ee,bridge:!0,stopped:!1}:e&&e.atBridge&&(e instanceof TypeError||e.name==="TimeoutError")?{error:n.gone||xe,bridge:!0,stopped:!1}:{error:e&&e.message||String(e),bridge:!1,stopped:!1}}N.engineMark||(N.engineMark=Y),N.chat={markdown:G,parseBlocks:K,parseInline:F,linkTarget:C,imageSource:H,message:ne,render:me,thread:ve,steps:Z,pending:J,starters:I,meta:X,error:ee,note:_,composer:Ne,attach:ye,labels:b,atBridge:Se,turnError:Ae}})(typeof globalThis<"u"?globalThis:this);
6
+ `),o.textContent=a?n.showLess:n.showAll.replace("{n}",String(r.length)),o.setAttribute("aria-expanded",String(a))}),s.appendChild(o)}return s}var ue={1:"h3",2:"h4",3:"h5"};function G(e,n,r){n=n||{};var t=Object.assign({},b,r||n.labels||{}),a=document.createDocumentFragment();return K(e).forEach(function(i){var s;i.t==="heading"?s=l(ue[i.level],{className:"gemboss-md-h gemboss-md-h"+i.level},x(i.text,n,t)):i.t==="code"?s=oe(i,t):i.t==="table"?s=l("div",{className:"gemboss-md-tablewrap"},l("table",{className:"gemboss-md-table"},[l("thead",null,l("tr",null,i.head.map(function(o){return l("th",null,x(o,n,t))}))),l("tbody",null,i.rows.map(function(o){return l("tr",null,i.head.map(function(d,f){return l("td",null,x(o[f]||"",n,t))}))}))])):i.t==="list"?s=l(i.ordered?"ol":"ul",{className:"gemboss-md-list",start:i.ordered&&i.start!==void 0?i.start:null},i.items.map(function(o){return l("li",null,x(o,n,t))})):i.t==="quote"?s=l("blockquote",{className:"gemboss-md-quote"},z(i.lines,n,t)):i.t==="rule"?s=l("hr",{className:"gemboss-md-rule"}):s=l("p",{className:"gemboss-md-p"},z(i.lines,n,t)),a.appendChild(s)}),a}function ce(){for(var e=12,n=1.7,r=.5,t=9.6,a=[],i=function(g){return Math.round(g*100)/100},s=function(g,m){return i(g)+" "+i(m)},o=0;o<8;o++){var d=o*Math.PI/4,f=Math.cos(d),c=Math.sin(d),p=-c,h=f;a.push(["M"+s(e+p*n,e+h*n),"L"+s(e+f*t+p*r,e+c*t+h*r),"Q"+s(e+f*(t+.6),e+c*(t+.6))+" "+s(e+f*t-p*r,e+c*t-h*r),"L"+s(e-p*n,e-h*n),"Z"].join(""))}return a.join(" ")}var V={claude:{path:ce(),label:"Claude Code"},codex:{path:"M12 2.6 20.1 7.3V16.7L12 21.4 3.9 16.7V7.3ZM12 6.9 7.6 9.45v5.1L12 17.1l4.4-2.55v-5.1Z",label:"Codex"}};function Y(e,n){var r=Object.prototype.hasOwnProperty.call(V,e)?V[e]:null;if(!r)return null;n=n||{};var t=document.createElementNS(A,"svg");if(t.setAttribute("class",n.className||"gemboss-engine-mark"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","currentColor"),n.decorative)t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false");else{t.setAttribute("role","img"),t.setAttribute("aria-label",n.title||r.label);var a=document.createElementNS(A,"title");a.textContent=n.title||r.label,t.appendChild(a)}var i=document.createElementNS(A,"path");return i.setAttribute("d",r.path),i.setAttribute("fill-rule","nonzero"),t.appendChild(i),t}function Z(e,n){var r=Object.assign({},b,n||{});return!e||!e.length?null:l("ul",{className:"gemboss-narr","aria-label":r.steps},e.map(function(t){var a=t.state||"ok",i;if(t.kind==="thinking")return i=[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:"\u2733"}),a==="running"||!t.detail?l("span",{className:"gemboss-narr-text gemboss-narr-thinking-live"},t.label||r.thinking):l("details",{className:"gemboss-narr-thinking"},[l("summary",null,t.label||r.thought),l("div",{className:"gemboss-narr-thinking-body",text:t.detail})])],l("li",{className:"gemboss-narr-row thinking "+a},i);if(t.kind==="steer")return l("li",{className:"gemboss-narr-row steer "+a},[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:"\u276F"}),l("span",{className:"gemboss-narr-text gemboss-narr-steer"},t.label)]);if(t.kind==="trace")return l("li",{className:"gemboss-narr-row trace "+a,title:t.error||null},[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:a==="failed"?"\u2715":"\u23FA"}),t.detail?l("details",{className:"gemboss-narr-trace"},[l("summary",null,l("code",null,t.label)),l("div",{className:"gemboss-narr-trace-body"},l("code",{text:t.detail}))]):l("code",{className:"gemboss-narr-text gemboss-narr-trace-call"},t.label)]);var s=t.engine?Y(t.engine):null;return l("li",{className:"gemboss-narr-row "+a,title:t.error||null},[s?l("span",{className:"gemboss-narr-dot gemboss-narr-dot-engine"},s):l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:a==="ok"?"\u2713":a==="failed"?"\u2715":"\xB7"}),l("span",{className:"gemboss-narr-text"},[t.label,t.target?l("span",{className:"gb-chat-target"},[" ",t.target]):null])])}))}function J(e,n){e=e||{};var r=Object.assign({},b,n||{}),t=e.verbs&&e.verbs.length?e.verbs:[r.working],a=l("span",{className:"gemboss-ec-pending-verb","aria-hidden":"true",text:t[0]+"\u2026"}),i=l("span",{className:"gemboss-ec-pending-meta","aria-hidden":"true",text:"0s"}),s=l("div",{className:"gemboss-ec-pending",role:"status"},[l("span",{className:"gemboss-assistant-sronly",text:e.label||t[0]}),l("span",{className:"gemboss-ec-pending-star","aria-hidden":"true",text:"\u2733"}),e.compact?null:a,i]),o=e.since||Date.now(),d=!1,f=-1,c=function(){var h=Math.max(0,Math.floor((Date.now()-o)/1e3));i.textContent=(e.tokens>0?e.tokens+" token"+(e.tokens===1?"":"s")+" \xB7 ":"")+h+"s";var g=t.length>1?Math.floor(h/2)%t.length:0;if(g!==f&&!e.compact){f=g;var m=l("span",{className:"gemboss-ec-pending-verb","aria-hidden":"true",text:t[f]+"\u2026"});a.replaceWith(m),a=m}};c();var p=setInterval(function(){if(s.isConnected)d=!0;else if(d||Date.now()-o>6e5){clearInterval(p);return}c()},250);return s}function I(e,n,r){r=r||{};var t=document.createDocumentFragment();return!e||!e.length||(r.title&&t.appendChild(l("p",{className:"gb-chat-label"},r.title)),t.appendChild(l("div",{className:"gemboss-ec-starters"},e.map(function(a){var i=l("button",{type:"button",className:"gemboss-ec-starter gb-chat-starter"},[a.hint?l("span",{className:"gb-chat-starter-hint",text:a.hint}):null,a.label]);return i.addEventListener("click",function(){n(a)}),i})))),t}function de(e){var n=function(){try{var r=l("textarea",{readonly:!0});r.value=e,r.style.position="fixed",r.style.opacity="0",document.body.appendChild(r),r.select();var t=document.execCommand("copy");return r.remove(),t}catch{return!1}};try{if(navigator.clipboard&&navigator.clipboard.writeText)return navigator.clipboard.writeText(e).then(function(){return!0},n)}catch{}return Promise.resolve(n())}function Q(e,n,r){var t=l("button",{type:"button",className:"ui-btn ui-btn--ghost gb-chat-copy",text:n}),a=0;return t.addEventListener("click",function(){de(e).then(function(i){i&&(t.textContent=r,clearTimeout(a),a=setTimeout(function(){t.textContent=n},1500))})}),t}function X(e,n,r){var t=Object.assign({},b,r||{}),a=(n||[]).filter(function(i){return i!=null&&i!==!1&&i!==""});return!e&&!a.length?null:l("div",{className:"gb-chat-meta"},[e?Q(e,t.copy,t.copied):null].concat(a.map(function(i){return l("span",null,i)})))}function ee(e,n,r){var t=Object.assign({},b,r||{});return n?l("div",{className:"ui-notice ui-notice--danger gb-chat-error",role:"alert"},[l("div",null,n.text),n.command?l("code",{className:"gb-chat-cmd",text:n.command}):null,n.command?l("div",null,Q(n.command,t.copyCommand,t.copied)):null,l("details",{className:"gb-chat-original"},[l("summary",{text:t.originalError}),l("span",null,e)])]):l("p",{className:"ui-notice ui-notice--danger gb-chat-error",role:"alert"},e)}function _(e){return l("p",{className:"gb-chat-note"},e)}var fe={warn:"gb-engine-notice gb-engine-notice--warn",down:"gb-engine-notice gb-engine-notice--down"},he={warn:"gb-engine-dot gb-engine-dot--warn",down:"gb-engine-dot gb-engine-dot--down"};function te(e,n,r,t){if(w.bridgeNotice)return w.bridgeNotice({text:k(e)?void 0:e,node:k(e)?e:void 0,tone:n,action:r,onOpenSetup:t});var a=n==="warn"?"warn":"down",i=l("p",{className:fe[a],role:"status"},[l("span",{className:he[a],"aria-hidden":"true"}),l("span",{className:"gb-engine-notice-text"},e)]);if(t){var s=l("button",{type:"button",className:"gb-engine-notice-link",text:r||"Open setup"});s.addEventListener("click",t),i.appendChild(s)}return i}function B(e,n){return l("div",{className:"gemboss-ec-bubble"},[e,n?l("span",{className:"gemboss-ec-cursor","aria-hidden":"true"}):null])}function ne(e,n){n=n||{};var r=Object.assign({},b,n.labels||{});if(e.role==="note")return e.tone==="warn"||e.tone==="down"?te(e.text,e.tone,e.action&&e.action.label,e.action&&e.action.onClick):_(e.text);if(e.role==="user")return l("div",{className:"gemboss-ec-msg user",lang:e.lang||null},B(e.text));var t=!!e.text;return l("div",{className:"gemboss-ec-msg assistant",lang:e.lang||null},[e.steps&&e.steps.length?Z(e.steps,r):null,t?B(e.markdown===!1?e.text:G(e.text,n,r),e.pending):e.pending?J({verbs:n.pendingVerbs,since:n.since},r):null,n.renderExtra?n.renderExtra(e):null,e.warn?l("p",{className:"ui-notice ui-notice--warning gb-chat-notice"},e.warn):null,e.error?e.bridge?te(e.error,"down",null,n.onOpenSetup):ee(e.error,e.fix,r):null,e.stopped?_(r.stopped):null,!e.pending&&t?X(e.text,e.meta,r):null])}function ge(e){try{return JSON.stringify(e,function(n,r){return typeof r=="function"?"fn":k(r)?"node:"+(r.textContent||""):r})}catch{return String(Math.random())}}function pe(e,n){var r=e.querySelectorAll("details"),t=n.querySelectorAll("details"),a;for(a=0;a<r.length&&a<t.length;a++)r[a].open&&(t[a].open=!0);for(r=e.querySelectorAll(".gemboss-md-codemore"),t=n.querySelectorAll(".gemboss-md-codemore"),a=0;a<r.length&&a<t.length;a++)r[a].getAttribute("aria-expanded")==="true"&&t[a].click()}function me(e,n,r){r=r||{};var t=Object.assign({},b,r.labels||{}),a=e.__gbChat||(e.__gbChat={els:new Map,since:new Map}),i=[],s=new Map;if(!n.length&&(r.intro||r.starters&&r.starters.length)){var o=function(d){r.onStarter&&r.onStarter(d)};i.push(l("div",{className:"gemboss-ec-msg assistant"},[r.intro?B(r.intro):null,r.starters&&r.starters.length?I(r.starters,o,{title:r.startersTitle}):null,r.moreStarters&&r.moreStarters.length?I(r.moreStarters,o,{title:r.moreStartersTitle}):null]))}return n.forEach(function(d,f){var c=d.id!==void 0&&d.id!==null?"id:"+d.id:"at:"+f,p=ge(d),h=a.els.get(c);d.pending&&!d.text&&!a.since.has(c)&&a.since.set(c,Date.now());var g;h&&h.look===p?g=h.el:(g=ne(d,Object.assign({},r,{labels:t,since:a.since.get(c)})),h&&pe(h.el,g)),s.set(c,{el:g,look:p}),i.push(g)}),a.els=s,a.since.forEach(function(d,f){s.has(f)||a.since.delete(f)}),e.replaceChildren.apply(e,i),e}function ve(e,n){n=n||{};var r=Object.assign({},b,n.labels||{});e.classList.add("gemboss-ec-scroll"),e.setAttribute("role","log"),e.hasAttribute("aria-label")||e.setAttribute("aria-label",n.label||r.thread);var t=!0,a=0,i=0,s=0,o=!1,d=null,f=function(){e.scrollTop=e.scrollHeight,d=e.scrollTop},c=function(){!t||a||(a=requestAnimationFrame(function(){a=0,t&&f()}))},p=function(){i=Date.now(),o=!0,t=!1},h=function(){o=!1},g=function(){var u=e.scrollHeight-e.clientHeight-e.scrollTop,N=d!==null&&e.scrollTop===d;d=null,u>24&&!N?t=!1:u<=4&&(!o||Date.now()-i>400)&&(t=!0)},m=function(u){u.deltaY<0?p():u.deltaY>0&&h()},v=function(u){s=u.touches[0]?u.touches[0].clientY:0},y=function(u){var N=u.touches[0]?u.touches[0].clientY:0;N>s+2?p():N<s-2&&h()},L=function(u){u.key==="ArrowUp"||u.key==="PageUp"||u.key==="Home"?p():(u.key==="ArrowDown"||u.key==="PageDown"||u.key==="End")&&h()};e.addEventListener("scroll",g,{passive:!0}),e.addEventListener("wheel",m,{passive:!0}),e.addEventListener("touchstart",v,{passive:!0}),e.addEventListener("touchmove",y,{passive:!0}),e.addEventListener("keydown",L),e.addEventListener("load",c,!0);var E=new MutationObserver(c);E.observe(e,{childList:!0,subtree:!0,characterData:!0});var S=typeof ResizeObserver>"u"?null:new ResizeObserver(c);return S&&S.observe(e),f(),{el:e,setBusy:function(u){u?e.setAttribute("aria-busy","true"):e.removeAttribute("aria-busy")},toEnd:function(){t=!0,f()},destroy:function(){cancelAnimationFrame(a),e.removeEventListener("scroll",g),e.removeEventListener("wheel",m),e.removeEventListener("touchstart",v),e.removeEventListener("touchmove",y),e.removeEventListener("keydown",L),e.removeEventListener("load",c,!0),E.disconnect(),S&&S.disconnect()}}}function be(){return M("0 0 24 24",[{d:"M21.4 11.1l-8.5 8.5a5.5 5.5 0 01-7.8-7.8l8.5-8.5a3.7 3.7 0 015.2 5.2l-8.5 8.5a1.8 1.8 0 01-2.6-2.6l7.8-7.8",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"}])}function ye(e){e=e||{};var n=e.label||b.attach,r=l("input",{type:"file",hidden:!0,accept:e.accept||null,multiple:e.multiple||null,tabindex:"-1"}),t=l("button",{type:"button",className:"gemboss-ec-attach-btn","aria-label":n,title:e.title||n,disabled:e.disabled||null},be());t.addEventListener("click",function(){r.click()}),r.addEventListener("change",function(){var i=Array.prototype.slice.call(r.files||[]);r.value="",i.length&&e.onFiles&&e.onFiles(i)});var a=document.createDocumentFragment();return a.appendChild(t),a.appendChild(r),a}var W=["full","compact","icon","tight"];function Ne(e){var n=e.querySelector(".gemboss-ec-toolbar-lead"),r=e.querySelector(".gemboss-ec-toolbar");if(n&&n.scrollWidth>n.clientWidth+1||r&&r.scrollWidth>r.clientWidth+1)return!0;var t,a=e.querySelectorAll(".gemboss-ec-chiplabel, .gb-engine-chiplabel");for(t=0;t<a.length;t++)if(a[t].offsetParent!==null&&a[t].scrollWidth>a[t].clientWidth+1)return!0;for(a=e.querySelectorAll(".gemboss-ec-chipctl, .gb-engine-chip"),t=0;t<a.length;t++)if(a[t].offsetParent!==null&&a[t].tagName!=="SELECT"&&a[t].scrollWidth>a[t].clientWidth+1)return!0;return!1}function we(e,n){e&&!k(e)&&(n=e,e=null),n=n||{};var r=Object.assign({},b,n.labels||{}),t=!1,a=l("textarea",{rows:n.rows||1,maxlength:n.maxLength||null,"aria-label":n.label||r.message,placeholder:n.placeholder||null}),i=l("div",{className:"gemboss-ec-toolbar-lead"},n.leading||null),s=n.tools?l("div",{className:"gemboss-ec-toolbar-tools"},n.tools):null,o=l("button",{type:"submit",disabled:!0,"aria-label":r.send,title:r.send},M("0 0 20 20",[{d:"M10.53 4.47a.75.75 0 0 0-1.06 0l-4.5 4.5a.75.75 0 0 0 1.06 1.06l3.22-3.22v8.69a.75.75 0 0 0 1.5 0v-8.69l3.22 3.22a.75.75 0 1 0 1.06-1.06l-4.5-4.5Z"}])),d=l("button",{type:"button",className:"gemboss-ec-stop","aria-label":r.stop,title:r.stop},M("0 0 12 12",[{tag:"rect",x:"1",y:"1",width:"10",height:"10",rx:"2"}])),f=l("div",{className:"gemboss-ec-toolbar"},[i,s,n.status||null,o]),c=l("div",{className:"gemboss-ec-input","data-density":"full"},[n.top||null,a,f]);e&&e.appendChild(c);var p=function(){return!a.disabled&&a.value.trim()!==""&&(!t||n.sendWhileBusy||!n.onStop)},h=function(){var u=t&&!!n.onStop,N=u?d:o,re=u?o:d;re.parentNode&&re.replaceWith(N),o.disabled=!p()},g=function(){p()&&(n.onSend&&n.onSend(a.value),n.thread&&n.thread.toEnd&&n.thread.toEnd(),n.keepValue||(a.value="",m(),h()))},m=function(){if(!(a.offsetParent===null||a.scrollHeight===0)){var u=parseFloat(getComputedStyle(a).maxHeight)||84;a.style.height="auto";var N=a.scrollHeight+(a.offsetHeight-a.clientHeight);a.style.height=Math.min(N,u)+"px",a.style.overflowY=N>u?"auto":"hidden"}};a.addEventListener("input",function(){m(),h(),n.onChange&&n.onChange(a.value)}),a.addEventListener("keydown",function(u){n.onKeyDown&&n.onKeyDown(u),!u.defaultPrevented&&u.key==="Enter"&&!u.shiftKey&&!u.isComposing&&u.keyCode!==229&&(u.preventDefault(),g())}),o.addEventListener("click",function(u){u.preventDefault(),g()}),d.addEventListener("click",function(){n.onStop&&n.onStop(),a.focus()});var v=!1,y=function(){if(!(v||c.offsetParent===null)){v=!0;for(var u=0;u<W.length&&(c.setAttribute("data-density",W[u]),!(u===W.length-1||!Ne(c)));u++);v=!1,n.onDensity&&n.onDensity(c.getAttribute("data-density"))}},L=-1,E=typeof ResizeObserver>"u"?null:new ResizeObserver(function(){c.clientWidth!==L&&(L=c.clientWidth,y(),m())});E&&E.observe(c);var S=new MutationObserver(function(){v||y()});return S.observe(i,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-label","title","aria-label"]}),document.fonts&&document.fonts.ready&&document.fonts.ready.then(y),requestAnimationFrame(function(){y(),m()}),{el:c,textarea:a,value:function(){return a.value},setValue:function(u){a.value=u==null?"":String(u),m(),h()},setBusy:function(u){t=!!u,h()},setDisabled:function(u){a.disabled=!!u,h()},focus:function(){a.focus()},measure:y,destroy:function(){E&&E.disconnect(),S.disconnect(),c.remove()}}}var xe="The bridge on your machine is not responding: it was stopped, or the machine just slept. The panel is checking again; rerun the install command if it does not come back.",Ee="The bridge on your machine rejected this browser's pairing code (it was installed with another code). Pair again in Setup.";function Se(e,n){return e&&typeof e=="object"&&(e.atBridge=!0,n!==void 0&&(e.status=n)),e}function Ae(e,n){return n=n||{},e&&e.name==="AbortError"?{error:null,bridge:!1,stopped:!0}:e&&e.atBridge&&e.status===401?{error:n.rejected||Ee,bridge:!0,stopped:!1}:e&&e.atBridge&&(e instanceof TypeError||e.name==="TimeoutError")?{error:n.gone||xe,bridge:!0,stopped:!1}:{error:e&&e.message||String(e),bridge:!1,stopped:!1}}w.engineMark||(w.engineMark=Y),w.chat={markdown:G,parseBlocks:K,parseInline:F,linkTarget:C,imageSource:H,message:ne,render:me,thread:ve,steps:Z,pending:J,starters:I,meta:X,error:ee,note:_,composer:we,attach:ye,labels:b,atBridge:Se,turnError:Ae}})(typeof globalThis<"u"?globalThis:this);
@@ -11,10 +11,10 @@
11
11
  // · the edge box hands each keystroke to the caller (`onChange(side, raw)`) and says when to commit (`onCommit`, on
12
12
  // blur): the caller decides what an empty edge means, and the admin has three answers to that on purpose.
13
13
  import "../editor.css"; // its own stylesheet travels with it (0.23.0): a surface that never renders it never loads it
14
- import { useEffect, useRef, useState } from "react";
14
+ import { useEffect, useId, useRef, useState } from "react";
15
15
  import type React from "react";
16
16
  import { cssUrl } from "./css-url";
17
- import { swatchState } from "./swatch";
17
+ import { swatchState, swatchWhy } from "./swatch";
18
18
  import { Segmented } from "../segmented";
19
19
 
20
20
  const blurOnEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
@@ -104,12 +104,9 @@ export function ValueControl({ id, label, presets, text, placeholder, suffix, sl
104
104
  {presets.length ? <Seg label={`${label} presets`} value={lit} disabled={disabled} onChange={onPreset}
105
105
  options={presets.map((p) => ({ value: p.value, label: p.label }))} /> : null}
106
106
  <div className="gemboss-ei-value-row">
107
- <span className="gemboss-ei-value-box">
108
- <input id={id} className="gemboss-field gemboss-field--narrow gemboss-ei-inp" inputMode={inputMode} value={text} placeholder={placeholder}
109
- disabled={disabled} aria-label={label} onChange={(e) => onText(e.target.value)} onBlur={onCommit}
110
- onKeyDown={(e) => { onKeyDown?.(e); if (!e.defaultPrevented) blurOnEnter(e); }} />
111
- {suffix ? <span className="gemboss-ei-suffix">{suffix}</span> : null}
112
- </span>
107
+ <UnitField className="gemboss-ei-value-box" unit={suffix} id={id} inputMode={inputMode} value={text} placeholder={placeholder}
108
+ disabled={disabled} aria-label={label} onChange={(e) => onText(e.target.value)} onBlur={onCommit}
109
+ onKeyDown={(e) => { onKeyDown?.(e); if (!e.defaultPrevented) blurOnEnter(e); }} />
113
110
  {slider && onSlide ? <RangeControl min={slider.min} max={slider.max} step={slider.step} value={slider.value} disabled={disabled} onChange={onSlide} /> : null}
114
111
  </div>
115
112
  {note ? <p className="gemboss-ei-hint" role="status">{note}</p> : null}
@@ -117,6 +114,33 @@ export function ValueControl({ id, label, presets, text, placeholder, suffix, sl
117
114
  );
118
115
  }
119
116
 
117
+ // ── UnitField ───────────────────────────────────────────────────────────────────────────────────────────────────────
118
+
119
+ export interface UnitFieldProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "className" | "style"> {
120
+ /** The unit after the value, inside the box ("px", "ms", "%"). Empty draws none. */
121
+ unit?: string;
122
+ /** Classes on the box, e.g. `gemboss-ei-inp` for a row's full-width control, `gemboss-ei-value-box` in ValueControl. */
123
+ className?: string;
124
+ /** Style on the box (a fixed width where a row lays out several controls). */
125
+ style?: React.CSSProperties;
126
+ }
127
+
128
+ /**
129
+ * A number box with its unit INSIDE (0.40.0; control standard, research pickers-2026-10-16 §13b U1), as Polaris TextField's
130
+ * `suffix`: the kit's field group (`.gemboss-field--group`, the box) around a bare input and `.gemboss-field__suffix`. Drawn beside
131
+ * the box, the unit was a second edge: 71 rows of the element panel stopped short of the panel's right edge. The box is a <label>,
132
+ * so a click or a tap anywhere in it, the unit included, focuses the input (one 44px target under a finger), as the kit's other
133
+ * field groups do. One owner for the shape: FieldRenderer's number, ValueControl's box and the theme inspector's design rows.
134
+ */
135
+ export function UnitField({ unit, className, style, ...input }: UnitFieldProps) {
136
+ return (
137
+ <label className={`gemboss-field gemboss-field--group${className ? ` ${className}` : ""}`} style={style}>
138
+ <input {...input} className="gemboss-field__bare" />
139
+ {unit ? <span className="gemboss-field__suffix">{unit}</span> : null}
140
+ </label>
141
+ );
142
+ }
143
+
120
144
  // ── ColorControl ────────────────────────────────────────────────────────────────────────────────────────────────────
121
145
 
122
146
  export interface ColorControlProps {
@@ -139,15 +163,25 @@ export interface ColorControlProps {
139
163
  export function ColorControl({ id, value, onChange, disabled, label, placeholder }: ColorControlProps) {
140
164
  const [draft, setDraft] = useState(value);
141
165
  useEffect(() => { setDraft(value); }, [value]);
142
- const { swatch, usable } = swatchState(draft);
166
+ // NOT SET IS NOT BLACK (control standard §13b U7, Chris 20/10/2026): an empty colour drew the swatch black beside "Not set", so a
167
+ // seller read a colour nobody chose. Empty, the swatch is white with the slash a colour picker draws for "none" (editor.css
168
+ // `[data-empty]`), and opens on white.
169
+ const empty = !draft.trim();
170
+ const { swatch, usable } = swatchState(draft, "#ffffff");
171
+ // Why the swatch is off, said under it, read with it (a tooltip alone reached neither a finger nor the keys). The COMMITTED
172
+ // value's reason, and kept while the panel saves (it disables every field): taken from the draft or dropped while saving, the
173
+ // line came and went on every write and while a hex was typed (review of #4636). A draft that is a whole hex has none.
174
+ const why = usable ? null : swatchWhy(value);
175
+ const whyId = useId();
143
176
  return (
144
177
  <div className="gemboss-ei-color">
145
- <input type="color" className="gemboss-ei-swatch" aria-label={`${label} swatch`} value={swatch}
178
+ <input type="color" className="gemboss-ei-swatch" aria-label={`${label} swatch`} value={swatch} data-empty={empty ? "" : undefined}
146
179
  disabled={disabled || !usable}
147
- title={usable ? undefined : `${draft} — not a plain 6-digit hex; edit it in the box below`}
180
+ title={usable ? undefined : swatchWhy(draft) ?? undefined} aria-describedby={why ? whyId : undefined}
148
181
  onChange={(e) => { setDraft(e.target.value); onChange(e.target.value); }} />
149
- <input id={id} className="gemboss-field gemboss-ei-inp" placeholder={placeholder ?? "#000000 / var(--…)"} value={draft} disabled={disabled}
182
+ <input id={id} className="gemboss-field gemboss-ei-inp" placeholder={placeholder ?? "Not set"} value={draft} disabled={disabled}
150
183
  onChange={(e) => setDraft(e.target.value)} onBlur={() => onChange(draft)} onKeyDown={blurOnEnter} />
184
+ {why ? <p id={whyId} className="gemboss-ei-color-why">{why}</p> : null}
151
185
  </div>
152
186
  );
153
187
  }
@@ -254,7 +288,9 @@ export function EdgeBox({ label, values, placeholders, onChange, onCommit, disab
254
288
 
255
289
  // ── ImageControl ────────────────────────────────────────────────────────────────────────────────────────────────────
256
290
 
257
- /** A dashed pill button (`.gemboss-ei-addchip`): an image slot's Library / Generate / Clear, a panel's "+ Add". */
291
+ /** A field's own button (an image slot's Library / Generate / Clear, a link's Choose, a panel's "+ Add"): the kit's `.ui-btn`, a Polaris
292
+ * secondary button (28px, 12px / 550, radius 8; 44px under a finger). It was a dashed pill (`.gemboss-ei-addchip`, 27px, 11px / 600,
293
+ * radius 999): control standard U3, research pickers-2026-10-16 §13b. */
258
294
  export function ChipButton({ disabled, expanded, onClick, children }: {
259
295
  disabled?: boolean;
260
296
  /** `aria-expanded`, for a chip that opens something under it (the Library grid). */
@@ -262,7 +298,7 @@ export function ChipButton({ disabled, expanded, onClick, children }: {
262
298
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
263
299
  children: React.ReactNode;
264
300
  }) {
265
- return <button type="button" className="gemboss-ei-addchip" disabled={disabled} aria-expanded={expanded} onClick={onClick}>{children}</button>;
301
+ return <button type="button" className="ui-btn" disabled={disabled} aria-expanded={expanded} onClick={onClick}>{children}</button>;
266
302
  }
267
303
 
268
304
  export interface ImageControlProps {
@@ -36,10 +36,10 @@ export {
36
36
  type FieldProps, type InspectorGroupProps, type PanelHeaderProps, type PanelTab, type PanelTabsProps,
37
37
  } from "./panel";
38
38
  export {
39
- ChipButton, ColorControl, EdgeBox, ImageControl, RangeControl, Seg, ValueControl,
40
- type ColorControlProps, type EdgeBoxProps, type EdgeSide, type ImageControlProps, type RangeControlProps, type SegOption, type SegProps, type ValueControlProps,
39
+ ChipButton, ColorControl, EdgeBox, ImageControl, RangeControl, Seg, UnitField, ValueControl,
40
+ type ColorControlProps, type EdgeBoxProps, type EdgeSide, type ImageControlProps, type RangeControlProps, type SegOption, type SegProps, type UnitFieldProps, type ValueControlProps,
41
41
  } from "./controls";
42
- export { swatchState, type SwatchState } from "./swatch";
42
+ export { swatchState, swatchWhy, type SwatchState } from "./swatch";
43
43
  export { cssUrl } from "./css-url";
44
44
  // Step 4 (0.22.0): the page's layers and the drawer (`layers`, stateless; the page editor draws them), a list whose rows
45
45
  // reorder (`sortable`, "use client": a drag holds state; no admin screen uses it), and the element outline (`tree`, the
@@ -112,7 +112,7 @@ export function PanelAction({ label, title, disabled, onClick, children }: {
112
112
  // ── InspectorGroup ──────────────────────────────────────────────────────────────────────────────────────────────────
113
113
 
114
114
  export interface InspectorGroupProps {
115
- /** The header: 11px / 700, uppercase, muted (`.gemboss-ei-gh`). */
115
+ /** The header: 13px / 650, sentence case (`.gemboss-ei-gh`). */
116
116
  title?: React.ReactNode;
117
117
  /** A short aside after the title, in the header's own line ("· all screens"). Collapsible groups only. */
118
118
  note?: React.ReactNode;
@@ -129,15 +129,21 @@ export interface InspectorGroupProps {
129
129
  collapsible?: boolean;
130
130
  /** A muted line after the body (`p.gemboss-ei-hint`). */
131
131
  hint?: React.ReactNode;
132
+ /**
133
+ * `data-*` attributes on the group itself (`.gemboss-ei-grp`, or the static `section`): what a page finds the group by, without a
134
+ * wrapper. A wrapper made every group the `:last-child` of its own parent, so the divider between groups was dropped (control
135
+ * standard §13b, 19/10/2026).
136
+ */
137
+ attrs?: Record<`data-${string}`, string>;
132
138
  children?: React.ReactNode;
133
139
  }
134
140
 
135
- export function InspectorGroup({ title, note, open, defaultOpen = true, onOpenChange, collapsible = true, hint, children }: InspectorGroupProps) {
141
+ export function InspectorGroup({ title, note, open, defaultOpen = true, onOpenChange, collapsible = true, hint, attrs, children }: InspectorGroupProps) {
136
142
  const [own, setOwn] = useState(defaultOpen);
137
143
  const hintNode = hint ? <p className="gemboss-ei-hint">{hint}</p> : null;
138
144
  if (!collapsible) {
139
145
  return (
140
- <section className="gemboss-ei-group">
146
+ <section className="gemboss-ei-group" {...attrs}>
141
147
  {title ? <h4 className="gemboss-ei-grouphead">{title}</h4> : null}
142
148
  {children}
143
149
  {hintNode}
@@ -146,7 +152,7 @@ export function InspectorGroup({ title, note, open, defaultOpen = true, onOpenCh
146
152
  }
147
153
  const isOpen = open ?? own;
148
154
  return (
149
- <div className="gemboss-ei-grp">
155
+ <div className="gemboss-ei-grp" {...attrs}>
150
156
  <button type="button" className="gemboss-ei-gh" data-open={isOpen ? "open" : "closed"} aria-expanded={isOpen}
151
157
  onClick={() => { if (open === undefined) setOwn(!isOpen); onOpenChange?.(!isOpen); }}>
152
158
  <span className="gemboss-ei-chev" aria-hidden>{isOpen ? "▾" : "▸"}</span>{title}
@@ -16,10 +16,20 @@
16
16
  //
17
17
  // Rows are kept by KEY (useKeyedRows), not by index: a row's open state and its controls' unsaved typing follow it when it
18
18
  // moves, and a field writes to its row by key. The drag is useRowDrag (sortable.tsx), the same rule as the Layers list.
19
+ //
20
+ // ITEMS IN A POPOVER (item management IM1a, Chris 20/10/2026, GemPages 7.5's "Item management" read in its code): with
21
+ // `editIn="popover"` the list stays one line per row and the row picked shows its controls in a layer under the list (the
22
+ // browser's top layer, placed like the kit's menus, menu-layer.ts), one row at a time; the other rows stay in view and a click
23
+ // on one moves the layer to it. It closes on its ✕, on Escape (the focus goes back to the row) or on the row clicked again,
24
+ // never on a click elsewhere: a row's controls open pickers and dialogs of their own. The host hears the row picked
25
+ // (`onActive`), to show that item on the canvas. A row's picture (`thumb`, its first image) sits before its name.
26
+ // One row: its controls show in place, open, as the inline list draws it (nothing to choose between; a layer would cost a click).
27
+ // No popover API: the row picked opens in place, as the inline list does.
19
28
  import "../editor.css"; // the field and the 44px rule under a finger (`.gemboss-ei-fld`)
20
29
  import "../editor-repeater.css"; // its own part (16/10/2026): a surface that never draws a list never loads it
21
- import { useEffect, useId, useRef, useState } from "react";
30
+ import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
22
31
  import type React from "react";
32
+ import { canFloatMenus, placeFloating } from "../menu-layer";
23
33
  import { useRowDrag } from "./sortable";
24
34
 
25
35
  let lastKey = 0;
@@ -139,9 +149,11 @@ export interface RepeaterListProps {
139
149
  title: (index: number) => string;
140
150
  /** A row's controls, drawn while it is open. */
141
151
  body: (index: number) => React.ReactNode;
142
- /** `to` is the index after the move. */
143
- onMove: (from: number, to: number) => void;
144
- onRemove: (index: number) => void;
152
+ /** `to` is the index after the move. Absent: the rows keep their order (no grip, no ↑ ↓, no Alt+arrows): a list whose order
153
+ * is kept elsewhere (items read from Content, in Content's order). */
154
+ onMove?: (from: number, to: number) => void;
155
+ /** Absent: no remove button (rows that are not this list's to remove). */
156
+ onRemove?: (index: number) => void;
145
157
  /** Absent: no duplicate button (a list whose rows must differ). */
146
158
  onDuplicate?: (index: number) => void;
147
159
  /** Absent: no add button. */
@@ -158,6 +170,12 @@ export interface RepeaterListProps {
158
170
  /** 44px buttons with a mouse too (`.gemboss-ei-rep--touch`): a surface whose every control is 44px (the page editor's
159
171
  * content panel, check:page-revision-touchable). Under a finger they are 44px everywhere. */
160
172
  touch?: boolean;
173
+ /** A row's picture before its name (its first image field), 20px; empty: none. */
174
+ thumb?: (index: number) => string | undefined;
175
+ /** Where a row's controls show: folded open under its header (default), or one row at a time in a layer under the list. */
176
+ editIn?: "inline" | "popover";
177
+ /** Popover mode: the row picked (null when none), so the host can show that item. */
178
+ onActive?: (index: number | null) => void;
161
179
  }
162
180
 
163
181
  // A function, not an element built at import: a page that compiles JSX against a global React (the admin's gates) sets that
@@ -165,8 +183,20 @@ export interface RepeaterListProps {
165
183
  const DuplicateIcon = () => <svg viewBox="0 0 16 16" width="13" height="13" aria-hidden fill="none" stroke="currentColor" strokeWidth="1.4"><rect x="5.5" y="5.5" width="8" height="8" rx="1.5" /><path d="M10.5 3.5v-.5A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v5A1.5 1.5 0 0 0 4 9.5h.5" /></svg>;
166
184
 
167
185
  /** `.gemboss-ei-fld.gemboss-ei-rep` › label (+ count) › `.gemboss-ei-rep-list` › one `.gemboss-ei-rep-item` per row › add. */
168
- export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove, onDuplicate, onAdd, addLabel = "+ Add item", canAdd = true, disabled, empty, rowAttrs, touch }: RepeaterListProps) {
186
+ export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove, onDuplicate, onAdd, addLabel = "+ Add item", canAdd = true, disabled, empty, rowAttrs, touch, thumb, editIn = "inline", onActive }: RepeaterListProps) {
169
187
  const uid = useId();
188
+ // The layer only where there is a row to choose: a list of one shows it in place.
189
+ const popover = editIn === "popover" && keys.length > 1;
190
+ // Popover mode: the row picked, by key; the layer floats only where the browser has the popover API (read after the first
191
+ // paint, so the server's markup and the first client render agree), else the row opens in place.
192
+ const [active, setActive] = useState<string | null>(null);
193
+ const [canFloat, setCanFloat] = useState(false);
194
+ useEffect(() => { setCanFloat(canFloatMenus()); }, []);
195
+ const activeKey = active !== null && keys.includes(active) ? active : null;
196
+ const pick = (key: string | null) => { setActive(key); onActive?.(key === null ? null : keys.indexOf(key)); };
197
+ // A row removed while picked: its layer goes, and the host hears it.
198
+ useEffect(() => { if (active !== null && !keys.includes(active)) { setActive(null); onActive?.(null); } }, [keys, active, onActive]);
199
+ const popRef = useRef<HTMLDivElement>(null);
170
200
  const [open, setOpen] = useState<ReadonlySet<string>>(() => new Set(keys.length <= 1 ? keys : []));
171
201
  // A row that arrives after the list was drawn opens. Compared by content: a caller may hand a new array each render.
172
202
  const [seen, setSeen] = useState<readonly string[]>(keys);
@@ -175,8 +205,10 @@ export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove,
175
205
  setSeen(keys);
176
206
  if (fresh.length) setOpen((o) => new Set([...o, ...fresh]));
177
207
  }
178
- const toggle = (key: string) => setOpen((o) => { const next = new Set(o); if (!next.delete(key)) next.add(key); return next; });
179
- const drag = useRowDrag(keys, onMove);
208
+ const toggle = (key: string) => popover
209
+ ? pick(activeKey === key ? null : key)
210
+ : setOpen((o) => { const next = new Set(o); if (!next.delete(key)) next.add(key); return next; });
211
+ const drag = useRowDrag(keys, onMove ?? (() => {}));
180
212
  // A keyboard move keeps the focus on the moved row's name once it has landed (as SortableLayerList).
181
213
  const refocus = useRef<{ key: string; from: number } | null>(null);
182
214
  const listRef = useRef<HTMLDivElement>(null);
@@ -190,47 +222,85 @@ export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove,
190
222
  const row = [...(listRef.current?.children ?? [])].find((r) => (r as HTMLElement).dataset.key === want.key);
191
223
  row?.querySelector<HTMLButtonElement>(".gemboss-ei-rep-toggle")?.focus();
192
224
  }, [keys]);
225
+ // The layer under the list: in the top layer, as wide as the list, placed again when the panel or the page scrolls or the
226
+ // window resizes (a scroll inside the layer does not count), so it never floats away from its list.
227
+ useLayoutEffect(() => {
228
+ const el = popRef.current, list = listRef.current;
229
+ if (!el || !list || activeKey === null) return;
230
+ if (el.getAttribute("popover") !== "manual") el.setAttribute("popover", "manual");
231
+ if (!el.matches(":popover-open")) el.showPopover();
232
+ const place = () => { el.style.width = `${Math.round(list.getBoundingClientRect().width)}px`; placeFloating(el, list); };
233
+ place();
234
+ el.focus({ preventScroll: true });
235
+ const onMove = (e: Event) => { if (e.target instanceof Node && el.contains(e.target)) return; place(); };
236
+ window.addEventListener("scroll", onMove, true);
237
+ window.addEventListener("resize", onMove);
238
+ return () => {
239
+ window.removeEventListener("scroll", onMove, true);
240
+ window.removeEventListener("resize", onMove);
241
+ if (el.matches(":popover-open")) el.hidePopover();
242
+ };
243
+ }, [activeKey, canFloat, keys.length]);
244
+ const closeLayer = () => {
245
+ const key = activeKey;
246
+ pick(null);
247
+ const row = [...(listRef.current?.children ?? [])].find((r) => (r as HTMLElement).dataset.key === key);
248
+ row?.querySelector<HTMLButtonElement>(".gemboss-ei-rep-toggle")?.focus();
249
+ };
250
+ const floating = popover && canFloat;
251
+ const rootClass = ["gemboss-ei-fld gemboss-ei-rep", touch ? "gemboss-ei-rep--touch" : "", popover ? "gemboss-ei-rep--pop" : ""].filter(Boolean).join(" ");
193
252
 
194
253
  return (
195
- <div className={touch ? "gemboss-ei-fld gemboss-ei-rep gemboss-ei-rep--touch" : "gemboss-ei-fld gemboss-ei-rep"}>
254
+ <div className={rootClass}>
196
255
  <label>{label}{keys.length ? <span className="gemboss-ei-rep-count">{keys.length}</span> : null}</label>
197
256
  {hint}
198
257
  {keys.length === 0 ? (empty ?? <p className="gemboss-ei-hint">No items yet.</p>) : (
199
258
  <div className="gemboss-ei-rep-list" ref={listRef}>
200
259
  {keys.map((key, i) => {
201
- const isOpen = open.has(key);
260
+ const picked = popover && activeKey === key;
261
+ // Inline: its own fold. Popover: the row picked opens in place only where nothing can float.
262
+ const isOpen = popover ? picked && !canFloat : open.has(key);
202
263
  const name = title(i) || `Item ${i + 1}`;
203
264
  const bodyId = `${uid}-${key}`;
265
+ const src = thumb?.(i);
204
266
  return (
205
- <div key={key} data-key={key} className={drag.dragging === key ? "gemboss-ei-rep-item is-dragging" : "gemboss-ei-rep-item"}
206
- data-drop={drag.dropAt(key)} {...rowAttrs?.(i)}
267
+ <div key={key} data-key={key} className={["gemboss-ei-rep-item", drag.dragging === key ? "is-dragging" : "", picked ? "is-active" : ""].filter(Boolean).join(" ")}
268
+ data-drop={onMove ? drag.dropAt(key) : undefined} {...rowAttrs?.(i)}
207
269
  // A list that went off mid-drag (a save started) takes no drop: the move would race that save.
208
- onDragOver={disabled ? undefined : drag.over(key)} onDrop={disabled ? drag.end : drag.drop}>
270
+ onDragOver={disabled || !onMove ? undefined : drag.over(key)} onDrop={!onMove ? undefined : disabled ? drag.end : drag.drop}>
209
271
  <div className="gemboss-ei-rep-head">
210
272
  {/* Kept while the list is off (a write in flight), inert: taking it away moved every row's name 16px on each save. */}
211
- <span className="gemboss-ei-rep-grip" aria-hidden draggable={!disabled} title={disabled ? undefined : "Drag to reorder"}
212
- onDragStart={disabled ? undefined : (e) => { const row = e.currentTarget.closest(".gemboss-ei-rep-item"); if (row) e.dataTransfer.setDragImage(row, 12, 12); drag.start(key)(e); }}
213
- onDragEnd={drag.end}>⋮⋮</span>
214
- <button type="button" className="gemboss-ei-rep-toggle" aria-expanded={isOpen} aria-controls={isOpen ? bodyId : undefined}
215
- aria-keyshortcuts={disabled ? undefined : "Alt+ArrowUp Alt+ArrowDown"} onClick={() => toggle(key)}
273
+ {onMove ? (
274
+ <span className="gemboss-ei-rep-grip" aria-hidden draggable={!disabled} title={disabled ? undefined : "Drag to reorder"}
275
+ onDragStart={disabled ? undefined : (e) => { const row = e.currentTarget.closest(".gemboss-ei-rep-item"); if (row) e.dataTransfer.setDragImage(row, 12, 12); drag.start(key)(e); }}
276
+ onDragEnd={drag.end}>⋮⋮</span>
277
+ ) : null}
278
+ <button type="button" className="gemboss-ei-rep-toggle" aria-expanded={popover ? picked : isOpen}
279
+ aria-controls={picked && floating ? `${uid}-pop` : isOpen ? bodyId : undefined}
280
+ aria-keyshortcuts={disabled || !onMove ? undefined : "Alt+ArrowUp Alt+ArrowDown"} onClick={() => toggle(key)}
216
281
  onKeyDown={(e) => {
217
- if (disabled || !e.altKey || (e.key !== "ArrowUp" && e.key !== "ArrowDown")) return;
282
+ if (disabled || !onMove || !e.altKey || (e.key !== "ArrowUp" && e.key !== "ArrowDown")) return;
218
283
  const to = e.key === "ArrowUp" ? i - 1 : i + 1;
219
284
  if (to < 0 || to >= keys.length) return;
220
285
  e.preventDefault();
221
286
  refocus.current = { key, from: i };
222
287
  onMove(i, to);
223
288
  }}>
289
+ {src ? <img className="gemboss-ei-rep-thumb" src={src} alt="" loading="lazy" /> : null}
224
290
  <span className="gemboss-ei-rep-title">{name}</span>
225
291
  </button>
226
- <span className="gemboss-ei-rep-acts">
227
- <button type="button" title="Move up" aria-label={`Move ${label} item ${i + 1} up`} disabled={disabled || i === 0} onClick={() => onMove(i, i - 1)}>↑</button>
228
- <button type="button" title="Move down" aria-label={`Move ${label} item ${i + 1} down`} disabled={disabled || i === keys.length - 1} onClick={() => onMove(i, i + 1)}>↓</button>
229
- {onDuplicate ? (
230
- <button type="button" title="Duplicate" aria-label={`Duplicate ${label} item ${i + 1}`} disabled={disabled || !canAdd} onClick={() => onDuplicate(i)}><DuplicateIcon /></button>
231
- ) : null}
232
- <button type="button" title="Remove" aria-label={`Remove ${label} item ${i + 1}`} disabled={disabled} onClick={() => onRemove(i)}>✕</button>
233
- </span>
292
+ {onMove || onDuplicate || onRemove ? (
293
+ <span className="gemboss-ei-rep-acts">
294
+ {onMove ? <>
295
+ <button type="button" title="Move up" aria-label={`Move ${label} item ${i + 1} up`} disabled={disabled || i === 0} onClick={() => onMove(i, i - 1)}>↑</button>
296
+ <button type="button" title="Move down" aria-label={`Move ${label} item ${i + 1} down`} disabled={disabled || i === keys.length - 1} onClick={() => onMove(i, i + 1)}>↓</button>
297
+ </> : null}
298
+ {onDuplicate ? (
299
+ <button type="button" title="Duplicate" aria-label={`Duplicate ${label} item ${i + 1}`} disabled={disabled || !canAdd} onClick={() => onDuplicate(i)}><DuplicateIcon /></button>
300
+ ) : null}
301
+ {onRemove ? <button type="button" title="Remove" aria-label={`Remove ${label} item ${i + 1}`} disabled={disabled} onClick={() => onRemove(i)}>✕</button> : null}
302
+ </span>
303
+ ) : null}
234
304
  </div>
235
305
  {isOpen ? <div id={bodyId} className="gemboss-ei-rep-body" role="group" aria-label={name}>{body(i)}</div> : null}
236
306
  </div>
@@ -238,7 +308,23 @@ export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove,
238
308
  })}
239
309
  </div>
240
310
  )}
241
- {onAdd ? <button type="button" className="gemboss-ei-rep-add" disabled={disabled || !canAdd} onClick={onAdd}>{addLabel}</button> : null}
311
+ {onAdd ? <button type="button" className="ui-btn gemboss-ei-rep-add" disabled={disabled || !canAdd} onClick={onAdd}>{addLabel}</button> : null}
312
+ {floating && activeKey !== null ? (() => {
313
+ const i = keys.indexOf(activeKey);
314
+ const name = title(i) || `Item ${i + 1}`;
315
+ return (
316
+ // A child of the list in the DOM (Tab order, React context, the host's providers), painted in the top layer.
317
+ <div ref={popRef} id={`${uid}-pop`} className="gemboss-ei-rep-pop gemboss-popover-surface" role="dialog" aria-label={name} tabIndex={-1}
318
+ onKeyDown={(e) => { if (e.key === "Escape") { e.stopPropagation(); closeLayer(); } }}>
319
+ <div className="gemboss-ei-rep-pophead">
320
+ <span className="gemboss-ei-rep-title">{name}</span>
321
+ <span className="gemboss-ei-rep-popat">{i + 1} / {keys.length}</span>
322
+ <button type="button" aria-label={`Close ${name}`} onClick={closeLayer}>✕</button>
323
+ </div>
324
+ {body(i)}
325
+ </div>
326
+ );
327
+ })() : null}
242
328
  </div>
243
329
  );
244
330
  }
@@ -43,3 +43,23 @@ export function swatchState(value: string, whenEmpty = '#000000'): SwatchState {
43
43
  if (!value) return { swatch: whenEmpty, usable: true };
44
44
  return SWATCHABLE.test(value) ? { swatch: value, usable: true } : { swatch: '#000000', usable: false };
45
45
  }
46
+
47
+ /** The theme's colour tokens a value may name (`var(--gem-color-accent)`), as a seller reads them. */
48
+ const TOKEN_NAMES: Record<string, string> = { text: "Text", accent: "Accent", "accent-text": "Accent text", muted: "Muted", background: "Background",
49
+ surface: "Surface", border: "Border", success: "Success", warning: "Warning", danger: "Danger" };
50
+ const tokenName = (t: string) => TOKEN_NAMES[t] ?? t.charAt(0).toUpperCase() + t.slice(1).replace(/-/g, " ");
51
+
52
+ /**
53
+ * WHY THE SWATCH IS OFF, said where a seller sees it (Claude Design: a disabled control says why). The editors' selection toolbar
54
+ * writes the theme's own colours as tokens (`var(--gem-color-accent)`), so every colour picked there turned its field's swatch grey,
55
+ * with only a tooltip a finger or the keyboard never reaches (inventory of 19/10/2026). Null when the swatch shows the value.
56
+ */
57
+ export function swatchWhy(value: string): string | null {
58
+ // Exactly when the swatch is off (swatchState's one rule): a padded hex is off too, and says so (review of #4636).
59
+ if (swatchState(value).usable) return null;
60
+ const v = value.trim();
61
+ const token = /^var\(\s*--gem-color-([a-z-]+)\s*\)$/i.exec(v)?.[1]?.toLowerCase();
62
+ if (token) return `Theme color: ${tokenName(token)}. Type a hex to use your own.`;
63
+ if (/^var\(/i.test(v)) return "A theme value. Type a hex to use your own.";
64
+ return "Kept as written: the swatch picks 6-digit hex colors only.";
65
+ }