Excludes the toolbar's own bundle from the JavaScript budget, raises the development thresholds, and skips WRNexus UI and theme stylesheets when measuring CSS coverage, so unminified development modules and framework styles stop reading as application problems. Pre-existing working-tree change, committed as-is rather than authored here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
25 KiB
TypeScript
82 lines
25 KiB
TypeScript
export const DEV_TOOLBAR_RUNTIME = String.raw`(() => {
|
|
if (window.__wrnexusDevToolbar) return;
|
|
const KEY = "__wrnexus_dev_toolbar_settings__";
|
|
const state = { open:false, issues:[], report:null, search:"", severity:"all", category:"all", platform:null, panels:[], runtimeMetrics:{ hydrationCount:0, hydrationMs:0, longTasks:[] }, config:{ position:"bottom-center", scanOnNavigation:true, scanOnHmr:true } };
|
|
try { state.config = Object.assign(state.config, JSON.parse(localStorage.getItem(KEY) || "{}")); } catch {}
|
|
const host = document.createElement("wrnexus-dev-toolbar");
|
|
host.setAttribute("data-wrnexus-dev-toolbar", "");
|
|
document.body.appendChild(host);
|
|
const root = host.attachShadow({mode:"open"});
|
|
const style = document.createElement("style");
|
|
fetch("/__wrnexus/dev-toolbar.css").then(r => r.ok ? r.text() : "").then(css => style.textContent = css).catch(() => {});
|
|
root.appendChild(style);
|
|
const shell = document.createElement("div"); shell.className="wrn-shell"; shell.dataset.position=state.config.position;
|
|
shell.innerHTML='<section class="wrn-panel"><header class="wrn-panel-head"><div><div class="wrn-panel-title">WRNexus DevToolbar</div><div class="wrn-panel-meta"></div></div><button class="wrn-small" data-close>Close</button></header><div class="wrn-toolbar-row"><input class="wrn-search" placeholder="Search issues, rules, files…"><span class="wrn-apps"><button class="wrn-small" data-category="all">Issues</button><button class="wrn-small" data-category="runtime">Runtime</button><button class="wrn-small" data-category="stores">Stores</button><button class="wrn-small" data-category="cache">Cache</button><button class="wrn-small" data-category="accessibility">A11y</button><button class="wrn-small" data-category="seo">SEO</button><button class="wrn-small" data-category="performance">Performance</button><button class="wrn-small" data-category="security">Security</button><button class="wrn-small" data-category="images">Images</button><button class="wrn-small" data-category="links">Links</button><button class="wrn-small" data-category="javascript">JavaScript</button></span><button class="wrn-small" data-scan>Rescan</button><button class="wrn-small" data-clear>Clear</button></div><div class="wrn-list"></div></section><nav class="wrn-bar"><span class="wrn-brand">WRNexus</span><button class="wrn-count" data-filter="error"><span class="wrn-dot error"></span><span data-errors>0</span></button><button class="wrn-count" data-filter="warning"><span class="wrn-dot warning"></span><span data-warnings>0</span></button><button class="wrn-count" data-filter="suggestion"><span class="wrn-dot suggestion"></span><span data-suggestions>0</span></button><button class="wrn-button" data-toggle>Inspect</button><button class="wrn-button" data-scan>↻</button></nav>';
|
|
root.appendChild(shell);
|
|
const panel=root.querySelector(".wrn-panel"), list=root.querySelector(".wrn-list"), meta=root.querySelector(".wrn-panel-meta"), search=root.querySelector(".wrn-search");
|
|
const esc = value => String(value ?? "").replace(/[&<>\"']/g, char => ({"&":"&","<":"<",">":">",'"':""","'":"'"})[char]);
|
|
const selector = el => { if (!el || el.nodeType !== 1) return ""; if (el.id) return "#"+CSS.escape(el.id); const parts=[]; let cur=el; while(cur && cur!==document.documentElement && parts.length<5){ let part=cur.tagName.toLowerCase(); const cls=[...cur.classList].slice(0,2).map(x=>"."+CSS.escape(x)).join(""); part+=cls; const parent=cur.parentElement; if(parent){ const same=[...parent.children].filter(x=>x.tagName===cur.tagName); if(same.length>1) part+=":nth-of-type("+(same.indexOf(cur)+1)+")"; } parts.unshift(part); cur=parent; } return parts.join(" > "); };
|
|
const issue = (ruleId,category,severity,title,message,el,recommendation,confidence="high",metadata={}) => { const sel=selector(el); const source=el?.getAttribute?.("data-wrnexus-source")||""; return { id:"wrn-"+Date.now()+"-"+Math.random().toString(36).slice(2), ruleId,category,severity,title,message,recommendation,confidence,target:el?{selector:sel,tagName:el.tagName.toLowerCase(),text:(el.textContent||"").trim().slice(0,120)}:undefined,source:source?{file:source}:undefined,metadata,fingerprint:[ruleId,location.pathname,sel,source,message].join("|"),createdAt:Date.now() }; };
|
|
const accessibleName = el => { const by=el.getAttribute("aria-labelledby"); if(by){ const t=by.split(/\s+/).map(id=>document.getElementById(id)?.textContent?.trim()||"").join(" ").trim(); if(t)return t; } return el.getAttribute("aria-label")?.trim()||el.getAttribute("alt")?.trim()||el.getAttribute("title")?.trim()||el.textContent?.trim()||""; };
|
|
const visible = el => { const s=getComputedStyle(el),r=el.getBoundingClientRect(); return s.display!=="none"&&s.visibility!=="hidden"&&Number(s.opacity)!==0&&r.width>0&&r.height>0; };
|
|
const scan = async () => {
|
|
const found=[]; const q=s=>[...document.querySelectorAll(s)].filter(el=>!el.closest("wrnexus-dev-toolbar")&&!el.closest("[data-dev-toolbar-ignore='all']"));
|
|
q("img:not([alt])").forEach(el=>found.push(issue("a11y/image-alt","accessibility","error","Image is missing alt text","Add alt text for meaningful images, or alt=\"\" for decorative images.",el,"Add a concise alt attribute.")));
|
|
q("button,a[href],[role='button']").filter(el=>!accessibleName(el)).forEach(el=>found.push(issue("a11y/control-name","accessibility","error","Control has no accessible name","Screen readers cannot identify this control.",el,"Add visible text, aria-label, or aria-labelledby.")));
|
|
q("input:not([type='hidden']),select,textarea").filter(el=>{const id=el.id;return !el.getAttribute("aria-label")&&!el.getAttribute("aria-labelledby")&&!(id&&document.querySelector("label[for='"+CSS.escape(id)+"']"))&&!el.closest("label")}).forEach(el=>found.push(issue("a11y/form-label","accessibility","error","Form control has no label","Users may not know what this field is for.",el,"Associate a visible label.")));
|
|
const ids=new Set(); q("[id]").forEach(el=>{if(ids.has(el.id))found.push(issue("a11y/duplicate-id","accessibility","error","Duplicate element ID","The ID “"+el.id+"” is used more than once.",el,"Use a unique ID.")); else ids.add(el.id)});
|
|
let prev=0; q("h1,h2,h3,h4,h5,h6").forEach(el=>{const level=Number(el.tagName.slice(1));if(prev&&level>prev+1)found.push(issue("a11y/heading-order","accessibility","warning","Heading level is skipped","Heading jumps from h"+prev+" to h"+level+".",el,"Use a logical heading hierarchy."));prev=level});
|
|
if(!document.title.trim()) found.push(issue("seo/title-missing","seo","error","Page title is missing","The document does not have a useful title.",null,"Add a unique title."));
|
|
if(!document.querySelector('meta[name="description"]')) found.push(issue("seo/description-missing","seo","warning","Meta description is missing","Search and social previews may lack a useful description.",null,"Add a unique meta description."));
|
|
if(!document.querySelector('meta[name="viewport"]')) found.push(issue("seo/viewport-missing","seo","error","Viewport meta tag is missing","The page may render incorrectly on mobile devices.",null,"Add width=device-width, initial-scale=1."));
|
|
if(!document.querySelector("h1")) found.push(issue("seo/h1-missing","seo","warning","Page has no h1 heading","The page lacks a clear primary heading.",null,"Add one descriptive h1."));
|
|
q("img").forEach(el=>{const r=el.getBoundingClientRect();if(el.complete&&el.naturalWidth===0)found.push(issue("images/broken","images","error","Image failed to load",el.currentSrc||el.src||"Empty image source",el,"Verify the URL and content type."));if(!el.hasAttribute("width")&&!el.hasAttribute("height")&&getComputedStyle(el).aspectRatio==="auto")found.push(issue("images/dimensions","images","warning","Image has no reserved dimensions","This image may cause layout shift.",el,"Add width and height or CSS aspect-ratio."));if(visible(el)&&el.naturalWidth>0&&r.width>el.naturalWidth*1.25)found.push(issue("images/upscaled","images","warning","Image is being enlarged","Rendered width exceeds intrinsic width.",el,"Use a larger source image."));if(r.top>innerHeight*1.5&&el.loading!=="lazy")found.push(issue("images/lazy-below-fold","images","suggestion","Below-fold image is not lazy loaded","This image starts far below the viewport.",el,"Consider loading=\"lazy\".","medium"));});
|
|
q("video").forEach(el=>{if(el.autoplay&&!el.muted)found.push(issue("media/autoplay-sound","media","error","Autoplay video is not muted","Autoplaying sound is disruptive and often blocked.",el,"Remove autoplay or add muted."));if(!el.querySelector('track[kind="captions"]'))found.push(issue("media/no-captions","media","warning","Video has no captions track","Spoken content may be inaccessible.",el,"Add a captions track when applicable."));});
|
|
q("iframe:not([title])").forEach(el=>found.push(issue("media/iframe-title","media","error","Iframe is missing a title","Assistive technology cannot identify the embed.",el,"Add a concise title.")));
|
|
const count=document.querySelectorAll("*").length;if(count>1500)found.push(issue("html/dom-size","html",count>3000?"error":"warning","DOM is large","The page contains "+count+" elements.",null,"Reduce wrappers and render hidden content on demand."));
|
|
q("button button,button a,a a,a button").forEach(el=>found.push(issue("html/nested-interactive","html","error","Interactive elements are nested","Nested links or buttons create invalid interaction behavior.",el,"Use one interactive element.")));
|
|
q("a").forEach(el=>{const href=el.getAttribute("href");if(!href||href==="#")found.push(issue("links/empty","links","warning","Link has no useful destination","The href is missing or only #.",el,"Use a real URL or a button."));if(el.target==="_blank"&&!/\b(noopener|noreferrer)\b/.test(el.rel))found.push(issue("links/blank-rel","security","warning","New-tab link lacks rel protection","The opened page may access window.opener.",el,"Add rel=\"noopener noreferrer\"."));});
|
|
q("form").forEach(el=>{const method=(el.getAttribute("method")||"get").toLowerCase();const action=el.getAttribute("action")||"";if(location.protocol==="https:"&&action.startsWith("http://"))found.push(issue("security/insecure-form-action","security","error","Form submits over HTTP",action,el,"Use an HTTPS or same-origin form action."));if(method!=="get"&&!el.hasAttribute("data-csrf")&&!el.querySelector('input[name="wrn-csrf"],input[name="_csrf"]'))found.push(issue("security/missing-csrf","security","error","State-changing form has no CSRF marker","Cookie-authenticated form submissions require CSRF protection.",el,"Add data-csrf or a framework-generated CSRF field."));});
|
|
q("script:not([src]):not([type='application/json']):not([nonce])").forEach(el=>found.push(issue("security/inline-script","security","warning","Inline script has no CSP nonce","A strict Content Security Policy will block this script.",el,"Move code to a client module or attach the request nonce.")));
|
|
q("iframe:not([sandbox])").forEach(el=>found.push(issue("security/iframe-sandbox","security","warning","Iframe is not sandboxed","Untrusted iframe content has broad browser capabilities.",el,"Add the narrowest sandbox and permissions policy.")));
|
|
q("[src],[href],[action]").forEach(el=>{const raw=el.getAttribute("src")||el.getAttribute("href")||el.getAttribute("action")||"";if(location.protocol==="https:"&&raw.startsWith("http://"))found.push(issue("security/mixed-content","security","error","Mixed-content resource",raw,el,"Use HTTPS or a same-origin resource."));});
|
|
try{for(let i=0;i<localStorage.length;i++){const key=localStorage.key(i)||"";if(/(?:token|secret|password|session|credential|authorization)/i.test(key))found.push(issue("security/sensitive-local-storage","security","error","Sensitive value may be stored in localStorage","Storage key “"+key+"” looks authentication- or secret-related.",null,"Keep sessions and credentials in Secure, HttpOnly cookies."));}}catch{}
|
|
if(document.documentElement.scrollWidth>innerWidth+2)found.push(issue("responsive/document-overflow","responsive","error","Page has horizontal overflow","Document width exceeds the viewport.",null,"Inspect fixed widths, long text and overflowing media."));
|
|
q("body *").filter(visible).slice(0,2500).forEach(el=>{const r=el.getBoundingClientRect();if((r.right>innerWidth+8||r.left<-8)&&found.filter(x=>x.ruleId==="responsive/element-overflow").length<20)found.push(issue("responsive/element-overflow","responsive","warning","Element extends outside the viewport","Element bounds exceed the current viewport.",el,"Use fluid sizing, wrapping, max-width or an intentional scroll container."));});
|
|
const resources=performance.getEntriesByType("resource");const total=resources.reduce((s,e)=>s+(e.transferSize||0),0);const jsResources=resources.filter(e=>/(?:\.m?js)(?:\?|$)/i.test(e.name)&&!/\/__wrnexus\/dev-toolbar\.js(?:\?|$)/.test(e.name));const jsBytes=jsResources.reduce((sum,e)=>sum+(e.transferSize||0),0);if(resources.length>150)found.push(issue("performance/resource-count","performance","warning","Page loads many resources","Found "+resources.length+" resource requests.",null,"Remove duplicates and defer non-critical resources."));if(total>5000000)found.push(issue("performance/transfer-size","performance",total>10000000?"error":"warning","Page transfer size is large","Observed transfer size is about "+(total/1000000).toFixed(2)+" MB.",null,"Compress and optimize page resources."));if(jsBytes>300000)found.push(issue("performance/javascript-budget","javascript",jsBytes>600000?"error":"warning","Development JavaScript is large","Application JavaScript transfer is about "+(jsBytes/1000).toFixed(1)+" KB in development.",null,"Check the production build report before splitting routes; development modules are unminified.","medium",{javascriptBytes:jsBytes,mode:"development"}));const hydrationRoots=q("[data-wrn-client-module],[data-wrn-hydrate]");if(hydrationRoots.length>50)found.push(issue("performance/hydration-count","runtime","warning","Many components hydrate",hydrationRoots.length+" hydration boundaries were found.",null,"Use visible, idle or interaction hydration."));if(state.runtimeMetrics.longTasks.length)found.push(issue("performance/long-tasks","javascript","warning","Long main-thread tasks detected",state.runtimeMetrics.longTasks.length+" task(s) exceeded 50 ms.",null,"Split expensive work and reduce hydration.","high",{longestMs:Math.max(...state.runtimeMetrics.longTasks)}));
|
|
let checkedSelectors=0,unusedSelectors=0;for(const sheet of [...document.styleSheets]){const href=sheet.href||"";if(/\/__wrnexus\/(?:ui|framework)\.css(?:\?|$)|\/__wrnexus\/theme\/[^/?]+\.css(?:\?|$)/.test(href))continue;let rules;try{rules=[...(sheet.cssRules||[])]}catch{continue}for(const rule of rules){if(checkedSelectors>=2000)break;const selector=rule.selectorText;if(!selector||selector.includes(":"))continue;checkedSelectors++;try{if(!document.querySelector(selector))unusedSelectors++}catch{}}}const unusedRatio=checkedSelectors?unusedSelectors/checkedSelectors:0;if(checkedSelectors>=20&&unusedRatio>=.8)found.push(issue("css/unused-selectors","css","suggestion","Low current-page CSS coverage",unusedSelectors+" of "+checkedSelectors+" inspected application selectors do not match this page.",null,"Review across routes before removing selectors. WRNexus UI and theme styles are excluded.","medium",{checkedSelectors,unusedSelectors,unusedRatio}));const memory=performance.memory;if(memory&&memory.jsHeapSizeLimit&&memory.usedJSHeapSize/memory.jsHeapSizeLimit>.8)found.push(issue("performance/memory-pressure","performance","warning","High JavaScript heap usage",Math.round(memory.usedJSHeapSize/1048576)+" MiB of "+Math.round(memory.jsHeapSizeLimit/1048576)+" MiB is in use.",null,"Inspect retained objects and repeated hydration."));
|
|
q("[data-wrn-client-module]").forEach(el=>found.push(issue("runtime/client-module","runtime","info","Client function module",el.getAttribute("data-wrn-client-module")||"Unknown module",el,"Loaded according to the component hydration strategy.","high",{hydration:el.getAttribute("data-wrn-hydrate"),runtime:el.getAttribute("data-wrn-runtime")})));
|
|
const storeContainer=window.__wrnexusStoreContainer;
|
|
if(storeContainer&&typeof storeContainer.inspect==="function"){
|
|
for(const store of storeContainer.inspect())found.push(issue("stores/instance","stores","info",store.kind+" store: "+store.name,JSON.stringify({state:store.state,computed:store.computed}),null,"Use store actions for mutations. Sensitive server state is never hydrated.","high",store));
|
|
}
|
|
try{
|
|
const inspection=await fetch("/__wrnexus/cache",{headers:{accept:"application/json"}}).then(response=>response.ok?response.json():null);
|
|
if(inspection&&inspection.layers){
|
|
for(const name of ["data","component","page"]){
|
|
const entries=inspection.layers[name]||[];
|
|
found.push(issue("cache/layer","cache","info",name+" cache",entries.length+" entries · "+entries.filter(entry=>entry.state==="fresh").length+" fresh · "+entries.filter(entry=>entry.state==="stale").length+" stale",null,"Use tags for related invalidation and vary by tenant, user, or language when needed.","high",{layer:name,entries}));
|
|
}
|
|
}
|
|
}catch{}
|
|
const unique=new Map();[...state.issues.filter(x=>["runtime","stores","cache","network","server","compiler","routing"].includes(x.category)),...found].forEach(x=>unique.set(x.fingerprint,x));state.issues=[...unique.values()];
|
|
state.report={url:location.href,pathname:location.pathname,title:document.title,status:200,generatedAt:Date.now(),issues:state.issues,metrics:{domNodes:count,cssResources:q('link[rel="stylesheet"]').length,scriptResources:q("script[src]").length,imageResources:q("img").length,totalTransferBytes:total,javascriptBytes:jsBytes,hydratedComponents:state.runtimeMetrics.hydrationCount,hydrationMs:state.runtimeMetrics.hydrationMs,longTasks:state.runtimeMetrics.longTasks.length,pageLoadMs:performance.getEntriesByType("navigation")[0]?.duration}}; render(); return state.report;
|
|
};
|
|
let highlightEl=null; const clearHighlight=()=>{highlightEl?.remove();highlightEl=null}; const highlight=sel=>{clearHighlight();let target;try{target=document.querySelector(sel)}catch{}if(!target)return;const r=target.getBoundingClientRect();highlightEl=document.createElement("div");highlightEl.className="wrn-highlight";Object.assign(highlightEl.style,{left:r.left+"px",top:r.top+"px",width:r.width+"px",height:r.height+"px"});root.appendChild(highlightEl);target.scrollIntoView({block:"center",behavior:"smooth"});setTimeout(clearHighlight,3000)};
|
|
const render=()=>{const filtered=state.issues.filter(x=>{const severityMatches=state.severity==="all"||x.severity===state.severity;const categoryMatches=state.category==="all"||x.category===state.category;const textMatches=!state.search||[x.title,x.message,x.ruleId,x.category,x.severity,x.source?.file].join(" ").toLowerCase().includes(state.search);return severityMatches&&categoryMatches&&textMatches;});root.querySelector("[data-errors]").textContent=state.issues.filter(x=>x.severity==="error").length;root.querySelector("[data-warnings]").textContent=state.issues.filter(x=>x.severity==="warning").length;root.querySelector("[data-suggestions]").textContent=state.issues.filter(x=>x.severity==="suggestion").length;root.querySelectorAll("[data-filter]").forEach(button=>{const active=button.dataset.filter===state.severity;button.classList.toggle("active",active);button.setAttribute("aria-pressed",String(active));});root.querySelectorAll("[data-category]").forEach(button=>{const active=button.dataset.category===state.category;button.classList.toggle("active",active);button.setAttribute("aria-pressed",String(active));});meta.textContent=location.pathname+" · "+state.issues.length+" findings"+(state.category!=="all"?" · "+state.category:"")+(state.severity!=="all"?" · "+state.severity:"");if(!filtered.length){list.innerHTML='<div class="wrn-empty">No issues match the current filters.</div>';return;}list.innerHTML=filtered.map(x=>'<article class="wrn-issue"><span class="wrn-severity '+esc(x.severity)+'"></span><div><div class="wrn-issue-title">'+esc(x.title)+'</div><div class="wrn-issue-message">'+esc(x.message)+'</div><div class="wrn-issue-meta">'+esc(x.severity)+' · '+esc(x.category)+' · '+esc(x.ruleId)+(x.source?.file?' · '+esc(x.source.file):'')+'</div></div><div class="wrn-actions">'+(x.target?.selector?'<button class="wrn-small" data-highlight="'+esc(x.target.selector)+'">Show</button>':'')+'</div></article>').join("");};
|
|
const addRuntime=(severity,title,message,metadata={})=>{const x=issue("runtime/browser","runtime",severity,title,message,null,"Inspect the browser console and source stack.","high",metadata);state.issues=[x,...state.issues.filter(i=>i.fingerprint!==x.fingerprint)];render()};
|
|
addEventListener("error",event=>{const target=event.target;if(target&&target!==window&&target.tagName){addRuntime("error","Resource failed to load",target.src||target.href||target.currentSrc||target.tagName,{tag:target.tagName});}else addRuntime("error",event.message||"Uncaught browser error",event.filename?event.filename+":"+event.lineno+":"+event.colno:"",{stack:event.error?.stack});},true);
|
|
addEventListener("unhandledrejection",event=>addRuntime("error","Unhandled promise rejection",event.reason?.message||String(event.reason),{stack:event.reason?.stack}));
|
|
root.addEventListener("click",event=>{const button=event.target.closest("button");if(!button)return;if(button.matches("[data-toggle]")){state.open=!state.open;state.severity="all";panel.classList.toggle("open",state.open);render()}if(button.matches("[data-close]")){state.open=false;panel.classList.remove("open")}if(button.matches("[data-scan]"))scan();if(button.matches("[data-clear]")){state.issues=[];state.severity="all";state.category="all";state.search="";search.value="";render()}if(button.dataset.category){state.category=button.dataset.category;state.open=true;panel.classList.add("open");render()}if(button.dataset.filter){state.severity=state.severity===button.dataset.filter?"all":button.dataset.filter;state.open=true;panel.classList.add("open");render()}if(button.dataset.highlight)highlight(button.dataset.highlight)});
|
|
search.addEventListener("input",()=>{state.search=search.value.toLowerCase();render()});
|
|
addEventListener("wrnexus:navigated",()=>{if(state.config.scanOnNavigation)setTimeout(scan,50)});addEventListener("wrnexus:hmr",()=>{if(state.config.scanOnHmr)setTimeout(scan,100)});addEventListener("wrnexus:runtime-error",event=>addRuntime("error","WRNexus runtime error",event.detail?.message||"Runtime failure",event.detail||{}));
|
|
addEventListener("wrnexus:diagnostic",event=>{const detail=event.detail||{};const code=detail.code||"WRN-RUNTIME";const category=String(code).includes("HYDRATE")?"runtime":String(code).includes("ROUTE")?"routing":"compiler";const title=String(code).includes("HYDRATE")?"Hydration diagnostic":"WRNexus diagnostic";const x=issue(String(code),category,"error",title,detail.message||"Framework diagnostic",null,"Open the source location and resolve the reported framework contract.","high",detail);state.issues=[x,...state.issues.filter(i=>i.fingerprint!==x.fingerprint)];render();});
|
|
addEventListener("wrnexus:store-mutation",event=>{const mutation=event.detail||{};const x=issue("stores/mutation","stores","info","Store action: "+(mutation.store||"unknown")+"."+(mutation.action||"direct"),"Changed fields: "+((mutation.changed||[]).join(", ")||"none"),null,"Use the Stores panel to inspect safe client state.","high",mutation);state.issues=[x,...state.issues.filter(i=>i.fingerprint!==x.fingerprint)];render();});
|
|
addEventListener("wrnexus:hydrated",event=>{state.runtimeMetrics.hydrationCount+=1;state.runtimeMetrics.hydrationMs+=Number(event.detail?.durationMs||0);});
|
|
try{new PerformanceObserver(list=>{for(const entry of list.getEntries()){if(entry.duration>50)state.runtimeMetrics.longTasks.push(entry.duration);}state.runtimeMetrics.longTasks=state.runtimeMetrics.longTasks.slice(-100);}).observe({type:"longtask",buffered:true});}catch{}
|
|
fetch("/__wrnexus/dev-toolbar/platform").then(r=>r.ok?r.json():null).then(data=>{if(!data)return;state.platform=data.platform;state.panels=data.panels||[];const apps=root.querySelector(".wrn-apps");const existing=new Set([...apps.querySelectorAll("[data-category]")].map(button=>button.dataset.category));for(const app of state.panels){if(!existing.has(app.id)){const button=document.createElement("button");button.className="wrn-small";button.dataset.category=app.id;button.textContent=app.title+(app.badge!==undefined?" ("+app.badge+")":"");button.title=app.description||app.title;apps.appendChild(button);}if(app.data!==undefined||app.issues?.length){const payload=app.data!==undefined?app.data:app.issues;state.issues.push(issue("plugin/"+app.id,app.id,"info",app.title,JSON.stringify(payload,null,2),null,app.description||"Plugin-provided development information.","high",{panel:app.id,data:payload}));}}render();}).catch(()=>{});
|
|
window.__wrnexusDevToolbar={open(){state.open=true;panel.classList.add("open")},close(){state.open=false;panel.classList.remove("open")},toggle(){state.open=!state.open;panel.classList.toggle("open",state.open)},scan,clear(){state.issues=[];render()},report(){return state.report},highlight,configure(config){state.config=Object.assign(state.config,config||{});shell.dataset.position=state.config.position;try{localStorage.setItem(KEY,JSON.stringify(state.config))}catch{}}};
|
|
const observer=new MutationObserver(()=>{clearTimeout(observer.timer);observer.timer=setTimeout(()=>{if(!state.open)return;scan()},400)});observer.observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:["class","style","src","href","alt","aria-label"]});
|
|
setTimeout(scan,100);
|
|
})();`;
|