← Commit history
SKILL.md+3
@@ -182,3 +182,6 @@ No 3D viewer, Babylon bundle or GLB may load on the default page path. A user mu Turning the preview off, hiding the tab, scrolling it offscreen or disconnecting the widget must abort pending work, dispose model containers and the viewer/engine, release the WebGL context, and stop timers/render loops. A later activation starts coarse again. Retaining the downloaded JavaScript module is normal; retaining a live engine/model is not. The shared runtime permits one active LOD preview per same-origin BroadcastChannel and uses a 16 MiB decoded geometry budget. Runtime imports can be blocked by a restrictive HTML sandbox; use the registered Markdown widget frame, and do not weaken page authentication or CSP to work around it.  Before publication, test image-only network requests, all four zoom levels, idle rendering, repeat activation/deactivation and live GPU-buffer/context cleanup. Screenshot the real authenticated widget and record purposeful inspection motion. A passing runtime check is separate from native CAD or manufacturing qualification.+++The wiki registry resolves script paths as repository file names: do not append a cache-busting query to the registry `script` URL. Raw `/render/` JavaScript is served as `application/octet-stream` with `nosniff`, so do not dynamically import a companion module from that route. The shared widget bundles its small control runtime and imports only the canonical viewer from the wiki's supported `/static/vendor/adom-3d-viewer-babylon9/` module path after activation. Keep the source module and bundled runtime in sync.
files/chip-thumbnail-widget.js+81−2
@@ -1,8 +1,87 @@ /* <adom-chip-thumbnail>: legacy image or reviewed reusable chip-artwork cache. MIT. */ (() => {-  const widgetBase=new URL('.',document.currentScript?.src||'https://wiki.adom.inc/adom/component-widgets/render/widgets/chip-thumbnail-widget.js');   const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));   const CSS = `:host{display:block;color:#dbe2ec;font:14px system-ui;--accent:#00b8b0}*{box-sizing:border-box}.cache{background:#0d1117;border:1px solid #30363d;border-radius:12px;padding:20px}h3{margin:0 0 8px;font-size:20px}.muted{color:#a1abb7;line-height:1.5}.badges{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.badge{padding:5px 9px;border:1px solid #394550;border-radius:6px;font-size:12px}.row{display:grid;grid-template-columns:minmax(220px,1fr) minmax(250px,1fr);gap:24px}.preview{background:#faf8f0;border-radius:9px;overflow:hidden;display:flex;align-items:center;justify-content:center;min-height:250px}.preview img{width:100%;max-height:370px;object-fit:contain;transition:transform .3s}.controls{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}button,a.download{font:inherit;border:1px solid #45505d;border-radius:6px;background:#17202a;color:#e5edf5;padding:8px 12px;cursor:pointer;text-decoration:none}button:hover,a:hover{border-color:var(--accent)}button[aria-pressed=true]{border-color:var(--accent);color:#60e1d6}button:focus-visible,a:focus-visible{outline:2px solid var(--accent);outline-offset:3px}code{overflow-wrap:anywhere;font-size:12px}dl{margin:0}dt{color:#9da8b4;font-size:12px;margin-top:12px}dd{margin:4px 0;line-height:1.5}details{margin-top:16px}summary{cursor:pointer;padding:8px 0}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#161b22;padding:12px;border-radius:6px;font-size:12px}.files{display:grid;gap:9px;margin:12px 0}.files a{color:#6edfd5}.error{color:#ffb7a5}.legacy{width:200px;text-align:center;margin:0}.legacy img{width:100%}.legacy figcaption{padding:8px} @media(max-width:580px){.row{grid-template-columns:1fr}.cache{padding:14px}}`;+// BEGIN bundled opt-in runtime (generated from component-lod-preview.js)+// Opt-in LOD inspection, built on the canonical Adom Babylon viewer.+// No module, engine or model is loaded until the user enables this preview.+const VIEWER='https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js?v=20260828a';+async function mountLodPreview({host,manifest,resolveAsset,onState,signal}) {+ let viewer,engine,scene,root,camera,raf=0,timer,observer,channel,stopped=false;+ let current=null,wanted='super-low',loading=null,candidate=null,since=0,frames=0,lastFrame=0,awakeUntil=0,lastCamera='';+ const cache=new Map(),levels=['super-low','low','medium','high'],thresholds=[90,190,340],budget=16*1024*1024;+ const stats={active:true,loading:true,level:null,wanted,frames:0,downloads:0,glbRequests:0,evictions:0,residentLevels:[],decodedBufferBytes:0,errors:[],initialLevel:'super-low'};+ function report(){stats.frames=frames;stats.wanted=wanted;stats.loading=!!loading;stats.residentLevels=[...cache.values()].map(e=>e.info.level);stats.decodedBufferBytes=[...cache.values()].reduce((s,e)=>s+e.info.decodedBufferBytes,0);onState({...stats});}+ function invalidate(){awakeUntil=performance.now()+1100;if(!raf&&!stopped&&!document.hidden)raf=requestAnimationFrame(render);}+ function render(now){raf=0;if(stopped||document.hidden)return;if(now-lastFrame>=1000/30){engine.beginFrame();try{scene.render();}finally{engine.endFrame();}frames++;lastFrame=now;}if(now<awakeUntil)raf=requestAnimationFrame(render);}+ function dispose(reason='turned off'){+  if(stopped)return;stopped=true;loading?.controller.abort();clearInterval(timer);cancelAnimationFrame(raf);observer?.disconnect();channel?.close();+  document.removeEventListener('visibilitychange',hidden);signal?.removeEventListener('abort',aborted);+  for(const e of cache.values())e.container.dispose();cache.clear();+  const canvas=engine?.getRenderingCanvas();const gl=canvas&&(canvas.getContext('webgl2')||canvas.getContext('webgl'));+  viewer?.destroy();gl?.getExtension('WEBGL_lose_context')?.loseContext();host.replaceChildren();+  stats.active=false;stats.loading=false;stats.level=null;stats.residentLevels=[];stats.decodedBufferBytes=0;stats.stopReason=reason;onState({...stats,frames});+ }+ const hidden=()=>{if(document.hidden)dispose('tab hidden');},aborted=()=>dispose('turned off');+ signal?.addEventListener('abort',aborted,{once:true});+ try{+  if(signal?.aborted)throw new DOMException('Preview cancelled','AbortError');+  if(manifest.upAxis!=='Z'||!['metres','metre'].includes(manifest.units))throw Error('Preview requires a declared Z-up, metre-scale display model.');+  if(levels.some(level=>!manifest.levels.find(l=>l.level===level)?.path))throw Error('All four LOD files must be available before interactive inspection.');+  await import(VIEWER);if(stopped||signal?.aborted)return {dispose,state:()=>({...stats})};+  const api=window.Adom3DViewerBabylon9,B=api.BABYLON;+  viewer=api.init(host,{zUp:true,showViewCube:true});engine=viewer.getEngine();scene=viewer.getScene();scene.useRightHandedSystem=true;camera=viewer.getCamera();+  engine.stopRenderLoop();engine.runRenderLoop=()=>invalidate();+  viewer.setGroundVisible(false);scene.shadowsEnabled=false;viewer.getShadowGenerator()?.dispose();root=new B.TransformNode('component-z-up-mm',scene);root.scaling.setAll(1000);viewer.addContentRoot(root);+  const fill=new B.HemisphericLight('lod-inspection-fill',new B.Vector3(0,0,1),scene);fill.intensity=.9;fill.groundColor=new B.Color3(.5,.5,.5);+  const canvas=engine.getRenderingCanvas();for(const event of ['pointerdown','pointermove','pointerup','wheel','keydown'])canvas.addEventListener(event,invalidate,{passive:true});+  document.addEventListener('visibilitychange',hidden);+  observer=new IntersectionObserver(entries=>{if(!entries[0].isIntersecting)dispose('preview scrolled offscreen');});observer.observe(host);+  try{channel=new BroadcastChannel('adom-component-lod-preview');channel.onmessage=()=>dispose('another component preview enabled');channel.postMessage({enabled:true});}catch{channel=null;}+  function evict(){for(const [key,e] of cache)if(e!==current&&e.info.level!=='super-low'){e.container.dispose();cache.delete(key);stats.evictions++;}}+  function apply(e,level){+   if(current!==e){current?.container.removeAllFromScene();e.container.addAllToScene();for(const n of e.container.rootNodes)if(!n.parent)n.parent=root;current=e;}+   stats.level=level;evict();invalidate();report();+  }+  async function load(level){+   const info=manifest.levels.find(l=>l.level===level);if(cache.has(info.sha256)){apply(cache.get(info.sha256),level);return;}+   if(loading)return;+   evict();if(info.decodedBufferBytes+[...cache.values()].reduce((s,e)=>s+e.info.decodedBufferBytes,0)>budget)throw Error('Component preview exceeds the 16 MiB decoded-geometry budget.');+   const request={level,controller:new AbortController()};loading=request;report();+   let container;+   try{+    stats.glbRequests++;const r=await fetch(resolveAsset(info.path),{credentials:'include',signal:request.controller.signal});if(!r.ok)throw Error('LOD request returned '+r.status);+    const data=await r.arrayBuffer();if(data.byteLength!==info.sizeBytes)throw Error('LOD byte count does not match manifest.');+    const hash=[...new Uint8Array(await crypto.subtle.digest('SHA-256',data))].map(x=>x.toString(16).padStart(2,'0')).join('');if(hash!==info.sha256)throw Error('LOD SHA-256 does not match manifest.');+    if(stopped)return;+    const url=URL.createObjectURL(new Blob([data],{type:'model/gltf-binary'}));+    try{container=await B.SceneLoader.LoadAssetContainerAsync('',url,scene,undefined,'.glb');}finally{URL.revokeObjectURL(url);}+    if(stopped||wanted!==level){container.dispose();return;}+    const e={info,container};cache.set(info.sha256,e);stats.downloads++;apply(e,level);+   }catch(e){container?.dispose();if(e.name!=='AbortError'&&!stopped){stats.errors.push(String(e));dispose('model loading failed');throw e;}}+   finally{if(loading===request)loading=null;if(!stopped)report();}+  }+  await load('super-low');if(stopped)return {dispose,state:()=>({...stats})};+  const box=manifest.levels.find(l=>l.level==='super-low').bounds;+  const min=B.Vector3.FromArray(box.min).scale(1000),max=B.Vector3.FromArray(box.max).scale(1000),diameter=max.subtract(min).length();+  const center=min.add(max).scale(.5);camera.minZ=.001;camera.lowerRadiusLimit=Math.max(.01,diameter*.35);camera.upperRadiusLimit=diameter*35;+  camera.target=center;camera.setPosition(center.add(new B.Vector3(2,-3,3).normalize().scale(diameter*12)));+  const tick=()=>{+   if(stopped)return;+   const sig=JSON.stringify([camera.alpha,camera.beta,camera.radius,...camera.target.asArray(),host.clientWidth,host.clientHeight].map(n=>Math.round(n*1e7)/1e7));+   if(sig!==lastCamera){lastCamera=sig;engine.resize();invalidate();}+   const distance=B.Vector3.Distance(camera.globalPosition,center),pixels=diameter*host.clientHeight/(2*Math.max(.01,distance)*Math.tan(camera.fov/2));+   let index=levels.indexOf(wanted);while(index<3&&pixels>thresholds[index]*1.2)index++;while(index>0&&pixels<thresholds[index-1]*.8)index--;+   if(levels[index]!==candidate){candidate=levels[index];since=performance.now();}+   if(performance.now()-since>350){wanted=candidate;if(loading&&loading.level!==wanted)loading.controller.abort();if(!loading&&stats.level!==wanted)void load(wanted).catch(e=>{stats.errors.push(String(e));dispose('preview error');});}+   report();+  };+  timer=setInterval(tick,150);invalidate();report();+  return {dispose,state:()=>({...stats}),inspectDistance:multiple=>{camera.radius=diameter*multiple;invalidate();},camera:()=>({radius:camera.radius,diameter,alpha:camera.alpha,beta:camera.beta,target:camera.target.asArray(),size:[host.clientWidth,host.clientHeight]})};+ }catch(e){dispose(e.name==='AbortError'?'turned off':String(e));throw e;}+}++// END bundled opt-in runtime   class AdomChipThumbnail extends HTMLElement {     static get observedAttributes(){return ['src','mpn','package'];}     set data(value){this._data=value;if(this.shadowRoot)this._load();}@@ -47,7 +126,7 @@       host.hidden=false;button.textContent='Turn off 3D preview';status.textContent='Loading super-low detail…';this._height();       const update=state=>{if(this._previewAbort!==abort)return;this._state.preview=state;this._state.glbRequests=state.glbRequests||0;this._previewActive=state.active;button.textContent=state.active?'Turn off 3D preview':'Turn on 3D preview';host.hidden=!state.active;status.textContent=state.active?`${state.level||'Loading'}${state.loading?' · loading '+state.wanted:''} · zoom to change detail`:'3D preview off'+(state.stopReason?' · '+state.stopReason:'');this._height();};       try{-        const module=await import(new URL('component-lod-preview.js?v=1.1.9',widgetBase));+        const module={mountLodPreview};         if(abort.signal.aborted){update({active:false,stopReason:'turned off'});return;}         const controller=await module.mountLodPreview({host,manifest:d,resolveAsset:p=>this._url(p,src),onState:update,signal:abort.signal});if(this._previewAbort===abort)this._previewController=controller;else controller.dispose();       }catch(e){update({active:false,error:String(e),stopReason:'Could not start preview'});status.textContent=String(e.message);}
package.json+1−1
@@ -2,7 +2,7 @@   "slug": "component-widgets",   "title": "Component Widgets",   "type": "app",-  "version": "1.1.9",+  "version": "1.1.10",   "description": "Reusable component-page widgets for pinouts, thermal vias, molecule relationships, reviewed chip outlines and image-first LOD caches with an optional zoom-driven 3D preview.",   "license": "MIT",   "dependencies": {},
page.json+6−2
@@ -4,7 +4,7 @@   "type": "app",   "title": "Component Widgets",   "brief": "Reusable component widgets for pinouts, thermal vias, molecule relationships, reviewed chip outlines and lightweight four-level LOD cache listings.",-  "version": "1.1.9",+  "version": "1.1.10",   "visibility": {     "public": true   },@@ -60,6 +60,10 @@     "drop a pinout into a page",     "reusable component widget",     "cross-highlight pins on a page",+    "cached chip thumbnails",+    "reuse chip outline",+    "reviewed MPN placement",+    "chip artwork cache",     "component LOD widget",     "LOD listing",     "LOD manifest",@@ -90,4 +94,4 @@     "hero",     "readme"   ]-}+}\ No newline at end of file
skills/component-widgets/SKILL.md+3
@@ -182,3 +182,6 @@ No 3D viewer, Babylon bundle or GLB may load on the default page path. A user mu Turning the preview off, hiding the tab, scrolling it offscreen or disconnecting the widget must abort pending work, dispose model containers and the viewer/engine, release the WebGL context, and stop timers/render loops. A later activation starts coarse again. Retaining the downloaded JavaScript module is normal; retaining a live engine/model is not. The shared runtime permits one active LOD preview per same-origin BroadcastChannel and uses a 16 MiB decoded geometry budget. Runtime imports can be blocked by a restrictive HTML sandbox; use the registered Markdown widget frame, and do not weaken page authentication or CSP to work around it.  Before publication, test image-only network requests, all four zoom levels, idle rendering, repeat activation/deactivation and live GPU-buffer/context cleanup. Screenshot the real authenticated widget and record purposeful inspection motion. A passing runtime check is separate from native CAD or manufacturing qualification.+++The wiki registry resolves script paths as repository file names: do not append a cache-busting query to the registry `script` URL. Raw `/render/` JavaScript is served as `application/octet-stream` with `nosniff`, so do not dynamically import a companion module from that route. The shared widget bundles its small control runtime and imports only the canonical viewer from the wiki's supported `/static/vendor/adom-3d-viewer-babylon9/` module path after activation. Keep the source module and bundled runtime in sync.
skills/component-widgets/widgets/chip-thumbnail-widget.js+81−2
@@ -1,8 +1,87 @@ /* <adom-chip-thumbnail>: legacy image or reviewed reusable chip-artwork cache. MIT. */ (() => {-  const widgetBase=new URL('.',document.currentScript?.src||'https://wiki.adom.inc/adom/component-widgets/render/widgets/chip-thumbnail-widget.js');   const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));   const CSS = `:host{display:block;color:#dbe2ec;font:14px system-ui;--accent:#00b8b0}*{box-sizing:border-box}.cache{background:#0d1117;border:1px solid #30363d;border-radius:12px;padding:20px}h3{margin:0 0 8px;font-size:20px}.muted{color:#a1abb7;line-height:1.5}.badges{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.badge{padding:5px 9px;border:1px solid #394550;border-radius:6px;font-size:12px}.row{display:grid;grid-template-columns:minmax(220px,1fr) minmax(250px,1fr);gap:24px}.preview{background:#faf8f0;border-radius:9px;overflow:hidden;display:flex;align-items:center;justify-content:center;min-height:250px}.preview img{width:100%;max-height:370px;object-fit:contain;transition:transform .3s}.controls{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}button,a.download{font:inherit;border:1px solid #45505d;border-radius:6px;background:#17202a;color:#e5edf5;padding:8px 12px;cursor:pointer;text-decoration:none}button:hover,a:hover{border-color:var(--accent)}button[aria-pressed=true]{border-color:var(--accent);color:#60e1d6}button:focus-visible,a:focus-visible{outline:2px solid var(--accent);outline-offset:3px}code{overflow-wrap:anywhere;font-size:12px}dl{margin:0}dt{color:#9da8b4;font-size:12px;margin-top:12px}dd{margin:4px 0;line-height:1.5}details{margin-top:16px}summary{cursor:pointer;padding:8px 0}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#161b22;padding:12px;border-radius:6px;font-size:12px}.files{display:grid;gap:9px;margin:12px 0}.files a{color:#6edfd5}.error{color:#ffb7a5}.legacy{width:200px;text-align:center;margin:0}.legacy img{width:100%}.legacy figcaption{padding:8px} @media(max-width:580px){.row{grid-template-columns:1fr}.cache{padding:14px}}`;+// BEGIN bundled opt-in runtime (generated from component-lod-preview.js)+// Opt-in LOD inspection, built on the canonical Adom Babylon viewer.+// No module, engine or model is loaded until the user enables this preview.+const VIEWER='https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js?v=20260828a';+async function mountLodPreview({host,manifest,resolveAsset,onState,signal}) {+ let viewer,engine,scene,root,camera,raf=0,timer,observer,channel,stopped=false;+ let current=null,wanted='super-low',loading=null,candidate=null,since=0,frames=0,lastFrame=0,awakeUntil=0,lastCamera='';+ const cache=new Map(),levels=['super-low','low','medium','high'],thresholds=[90,190,340],budget=16*1024*1024;+ const stats={active:true,loading:true,level:null,wanted,frames:0,downloads:0,glbRequests:0,evictions:0,residentLevels:[],decodedBufferBytes:0,errors:[],initialLevel:'super-low'};+ function report(){stats.frames=frames;stats.wanted=wanted;stats.loading=!!loading;stats.residentLevels=[...cache.values()].map(e=>e.info.level);stats.decodedBufferBytes=[...cache.values()].reduce((s,e)=>s+e.info.decodedBufferBytes,0);onState({...stats});}+ function invalidate(){awakeUntil=performance.now()+1100;if(!raf&&!stopped&&!document.hidden)raf=requestAnimationFrame(render);}+ function render(now){raf=0;if(stopped||document.hidden)return;if(now-lastFrame>=1000/30){engine.beginFrame();try{scene.render();}finally{engine.endFrame();}frames++;lastFrame=now;}if(now<awakeUntil)raf=requestAnimationFrame(render);}+ function dispose(reason='turned off'){+  if(stopped)return;stopped=true;loading?.controller.abort();clearInterval(timer);cancelAnimationFrame(raf);observer?.disconnect();channel?.close();+  document.removeEventListener('visibilitychange',hidden);signal?.removeEventListener('abort',aborted);+  for(const e of cache.values())e.container.dispose();cache.clear();+  const canvas=engine?.getRenderingCanvas();const gl=canvas&&(canvas.getContext('webgl2')||canvas.getContext('webgl'));+  viewer?.destroy();gl?.getExtension('WEBGL_lose_context')?.loseContext();host.replaceChildren();+  stats.active=false;stats.loading=false;stats.level=null;stats.residentLevels=[];stats.decodedBufferBytes=0;stats.stopReason=reason;onState({...stats,frames});+ }+ const hidden=()=>{if(document.hidden)dispose('tab hidden');},aborted=()=>dispose('turned off');+ signal?.addEventListener('abort',aborted,{once:true});+ try{+  if(signal?.aborted)throw new DOMException('Preview cancelled','AbortError');+  if(manifest.upAxis!=='Z'||!['metres','metre'].includes(manifest.units))throw Error('Preview requires a declared Z-up, metre-scale display model.');+  if(levels.some(level=>!manifest.levels.find(l=>l.level===level)?.path))throw Error('All four LOD files must be available before interactive inspection.');+  await import(VIEWER);if(stopped||signal?.aborted)return {dispose,state:()=>({...stats})};+  const api=window.Adom3DViewerBabylon9,B=api.BABYLON;+  viewer=api.init(host,{zUp:true,showViewCube:true});engine=viewer.getEngine();scene=viewer.getScene();scene.useRightHandedSystem=true;camera=viewer.getCamera();+  engine.stopRenderLoop();engine.runRenderLoop=()=>invalidate();+  viewer.setGroundVisible(false);scene.shadowsEnabled=false;viewer.getShadowGenerator()?.dispose();root=new B.TransformNode('component-z-up-mm',scene);root.scaling.setAll(1000);viewer.addContentRoot(root);+  const fill=new B.HemisphericLight('lod-inspection-fill',new B.Vector3(0,0,1),scene);fill.intensity=.9;fill.groundColor=new B.Color3(.5,.5,.5);+  const canvas=engine.getRenderingCanvas();for(const event of ['pointerdown','pointermove','pointerup','wheel','keydown'])canvas.addEventListener(event,invalidate,{passive:true});+  document.addEventListener('visibilitychange',hidden);+  observer=new IntersectionObserver(entries=>{if(!entries[0].isIntersecting)dispose('preview scrolled offscreen');});observer.observe(host);+  try{channel=new BroadcastChannel('adom-component-lod-preview');channel.onmessage=()=>dispose('another component preview enabled');channel.postMessage({enabled:true});}catch{channel=null;}+  function evict(){for(const [key,e] of cache)if(e!==current&&e.info.level!=='super-low'){e.container.dispose();cache.delete(key);stats.evictions++;}}+  function apply(e,level){+   if(current!==e){current?.container.removeAllFromScene();e.container.addAllToScene();for(const n of e.container.rootNodes)if(!n.parent)n.parent=root;current=e;}+   stats.level=level;evict();invalidate();report();+  }+  async function load(level){+   const info=manifest.levels.find(l=>l.level===level);if(cache.has(info.sha256)){apply(cache.get(info.sha256),level);return;}+   if(loading)return;+   evict();if(info.decodedBufferBytes+[...cache.values()].reduce((s,e)=>s+e.info.decodedBufferBytes,0)>budget)throw Error('Component preview exceeds the 16 MiB decoded-geometry budget.');+   const request={level,controller:new AbortController()};loading=request;report();+   let container;+   try{+    stats.glbRequests++;const r=await fetch(resolveAsset(info.path),{credentials:'include',signal:request.controller.signal});if(!r.ok)throw Error('LOD request returned '+r.status);+    const data=await r.arrayBuffer();if(data.byteLength!==info.sizeBytes)throw Error('LOD byte count does not match manifest.');+    const hash=[...new Uint8Array(await crypto.subtle.digest('SHA-256',data))].map(x=>x.toString(16).padStart(2,'0')).join('');if(hash!==info.sha256)throw Error('LOD SHA-256 does not match manifest.');+    if(stopped)return;+    const url=URL.createObjectURL(new Blob([data],{type:'model/gltf-binary'}));+    try{container=await B.SceneLoader.LoadAssetContainerAsync('',url,scene,undefined,'.glb');}finally{URL.revokeObjectURL(url);}+    if(stopped||wanted!==level){container.dispose();return;}+    const e={info,container};cache.set(info.sha256,e);stats.downloads++;apply(e,level);+   }catch(e){container?.dispose();if(e.name!=='AbortError'&&!stopped){stats.errors.push(String(e));dispose('model loading failed');throw e;}}+   finally{if(loading===request)loading=null;if(!stopped)report();}+  }+  await load('super-low');if(stopped)return {dispose,state:()=>({...stats})};+  const box=manifest.levels.find(l=>l.level==='super-low').bounds;+  const min=B.Vector3.FromArray(box.min).scale(1000),max=B.Vector3.FromArray(box.max).scale(1000),diameter=max.subtract(min).length();+  const center=min.add(max).scale(.5);camera.minZ=.001;camera.lowerRadiusLimit=Math.max(.01,diameter*.35);camera.upperRadiusLimit=diameter*35;+  camera.target=center;camera.setPosition(center.add(new B.Vector3(2,-3,3).normalize().scale(diameter*12)));+  const tick=()=>{+   if(stopped)return;+   const sig=JSON.stringify([camera.alpha,camera.beta,camera.radius,...camera.target.asArray(),host.clientWidth,host.clientHeight].map(n=>Math.round(n*1e7)/1e7));+   if(sig!==lastCamera){lastCamera=sig;engine.resize();invalidate();}+   const distance=B.Vector3.Distance(camera.globalPosition,center),pixels=diameter*host.clientHeight/(2*Math.max(.01,distance)*Math.tan(camera.fov/2));+   let index=levels.indexOf(wanted);while(index<3&&pixels>thresholds[index]*1.2)index++;while(index>0&&pixels<thresholds[index-1]*.8)index--;+   if(levels[index]!==candidate){candidate=levels[index];since=performance.now();}+   if(performance.now()-since>350){wanted=candidate;if(loading&&loading.level!==wanted)loading.controller.abort();if(!loading&&stats.level!==wanted)void load(wanted).catch(e=>{stats.errors.push(String(e));dispose('preview error');});}+   report();+  };+  timer=setInterval(tick,150);invalidate();report();+  return {dispose,state:()=>({...stats}),inspectDistance:multiple=>{camera.radius=diameter*multiple;invalidate();},camera:()=>({radius:camera.radius,diameter,alpha:camera.alpha,beta:camera.beta,target:camera.target.asArray(),size:[host.clientWidth,host.clientHeight]})};+ }catch(e){dispose(e.name==='AbortError'?'turned off':String(e));throw e;}+}++// END bundled opt-in runtime   class AdomChipThumbnail extends HTMLElement {     static get observedAttributes(){return ['src','mpn','package'];}     set data(value){this._data=value;if(this.shadowRoot)this._load();}@@ -47,7 +126,7 @@       host.hidden=false;button.textContent='Turn off 3D preview';status.textContent='Loading super-low detail…';this._height();       const update=state=>{if(this._previewAbort!==abort)return;this._state.preview=state;this._state.glbRequests=state.glbRequests||0;this._previewActive=state.active;button.textContent=state.active?'Turn off 3D preview':'Turn on 3D preview';host.hidden=!state.active;status.textContent=state.active?`${state.level||'Loading'}${state.loading?' · loading '+state.wanted:''} · zoom to change detail`:'3D preview off'+(state.stopReason?' · '+state.stopReason:'');this._height();};       try{-        const module=await import(new URL('component-lod-preview.js?v=1.1.9',widgetBase));+        const module={mountLodPreview};         if(abort.signal.aborted){update({active:false,stopReason:'turned off'});return;}         const controller=await module.mountLodPreview({host,manifest:d,resolveAsset:p=>this._url(p,src),onState:update,signal:abort.signal});if(this._previewAbort===abort)this._previewController=controller;else controller.dispose();       }catch(e){update({active:false,error:String(e),stopReason:'Could not start preview'});status.textContent=String(e.message);}
widgets/chip-thumbnail-widget.js+81−2
@@ -1,8 +1,87 @@ /* <adom-chip-thumbnail>: legacy image or reviewed reusable chip-artwork cache. MIT. */ (() => {-  const widgetBase=new URL('.',document.currentScript?.src||'https://wiki.adom.inc/adom/component-widgets/render/widgets/chip-thumbnail-widget.js');   const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));   const CSS = `:host{display:block;color:#dbe2ec;font:14px system-ui;--accent:#00b8b0}*{box-sizing:border-box}.cache{background:#0d1117;border:1px solid #30363d;border-radius:12px;padding:20px}h3{margin:0 0 8px;font-size:20px}.muted{color:#a1abb7;line-height:1.5}.badges{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.badge{padding:5px 9px;border:1px solid #394550;border-radius:6px;font-size:12px}.row{display:grid;grid-template-columns:minmax(220px,1fr) minmax(250px,1fr);gap:24px}.preview{background:#faf8f0;border-radius:9px;overflow:hidden;display:flex;align-items:center;justify-content:center;min-height:250px}.preview img{width:100%;max-height:370px;object-fit:contain;transition:transform .3s}.controls{display:flex;gap:8px;flex-wrap:wrap;margin:12px 0}button,a.download{font:inherit;border:1px solid #45505d;border-radius:6px;background:#17202a;color:#e5edf5;padding:8px 12px;cursor:pointer;text-decoration:none}button:hover,a:hover{border-color:var(--accent)}button[aria-pressed=true]{border-color:var(--accent);color:#60e1d6}button:focus-visible,a:focus-visible{outline:2px solid var(--accent);outline-offset:3px}code{overflow-wrap:anywhere;font-size:12px}dl{margin:0}dt{color:#9da8b4;font-size:12px;margin-top:12px}dd{margin:4px 0;line-height:1.5}details{margin-top:16px}summary{cursor:pointer;padding:8px 0}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#161b22;padding:12px;border-radius:6px;font-size:12px}.files{display:grid;gap:9px;margin:12px 0}.files a{color:#6edfd5}.error{color:#ffb7a5}.legacy{width:200px;text-align:center;margin:0}.legacy img{width:100%}.legacy figcaption{padding:8px} @media(max-width:580px){.row{grid-template-columns:1fr}.cache{padding:14px}}`;+// BEGIN bundled opt-in runtime (generated from component-lod-preview.js)+// Opt-in LOD inspection, built on the canonical Adom Babylon viewer.+// No module, engine or model is loaded until the user enables this preview.+const VIEWER='https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js?v=20260828a';+async function mountLodPreview({host,manifest,resolveAsset,onState,signal}) {+ let viewer,engine,scene,root,camera,raf=0,timer,observer,channel,stopped=false;+ let current=null,wanted='super-low',loading=null,candidate=null,since=0,frames=0,lastFrame=0,awakeUntil=0,lastCamera='';+ const cache=new Map(),levels=['super-low','low','medium','high'],thresholds=[90,190,340],budget=16*1024*1024;+ const stats={active:true,loading:true,level:null,wanted,frames:0,downloads:0,glbRequests:0,evictions:0,residentLevels:[],decodedBufferBytes:0,errors:[],initialLevel:'super-low'};+ function report(){stats.frames=frames;stats.wanted=wanted;stats.loading=!!loading;stats.residentLevels=[...cache.values()].map(e=>e.info.level);stats.decodedBufferBytes=[...cache.values()].reduce((s,e)=>s+e.info.decodedBufferBytes,0);onState({...stats});}+ function invalidate(){awakeUntil=performance.now()+1100;if(!raf&&!stopped&&!document.hidden)raf=requestAnimationFrame(render);}+ function render(now){raf=0;if(stopped||document.hidden)return;if(now-lastFrame>=1000/30){engine.beginFrame();try{scene.render();}finally{engine.endFrame();}frames++;lastFrame=now;}if(now<awakeUntil)raf=requestAnimationFrame(render);}+ function dispose(reason='turned off'){+  if(stopped)return;stopped=true;loading?.controller.abort();clearInterval(timer);cancelAnimationFrame(raf);observer?.disconnect();channel?.close();+  document.removeEventListener('visibilitychange',hidden);signal?.removeEventListener('abort',aborted);+  for(const e of cache.values())e.container.dispose();cache.clear();+  const canvas=engine?.getRenderingCanvas();const gl=canvas&&(canvas.getContext('webgl2')||canvas.getContext('webgl'));+  viewer?.destroy();gl?.getExtension('WEBGL_lose_context')?.loseContext();host.replaceChildren();+  stats.active=false;stats.loading=false;stats.level=null;stats.residentLevels=[];stats.decodedBufferBytes=0;stats.stopReason=reason;onState({...stats,frames});+ }+ const hidden=()=>{if(document.hidden)dispose('tab hidden');},aborted=()=>dispose('turned off');+ signal?.addEventListener('abort',aborted,{once:true});+ try{+  if(signal?.aborted)throw new DOMException('Preview cancelled','AbortError');+  if(manifest.upAxis!=='Z'||!['metres','metre'].includes(manifest.units))throw Error('Preview requires a declared Z-up, metre-scale display model.');+  if(levels.some(level=>!manifest.levels.find(l=>l.level===level)?.path))throw Error('All four LOD files must be available before interactive inspection.');+  await import(VIEWER);if(stopped||signal?.aborted)return {dispose,state:()=>({...stats})};+  const api=window.Adom3DViewerBabylon9,B=api.BABYLON;+  viewer=api.init(host,{zUp:true,showViewCube:true});engine=viewer.getEngine();scene=viewer.getScene();scene.useRightHandedSystem=true;camera=viewer.getCamera();+  engine.stopRenderLoop();engine.runRenderLoop=()=>invalidate();+  viewer.setGroundVisible(false);scene.shadowsEnabled=false;viewer.getShadowGenerator()?.dispose();root=new B.TransformNode('component-z-up-mm',scene);root.scaling.setAll(1000);viewer.addContentRoot(root);+  const fill=new B.HemisphericLight('lod-inspection-fill',new B.Vector3(0,0,1),scene);fill.intensity=.9;fill.groundColor=new B.Color3(.5,.5,.5);+  const canvas=engine.getRenderingCanvas();for(const event of ['pointerdown','pointermove','pointerup','wheel','keydown'])canvas.addEventListener(event,invalidate,{passive:true});+  document.addEventListener('visibilitychange',hidden);+  observer=new IntersectionObserver(entries=>{if(!entries[0].isIntersecting)dispose('preview scrolled offscreen');});observer.observe(host);+  try{channel=new BroadcastChannel('adom-component-lod-preview');channel.onmessage=()=>dispose('another component preview enabled');channel.postMessage({enabled:true});}catch{channel=null;}+  function evict(){for(const [key,e] of cache)if(e!==current&&e.info.level!=='super-low'){e.container.dispose();cache.delete(key);stats.evictions++;}}+  function apply(e,level){+   if(current!==e){current?.container.removeAllFromScene();e.container.addAllToScene();for(const n of e.container.rootNodes)if(!n.parent)n.parent=root;current=e;}+   stats.level=level;evict();invalidate();report();+  }+  async function load(level){+   const info=manifest.levels.find(l=>l.level===level);if(cache.has(info.sha256)){apply(cache.get(info.sha256),level);return;}+   if(loading)return;+   evict();if(info.decodedBufferBytes+[...cache.values()].reduce((s,e)=>s+e.info.decodedBufferBytes,0)>budget)throw Error('Component preview exceeds the 16 MiB decoded-geometry budget.');+   const request={level,controller:new AbortController()};loading=request;report();+   let container;+   try{+    stats.glbRequests++;const r=await fetch(resolveAsset(info.path),{credentials:'include',signal:request.controller.signal});if(!r.ok)throw Error('LOD request returned '+r.status);+    const data=await r.arrayBuffer();if(data.byteLength!==info.sizeBytes)throw Error('LOD byte count does not match manifest.');+    const hash=[...new Uint8Array(await crypto.subtle.digest('SHA-256',data))].map(x=>x.toString(16).padStart(2,'0')).join('');if(hash!==info.sha256)throw Error('LOD SHA-256 does not match manifest.');+    if(stopped)return;+    const url=URL.createObjectURL(new Blob([data],{type:'model/gltf-binary'}));+    try{container=await B.SceneLoader.LoadAssetContainerAsync('',url,scene,undefined,'.glb');}finally{URL.revokeObjectURL(url);}+    if(stopped||wanted!==level){container.dispose();return;}+    const e={info,container};cache.set(info.sha256,e);stats.downloads++;apply(e,level);+   }catch(e){container?.dispose();if(e.name!=='AbortError'&&!stopped){stats.errors.push(String(e));dispose('model loading failed');throw e;}}+   finally{if(loading===request)loading=null;if(!stopped)report();}+  }+  await load('super-low');if(stopped)return {dispose,state:()=>({...stats})};+  const box=manifest.levels.find(l=>l.level==='super-low').bounds;+  const min=B.Vector3.FromArray(box.min).scale(1000),max=B.Vector3.FromArray(box.max).scale(1000),diameter=max.subtract(min).length();+  const center=min.add(max).scale(.5);camera.minZ=.001;camera.lowerRadiusLimit=Math.max(.01,diameter*.35);camera.upperRadiusLimit=diameter*35;+  camera.target=center;camera.setPosition(center.add(new B.Vector3(2,-3,3).normalize().scale(diameter*12)));+  const tick=()=>{+   if(stopped)return;+   const sig=JSON.stringify([camera.alpha,camera.beta,camera.radius,...camera.target.asArray(),host.clientWidth,host.clientHeight].map(n=>Math.round(n*1e7)/1e7));+   if(sig!==lastCamera){lastCamera=sig;engine.resize();invalidate();}+   const distance=B.Vector3.Distance(camera.globalPosition,center),pixels=diameter*host.clientHeight/(2*Math.max(.01,distance)*Math.tan(camera.fov/2));+   let index=levels.indexOf(wanted);while(index<3&&pixels>thresholds[index]*1.2)index++;while(index>0&&pixels<thresholds[index-1]*.8)index--;+   if(levels[index]!==candidate){candidate=levels[index];since=performance.now();}+   if(performance.now()-since>350){wanted=candidate;if(loading&&loading.level!==wanted)loading.controller.abort();if(!loading&&stats.level!==wanted)void load(wanted).catch(e=>{stats.errors.push(String(e));dispose('preview error');});}+   report();+  };+  timer=setInterval(tick,150);invalidate();report();+  return {dispose,state:()=>({...stats}),inspectDistance:multiple=>{camera.radius=diameter*multiple;invalidate();},camera:()=>({radius:camera.radius,diameter,alpha:camera.alpha,beta:camera.beta,target:camera.target.asArray(),size:[host.clientWidth,host.clientHeight]})};+ }catch(e){dispose(e.name==='AbortError'?'turned off':String(e));throw e;}+}++// END bundled opt-in runtime   class AdomChipThumbnail extends HTMLElement {     static get observedAttributes(){return ['src','mpn','package'];}     set data(value){this._data=value;if(this.shadowRoot)this._load();}@@ -47,7 +126,7 @@       host.hidden=false;button.textContent='Turn off 3D preview';status.textContent='Loading super-low detail…';this._height();       const update=state=>{if(this._previewAbort!==abort)return;this._state.preview=state;this._state.glbRequests=state.glbRequests||0;this._previewActive=state.active;button.textContent=state.active?'Turn off 3D preview':'Turn on 3D preview';host.hidden=!state.active;status.textContent=state.active?`${state.level||'Loading'}${state.loading?' · loading '+state.wanted:''} · zoom to change detail`:'3D preview off'+(state.stopReason?' · '+state.stopReason:'');this._height();};       try{-        const module=await import(new URL('component-lod-preview.js?v=1.1.9',widgetBase));+        const module={mountLodPreview};         if(abort.signal.aborted){update({active:false,stopReason:'turned off'});return;}         const controller=await module.mountLodPreview({host,manifest:d,resolveAsset:p=>this._url(p,src),onState:update,signal:abort.signal});if(this._previewAbort===abort)this._previewController=controller;else controller.dispose();       }catch(e){update({active:false,error:String(e),stopReason:'Could not start preview'});status.textContent=String(e.message);}