sova-devtools 0.1.3

In-app DevTools bar for Sova (HTML inject, SSE timeline, request snapshots)
Documentation
(function(){"use strict";const f="sova.devtools.v2",m="sova.devtools.spark",g="sova.devtools.panelH",M="sova.devtools",N="sova-dt-panel-h";function _(e){return Math.min(800,Math.max(220,Math.round(e)))}const b={open:!1,tab:"request"};function i(){try{const e=sessionStorage.getItem(f);if(!e)return{...b};const t=JSON.parse(e);return{open:!!t.open,tab:t.tab||"request"}}catch{return{...b}}}function A(e){try{sessionStorage.setItem(f,JSON.stringify(e))}catch{}}function l(e){const t={...i(),...e};return A(t),t}function h(){try{const e=sessionStorage.getItem(m);if(!e)return[];const t=JSON.parse(e);return Array.isArray(t)?t.filter(n=>typeof n=="number").slice(-40):[]}catch{return[]}}function B(e){const t=[...h(),e].slice(-40);try{sessionStorage.setItem(m,JSON.stringify(t))}catch{}return t}function x(){try{const e=Number(sessionStorage.getItem(g));if(Number.isFinite(e)&&e>=220&&e<=800)return e}catch{}return 420}function C(e){try{sessionStorage.setItem(g,String(Math.round(e)))}catch{}}const p="sova-dt-frame",y="sova-dt-bar",H="/_devtools/app?embed=1",P="/_devtools/app",F=36,o={bg:"#0b0d12",surface:"#141820",border:"#2c3340",borderStrong:"#454d5e",text:"#eef0f4",muted:"#9aa3b2",faint:"#6b7382",accent:"#3dd68c",ok:"#3dd68c",warn:"#e6b35a",err:"#f07178",info:"#6cb6ff",mono:'"IBM Plex Mono", ui-monospace, Menlo, Consolas, monospace',sans:'"IBM Plex Sans", ui-sans-serif, system-ui, sans-serif'};function O(){return document.getElementById("sova-devtools")}function $(){const e=O();if(!e)return null;const t=e.dataset,n=t.statusClass;return{type:"page",snap:t.snap||"",status:Number(t.status||0),statusClass:n==="warn"||n==="err"?n:"ok",ms:Number(t.ms||0),sql:Number(t.sql||0),errors:Number(t.errors||0),path:location.pathname}}function v(){try{return new BroadcastChannel(M)}catch{return null}}function u(e){v()?.postMessage(e)}function c(e,t,n,s=o.text){return`<button type="button" data-dt-toggle style="
    display:inline-flex;align-items:center;gap:6px;min-height:32px;
    padding:4px 10px;border:1px solid ${o.border};border-left:3px solid ${n};
    border-radius:6px;background:${o.surface};color:${o.text};cursor:pointer;
    font-family:${o.sans};font-size:12px;white-space:nowrap;
  "><span style="font-size:9px;text-transform:uppercase;letter-spacing:.06em;color:${o.faint}">${e}</span><span style="font-family:${o.mono};font-weight:500;color:${s}">${t}</span></button>`}function z(e){const t=e.length?e:[0],n=72,s=16,r=2,a=Math.max(...t,1),k=Math.min(...t,0),j=Math.max(a-k,1e-6),I=t.length,q=t.map((D,L)=>{const T=I===1?n/2:L/(I-1)*(n-r*2)+r,J=s-r-(D-k)/j*(s-r*2);return`${L===0?"M":"L"}${T.toFixed(1)},${J.toFixed(1)}`}).join(" ");return`<svg width="${n}" height="${s}" viewBox="0 0 ${n} ${s}" aria-hidden="true" style="display:block"><path d="${q}" fill="none" stroke="${o.accent}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`}function R(e){let t=document.getElementById(y);t||(t=document.createElement("div"),t.id=y,t.setAttribute("role","toolbar"),Object.assign(t.style,{position:"fixed",left:"0",right:"0",bottom:"0",height:"36px",display:"flex",alignItems:"center",gap:"6px",padding:"4px 8px",background:o.bg,borderTop:`1px solid ${o.borderStrong}`,zIndex:"2147483001",boxSizing:"border-box",overflowX:"auto"}),document.documentElement.appendChild(t),t.addEventListener("click",s=>{const r=s.target;if(r.closest("[data-dt-tab]")){s.stopPropagation(),window.open(P,"_blank","noopener,noreferrer");const a=$();a&&u(a);return}(r.closest("[data-dt-toggle]")||r.closest("[data-dt-brand]"))&&U()}),window.addEventListener("keydown",s=>{if(s.key==="Escape"&&i().open){const a=l({open:!1});d(!1),w(a)}}));const n=e.statusClass==="err"?o.err:e.statusClass==="warn"?o.warn:o.ok;t.innerHTML=`
    <button type="button" data-dt-brand style="
      min-height:32px;padding:0 10px;border:1px solid ${o.border};border-radius:6px;
      background:${o.surface};color:${o.accent};font-weight:700;font-size:13px;
      font-family:${o.sans};cursor:pointer;letter-spacing:.02em;
    ">Sova</button>
    ${c("status",String(e.status),n,n)}
    ${c("time",e.ms.toFixed(1)+"ms",o.borderStrong)}
    ${c("sql",String(e.sql),o.info,o.info)}
    ${c("err",String(e.errors),e.errors?o.err:o.borderStrong,e.errors?o.err:o.text)}
    <span style="display:inline-flex;align-items:center;margin-left:4px">${z(h())}</span>
    <span style="margin-left:auto;display:flex;gap:6px;align-items:center">
      <button type="button" data-dt-tab style="
        min-height:32px;padding:0 10px;border:1px solid ${o.border};border-radius:6px;
        background:${o.surface};color:${o.muted};font-size:11px;font-family:${o.sans};cursor:pointer;
      ">New tab</button>
    </span>
  `}function S(e,t){const n=_(t);e.style.height=`${n}px`,C(n)}function d(e){let t=document.getElementById(p);if(!e){t&&(t.hidden=!0);return}t?S(t,x()):(t=document.createElement("iframe"),t.id=p,t.title="Sova DevTools",t.src=H,Object.assign(t.style,{position:"fixed",left:"0",right:"0",bottom:`${F}px`,height:`${x()}px`,width:"100%",border:"0",zIndex:"2147483001",background:o.bg,boxShadow:"0 -12px 40px rgba(0,0,0,.45)"}),document.documentElement.appendChild(t)),t.hidden=!1}function w(e){u({type:"ui",open:e.open,tab:e.tab})}function U(){const e=!i().open,t=l({open:e});d(e),w(t)}function E(){const e=$();e&&(e.ms>0&&B(e.ms),R(e),u(e),d(i().open),v()?.addEventListener("message",t=>{const n=t.data;if(!n||typeof n!="object"||n.type!=="ui")return;const s=l({open:n.open,...n.tab?{tab:n.tab}:{}});d(s.open)}),window.addEventListener("message",t=>{const n=t.data;if(!n||typeof n!="object"||n.type!==N)return;const s=document.getElementById(p);!s||typeof n.height!="number"||S(s,n.height)}),window.addEventListener("pageshow",t=>{t.persisted&&location.reload()}))}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",E):E()})();