sova-devtools 0.1.7

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 _(t){return Math.min(800,Math.max(220,Math.round(t)))}const b={open:!1,tab:"request"};function i(){try{const t=sessionStorage.getItem(f);if(!t)return{...b};const e=JSON.parse(t);return{open:!!e.open,tab:e.tab||"request"}}catch{return{...b}}}function A(t){try{sessionStorage.setItem(f,JSON.stringify(t))}catch{}}function l(t){const e={...i(),...t};return A(e),e}function h(){try{const t=sessionStorage.getItem(m);if(!t)return[];const e=JSON.parse(t);return Array.isArray(e)?e.filter(n=>typeof n=="number").slice(-40):[]}catch{return[]}}function B(t){const e=[...h(),t].slice(-40);try{sessionStorage.setItem(m,JSON.stringify(e))}catch{}return e}function x(){try{const t=Number(sessionStorage.getItem(g));if(Number.isFinite(t)&&t>=220&&t<=800)return t}catch{}return 420}function C(t){try{sessionStorage.setItem(g,String(Math.round(t)))}catch{}}const u="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 t=O();if(!t)return null;const e=t.dataset,n=e.statusClass;return{type:"page",snap:e.snap||"",status:Number(e.status||0),statusClass:n==="warn"||n==="err"?n:"ok",ms:Number(e.ms||0),sql:Number(e.sql||0),errors:Number(e.errors||0),path:location.pathname}}function v(){try{return new BroadcastChannel(M)}catch{return null}}function p(t){v()?.postMessage(t)}function c(t,e,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}">${t}</span><span style="font-family:${o.mono};font-weight:500;color:${s}">${e}</span></button>`}function z(t){const e=t.length?t:[0],n=72,s=16,r=2,a=Math.max(...e,1),k=Math.min(...e,0),j=Math.max(a-k,1e-6),I=e.length,q=e.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(t){let e=document.getElementById(y);e||(e=document.createElement("div"),e.id=y,e.setAttribute("role","toolbar"),Object.assign(e.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(e),e.addEventListener("click",s=>{const r=s.target;if(r.closest("[data-dt-tab]")){s.stopPropagation(),window.open(P,"_blank","noopener,noreferrer");const a=$();a&&p(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=t.statusClass==="err"?o.err:t.statusClass==="warn"?o.warn:o.ok;e.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(t.status),n,n)}
    ${c("time",t.ms.toFixed(1)+"ms",o.borderStrong)}
    ${c("sql",String(t.sql),o.info,o.info)}
    ${c("err",String(t.errors),t.errors?o.err:o.borderStrong,t.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(t,e){const n=_(e);t.style.height=`${n}px`,C(n)}function d(t){let e=document.getElementById(u);if(!t){e&&(e.hidden=!0);return}e?S(e,x()):(e=document.createElement("iframe"),e.id=u,e.title="Sova DevTools",e.src=H,Object.assign(e.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(e)),e.hidden=!1}function w(t){p({type:"ui",open:t.open,tab:t.tab})}function U(){const t=!i().open,e=l({open:t});d(t),w(e)}function E(){const t=$();t&&(t.ms>0&&B(t.ms),R(t),p(t),d(i().open),v()?.addEventListener("message",e=>{const n=e.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",e=>{const n=e.data;if(!n||typeof n!="object"||n.type!==N)return;const s=document.getElementById(u);!s||typeof n.height!="number"||S(s,n.height)}))}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",E):E()})();