← Commit history
SKILL.md+24
@@ -133,3 +133,27 @@ The cache widget focuses on chip outlines and their reuse. Do not repeat shaded chip renders here: the component page already has its full 3D viewer. Existing shaded thumbnail files can stay cached for library/card consumers without being displayed in this widget. Legacy image-only widget usage is unchanged.++## Component LOD listing++Use `::adom-component-lod{src="/OWNER/COMPONENT/render/lod/manifest.json"}`+for a compact four-level listing. The `adom/component-lod@1` manifest records+high, medium, low and super-low files, sizes, decoded attribute/index estimates,+triangles, primitives, hashes and review status. Missing levels remain visible+as not generated; candidates never become visually approved through this widget.+The widget uses the existing cache implementation and creates no WebGL context,+fetches no GLB, and duplicates no shaded chip viewer. The chip outline widget+remains focused on cached symbol artwork.++Reuse the shared `wiki-component-lod` skill for generation, coordinate/MPN+fidelity and runtime budgets. LOD means level of detail; LDO is only a discovery+alias. Place manifests and assets in the existing component repo, including+existing Basic Parts only when the user authorizes that scope. Preserve the+manufacturer STEP and existing page content. A demo board or insertion-scene GLB+is not an isolated component asset. Mark missing/uncertain source geometry+explicitly until reconciled; do not invent a qualified LOD set.++The legacy `adom-chip-thumbnail` tag also accepts this LOD manifest. In a+network-restricted HTML README, inline the widget script and JSON instead of+fetching. Use `selfCheck()` for available levels, source hash, axes, review state+and AI hints. Metrics are manifest-reported, not live hash or GPU validation.
files/chip-thumbnail-widget.js+29−1
@@ -21,6 +21,7 @@         }         if(token!==this._request)return;         if(d?.schema==='adom/chip-artwork-cache@1')this._cache(d,src);+        else if(d?.schema==='adom/component-lod@1')this._lod(d,src);         else if(d?.schema)throw new Error('Unsupported cache schema: '+d.schema);         else this._legacy(d||{src,mpn:this.getAttribute('mpn'),package:this.getAttribute('package')});       }catch(e){if(token===this._request){this._state={ok:false,error:String(e),hints:['Check the manifest URL, permissions and schema. No regeneration was attempted.']};this.shadowRoot.innerHTML=`<style>${CSS}</style><div class="cache error">${esc(e.message)}</div>`;}}@@ -37,6 +38,32 @@       this.shadowRoot.innerHTML=`<style>${CSS}</style><figure class="cache legacy">${src&&valid?`<img alt="${esc(d.mpn)}" src="${esc(src)}">`:'<div class="muted">No render cached</div>'}<figcaption>${esc(d.mpn)} ${esc(d.package)}</figcaption></figure>`;       const img=this.shadowRoot.querySelector('img');if(img){img.onerror=()=>{img.replaceWith(document.createTextNode('Render unavailable'));this._height();};img.onload=()=>this._height();}this._state={ok:true,mode:'legacy-image'};     }+    _lod(d,src){+      if(!Array.isArray(d.levels)||d.levels.length>4)throw new Error('LOD manifest must list at most four detail levels');+      const order=['high','medium','low','super-low'],seen=new Set();+      for(const l of d.levels){if(!order.includes(l.level)||seen.has(l.level))throw new Error('Unknown or duplicate LOD level');seen.add(l.level);}+      const sourceHash=d.source?.sha256,validSource=/^[a-f0-9]{64}$/i.test(sourceHash||'');+      const rows=order.map(level=>{+        const l=d.levels.find(x=>x.level===level);+        if(!l||!l.path)return {level,available:false,status:l?.review||d.missingReason||'Not generated'};+        const fields=['sizeBytes','decodedBufferBytes','triangles','primitives'];+        if(!/^[a-f0-9]{64}$/i.test(l.sha256||'')||fields.some(k=>!Number.isSafeInteger(l[k])||l[k]<0))throw new Error('Invalid hash or metrics for '+level);+        return {...l,available:true,url:this._url(l.path,src),status:l.review||'Review not recorded'};+      });+      const count=rows.filter(r=>r.available).length,num=n=>Number(n).toLocaleString('en-US'),kib=n=>(n/1024).toFixed(1)+' KiB';+      this._state={ok:true,mode:'lod',mpn:d.mpn,manifest:src,sourceHash:sourceHash||null,sourceHashRecorded:validSource,+        upAxis:d.upAxis||'unverified',units:d.units||'unverified',status:d.status||'unreviewed',levels:rows.map(({url,...r})=>r),+        availableLevels:count,glbRequests:0,hints:['Read this manifest and verify exact source/level hashes before reuse.','This listing does not fetch or decode GLBs. Download size is not GPU memory.','Missing or candidate detail is not visually or physically qualified.',...(d.hints||[])]};+      const sourceLink=d.source?.path?this._url(d.source.path,src):null;+      this.shadowRoot.innerHTML=`<style>${CSS}+        .table-scroll{overflow-x:auto;margin:18px 0}table{border-collapse:collapse;width:100%;text-align:left;font-size:13px;color:#dbe2ec;font-family:system-ui,sans-serif}caption{text-align:left;padding:0 0 10px;color:#a1abb7}th,td{padding:10px 12px;border-bottom:1px solid #30363d;vertical-align:top}th{color:#a1abb7;font-weight:500;white-space:nowrap}th:first-child,td:first-child{padding-left:0}td:nth-child(2),td:nth-child(3),td:nth-child(4),td:nth-child(5){white-space:nowrap;font-variant-numeric:tabular-nums}.lod a{color:#6edfd5}.lod details code{display:block;margin:8px 0}.status{max-width:340px;line-height:1.5}.missing{color:#e8b66f}.lod .manifest-link{display:inline-block;margin-top:8px}+        </style><section class="cache lod"><h3>Component LODs</h3><div class="muted">${esc(d.mpn)} · Cached levels of detail for board and scaffold viewers.</div>+        <div class="badges"><span class="badge">${count}/4 levels available</span><span class="badge">${esc(d.upAxis?d.upAxis+'-up':'Orientation unverified')} · ${esc(d.units||'units unverified')}</span><span class="badge">${esc(d.status||'Review not recorded')}</span></div>+        <div class="table-scroll"><table><caption>Start with super-low; load more detail when inspection needs it.</caption><thead><tr><th scope="col">Detail</th><th scope="col">Download</th><th scope="col">Decoded buffers*</th><th scope="col">Triangles</th><th scope="col">Primitives</th><th scope="col">Review</th></tr></thead><tbody>${rows.map(r=>r.available?`<tr data-level="${r.level}"><th scope="row">${esc(r.level)}</th><td><a href="${esc(r.url)}" download="${esc(r.path.split('/').pop())}">${kib(r.sizeBytes)} GLB</a></td><td>${kib(r.decodedBufferBytes)}</td><td>${num(r.triangles)}</td><td>${num(r.primitives)}</td><td class="status">${esc(r.status)}${r.detailVisibility?`<div class="muted">MPN: ${r.detailVisibility.virtualMpn?'included':'omitted at this scale'}</div>`:''}</td></tr>`:`<tr data-level="${r.level}"><th scope="row">${esc(r.level)}</th><td colspan="5" class="missing">${esc(r.status)}</td></tr>`).join('')}</tbody></table></div>+        <p class="muted">*Manifest-reported attribute/index bytes. Excludes textures, the board, renderer overhead and driver allocations. This widget loads no 3D models.</p>+        <details><summary>Source hashes and reuse contract</summary><p class="muted">${validSource?'Source SHA-256 is recorded; verify it before reuse.':'Source hash is missing; resolve provenance before generation.'}</p>${sourceLink?`<a href="${esc(sourceLink)}" download>Source component asset</a>`:''}<code>${esc(sourceHash||'No source hash recorded')}</code>${rows.filter(r=>r.available).map(r=>`<code>${esc(r.level)}: ${esc(r.sha256)}</code>`).join('')}<pre>${esc((d.hints||[]).join('\n'))}</pre></details>${src?`<a class="manifest-link" href="${esc(new URL(src,location.href).href)}" download>LOD manifest for AI and viewer reuse</a>`:''}</section>`;+      this.shadowRoot.querySelector('details').addEventListener('toggle',()=>this._height());+    }     _cache(d,src){       if(!Array.isArray(d.assets)||d.assets.length>100)throw new Error('Invalid asset list');       const assets=d.assets.filter(a=>a.kind!=='chip-shaded').map(a=>({...a,url:this._url(a.path,src)}));@@ -53,7 +80,8 @@       if(src){const a=document.createElement('a');a.href=src;a.download='';a.textContent='Cache manifest and source hashes';files.append(a);}       this.shadowRoot.querySelector('details').addEventListener('toggle',()=>this._height());     }-    selfCheck(){const s=structuredClone(this._state||{ok:false,status:'loading'}),img=this.shadowRoot?.querySelector('img');s.previewLoaded=!!(img?.complete&&img.naturalWidth>0);return s;}+    selfCheck(){const s=structuredClone(this._state||{ok:false,status:'loading'}),img=this.shadowRoot?.querySelector('img');if(s.mode!=='lod')s.previewLoaded=!!(img?.complete&&img.naturalWidth>0);return s;}   }   if(!customElements.get('adom-chip-thumbnail'))customElements.define('adom-chip-thumbnail',AdomChipThumbnail);+  if(!customElements.get('adom-component-lod'))customElements.define('adom-component-lod',class extends AdomChipThumbnail {}); })();
package.json+6−2
@@ -2,7 +2,7 @@   "slug": "component-widgets",   "title": "Component Widgets",   "type": "app",-  "version": "1.1.7",+  "version": "1.1.8",   "description": "Reusable, dependency-free web-component widgets to drop into any component or molecule page: <adom-pinout> (datasheet-style pin diagram + synced table), <adom-heatsink-via> (exposed-pad thermal via array, only when the part has an EP), <adom-molecule> (ganged part set + nets), and <adom-chip-thumbnail> (rendered part image). Each is one custom element, self-contained (Shadow DOM, no deps), theme-aware (light + dark), fed by inline JSON or el.data, and all on one lbr-hover cross-highlight bus so a pin hovered in one lights the same pin everywhere. Symbol, footprint, and 3D stay with adom-lbr; the datasheet renders via the native viewer. Installs the widget JS, iframe-embeddable viewers, example data, and an AI skill to ~/.claude/skills/component-widgets. Live reference page renders them all as a readme.html.",   "license": "MIT",   "dependencies": {},@@ -42,7 +42,11 @@     "cached chip thumbnails",     "reuse chip outline",     "reviewed MPN placement",-    "chip artwork cache"+    "chip artwork cache",+    "component LOD widget",+    "LOD listing",+    "LOD manifest",+    "high medium low super-low"   ],   "discovery_pitch": "Reach for component-widgets when a component or molecule page needs a live pinout, an exposed-pad thermal via pattern, a ganged molecule/sub-circuit view, or a part thumbnail: each is one self-contained <adom-*> tag fed by inline JSON, theme-aware, and cross-linked with adom-lbr's symbol/footprint/3D on one hover bus.",   "sample_prompts": [
page.json+12−4
@@ -3,8 +3,8 @@   "slug": "component-widgets",   "type": "app",   "title": "Component Widgets",-  "brief": "One home for the reusable, readme-instantiable component/molecule widgets (pinout, heat-sink via, chip thumbnail, molecule) \u2014 self-contained web components on the shared adom-lbr hover bus.",-  "version": "1.1.7",+  "brief": "One home for the reusable, readme-instantiable component/molecule widgets (pinout, heat-sink via, chip thumbnail, molecule) — self-contained web components on the shared adom-lbr hover bus.",+  "version": "1.1.8",   "visibility": {     "public": true   },@@ -59,7 +59,15 @@     "readme-drop-in widget",     "drop a pinout into a page",     "reusable component widget",-    "cross-highlight pins on a page"+    "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",+    "high medium low super-low"   ],   "discovery_pitch": "Reach for component-widgets when a component or molecule page needs a live pinout, an exposed-pad thermal via pattern, a ganged molecule/sub-circuit view, or a part thumbnail: each is one self-contained <adom-*> tag fed by inline JSON, theme-aware, and cross-linked with adom-lbr's symbol/footprint/3D on one hover bus.",   "metadata": {},@@ -83,4 +91,4 @@     "hero",     "readme"   ]-}+}\ No newline at end of file
skills/component-widgets/SKILL.md+24
@@ -148,3 +148,27 @@ The cache widget focuses on chip outlines and their reuse. Do not repeat shaded chip renders here: the component page already has its full 3D viewer. Existing shaded thumbnail files can stay cached for library/card consumers without being displayed in this widget. Legacy image-only widget usage is unchanged.++## Component LOD listing++Use `::adom-component-lod{src="/OWNER/COMPONENT/render/lod/manifest.json"}`+for a compact four-level listing. The `adom/component-lod@1` manifest records+high, medium, low and super-low files, sizes, decoded attribute/index estimates,+triangles, primitives, hashes and review status. Missing levels remain visible+as not generated; candidates never become visually approved through this widget.+The widget uses the existing cache implementation and creates no WebGL context,+fetches no GLB, and duplicates no shaded chip viewer. The chip outline widget+remains focused on cached symbol artwork.++Reuse the shared `wiki-component-lod` skill for generation, coordinate/MPN+fidelity and runtime budgets. LOD means level of detail; LDO is only a discovery+alias. Place manifests and assets in the existing component repo, including+existing Basic Parts only when the user authorizes that scope. Preserve the+manufacturer STEP and existing page content. A demo board or insertion-scene GLB+is not an isolated component asset. Mark missing/uncertain source geometry+explicitly until reconciled; do not invent a qualified LOD set.++The legacy `adom-chip-thumbnail` tag also accepts this LOD manifest. In a+network-restricted HTML README, inline the widget script and JSON instead of+fetching. Use `selfCheck()` for available levels, source hash, axes, review state+and AI hints. Metrics are manifest-reported, not live hash or GPU validation.
skills/component-widgets/widgets/chip-thumbnail-widget.js+29−1
@@ -21,6 +21,7 @@         }         if(token!==this._request)return;         if(d?.schema==='adom/chip-artwork-cache@1')this._cache(d,src);+        else if(d?.schema==='adom/component-lod@1')this._lod(d,src);         else if(d?.schema)throw new Error('Unsupported cache schema: '+d.schema);         else this._legacy(d||{src,mpn:this.getAttribute('mpn'),package:this.getAttribute('package')});       }catch(e){if(token===this._request){this._state={ok:false,error:String(e),hints:['Check the manifest URL, permissions and schema. No regeneration was attempted.']};this.shadowRoot.innerHTML=`<style>${CSS}</style><div class="cache error">${esc(e.message)}</div>`;}}@@ -37,6 +38,32 @@       this.shadowRoot.innerHTML=`<style>${CSS}</style><figure class="cache legacy">${src&&valid?`<img alt="${esc(d.mpn)}" src="${esc(src)}">`:'<div class="muted">No render cached</div>'}<figcaption>${esc(d.mpn)} ${esc(d.package)}</figcaption></figure>`;       const img=this.shadowRoot.querySelector('img');if(img){img.onerror=()=>{img.replaceWith(document.createTextNode('Render unavailable'));this._height();};img.onload=()=>this._height();}this._state={ok:true,mode:'legacy-image'};     }+    _lod(d,src){+      if(!Array.isArray(d.levels)||d.levels.length>4)throw new Error('LOD manifest must list at most four detail levels');+      const order=['high','medium','low','super-low'],seen=new Set();+      for(const l of d.levels){if(!order.includes(l.level)||seen.has(l.level))throw new Error('Unknown or duplicate LOD level');seen.add(l.level);}+      const sourceHash=d.source?.sha256,validSource=/^[a-f0-9]{64}$/i.test(sourceHash||'');+      const rows=order.map(level=>{+        const l=d.levels.find(x=>x.level===level);+        if(!l||!l.path)return {level,available:false,status:l?.review||d.missingReason||'Not generated'};+        const fields=['sizeBytes','decodedBufferBytes','triangles','primitives'];+        if(!/^[a-f0-9]{64}$/i.test(l.sha256||'')||fields.some(k=>!Number.isSafeInteger(l[k])||l[k]<0))throw new Error('Invalid hash or metrics for '+level);+        return {...l,available:true,url:this._url(l.path,src),status:l.review||'Review not recorded'};+      });+      const count=rows.filter(r=>r.available).length,num=n=>Number(n).toLocaleString('en-US'),kib=n=>(n/1024).toFixed(1)+' KiB';+      this._state={ok:true,mode:'lod',mpn:d.mpn,manifest:src,sourceHash:sourceHash||null,sourceHashRecorded:validSource,+        upAxis:d.upAxis||'unverified',units:d.units||'unverified',status:d.status||'unreviewed',levels:rows.map(({url,...r})=>r),+        availableLevels:count,glbRequests:0,hints:['Read this manifest and verify exact source/level hashes before reuse.','This listing does not fetch or decode GLBs. Download size is not GPU memory.','Missing or candidate detail is not visually or physically qualified.',...(d.hints||[])]};+      const sourceLink=d.source?.path?this._url(d.source.path,src):null;+      this.shadowRoot.innerHTML=`<style>${CSS}+        .table-scroll{overflow-x:auto;margin:18px 0}table{border-collapse:collapse;width:100%;text-align:left;font-size:13px;color:#dbe2ec;font-family:system-ui,sans-serif}caption{text-align:left;padding:0 0 10px;color:#a1abb7}th,td{padding:10px 12px;border-bottom:1px solid #30363d;vertical-align:top}th{color:#a1abb7;font-weight:500;white-space:nowrap}th:first-child,td:first-child{padding-left:0}td:nth-child(2),td:nth-child(3),td:nth-child(4),td:nth-child(5){white-space:nowrap;font-variant-numeric:tabular-nums}.lod a{color:#6edfd5}.lod details code{display:block;margin:8px 0}.status{max-width:340px;line-height:1.5}.missing{color:#e8b66f}.lod .manifest-link{display:inline-block;margin-top:8px}+        </style><section class="cache lod"><h3>Component LODs</h3><div class="muted">${esc(d.mpn)} · Cached levels of detail for board and scaffold viewers.</div>+        <div class="badges"><span class="badge">${count}/4 levels available</span><span class="badge">${esc(d.upAxis?d.upAxis+'-up':'Orientation unverified')} · ${esc(d.units||'units unverified')}</span><span class="badge">${esc(d.status||'Review not recorded')}</span></div>+        <div class="table-scroll"><table><caption>Start with super-low; load more detail when inspection needs it.</caption><thead><tr><th scope="col">Detail</th><th scope="col">Download</th><th scope="col">Decoded buffers*</th><th scope="col">Triangles</th><th scope="col">Primitives</th><th scope="col">Review</th></tr></thead><tbody>${rows.map(r=>r.available?`<tr data-level="${r.level}"><th scope="row">${esc(r.level)}</th><td><a href="${esc(r.url)}" download="${esc(r.path.split('/').pop())}">${kib(r.sizeBytes)} GLB</a></td><td>${kib(r.decodedBufferBytes)}</td><td>${num(r.triangles)}</td><td>${num(r.primitives)}</td><td class="status">${esc(r.status)}${r.detailVisibility?`<div class="muted">MPN: ${r.detailVisibility.virtualMpn?'included':'omitted at this scale'}</div>`:''}</td></tr>`:`<tr data-level="${r.level}"><th scope="row">${esc(r.level)}</th><td colspan="5" class="missing">${esc(r.status)}</td></tr>`).join('')}</tbody></table></div>+        <p class="muted">*Manifest-reported attribute/index bytes. Excludes textures, the board, renderer overhead and driver allocations. This widget loads no 3D models.</p>+        <details><summary>Source hashes and reuse contract</summary><p class="muted">${validSource?'Source SHA-256 is recorded; verify it before reuse.':'Source hash is missing; resolve provenance before generation.'}</p>${sourceLink?`<a href="${esc(sourceLink)}" download>Source component asset</a>`:''}<code>${esc(sourceHash||'No source hash recorded')}</code>${rows.filter(r=>r.available).map(r=>`<code>${esc(r.level)}: ${esc(r.sha256)}</code>`).join('')}<pre>${esc((d.hints||[]).join('\n'))}</pre></details>${src?`<a class="manifest-link" href="${esc(new URL(src,location.href).href)}" download>LOD manifest for AI and viewer reuse</a>`:''}</section>`;+      this.shadowRoot.querySelector('details').addEventListener('toggle',()=>this._height());+    }     _cache(d,src){       if(!Array.isArray(d.assets)||d.assets.length>100)throw new Error('Invalid asset list');       const assets=d.assets.filter(a=>a.kind!=='chip-shaded').map(a=>({...a,url:this._url(a.path,src)}));@@ -53,7 +80,8 @@       if(src){const a=document.createElement('a');a.href=src;a.download='';a.textContent='Cache manifest and source hashes';files.append(a);}       this.shadowRoot.querySelector('details').addEventListener('toggle',()=>this._height());     }-    selfCheck(){const s=structuredClone(this._state||{ok:false,status:'loading'}),img=this.shadowRoot?.querySelector('img');s.previewLoaded=!!(img?.complete&&img.naturalWidth>0);return s;}+    selfCheck(){const s=structuredClone(this._state||{ok:false,status:'loading'}),img=this.shadowRoot?.querySelector('img');if(s.mode!=='lod')s.previewLoaded=!!(img?.complete&&img.naturalWidth>0);return s;}   }   if(!customElements.get('adom-chip-thumbnail'))customElements.define('adom-chip-thumbnail',AdomChipThumbnail);+  if(!customElements.get('adom-component-lod'))customElements.define('adom-component-lod',class extends AdomChipThumbnail {}); })();
widgets/chip-thumbnail-widget.js+29−1
@@ -21,6 +21,7 @@         }         if(token!==this._request)return;         if(d?.schema==='adom/chip-artwork-cache@1')this._cache(d,src);+        else if(d?.schema==='adom/component-lod@1')this._lod(d,src);         else if(d?.schema)throw new Error('Unsupported cache schema: '+d.schema);         else this._legacy(d||{src,mpn:this.getAttribute('mpn'),package:this.getAttribute('package')});       }catch(e){if(token===this._request){this._state={ok:false,error:String(e),hints:['Check the manifest URL, permissions and schema. No regeneration was attempted.']};this.shadowRoot.innerHTML=`<style>${CSS}</style><div class="cache error">${esc(e.message)}</div>`;}}@@ -37,6 +38,32 @@       this.shadowRoot.innerHTML=`<style>${CSS}</style><figure class="cache legacy">${src&&valid?`<img alt="${esc(d.mpn)}" src="${esc(src)}">`:'<div class="muted">No render cached</div>'}<figcaption>${esc(d.mpn)} ${esc(d.package)}</figcaption></figure>`;       const img=this.shadowRoot.querySelector('img');if(img){img.onerror=()=>{img.replaceWith(document.createTextNode('Render unavailable'));this._height();};img.onload=()=>this._height();}this._state={ok:true,mode:'legacy-image'};     }+    _lod(d,src){+      if(!Array.isArray(d.levels)||d.levels.length>4)throw new Error('LOD manifest must list at most four detail levels');+      const order=['high','medium','low','super-low'],seen=new Set();+      for(const l of d.levels){if(!order.includes(l.level)||seen.has(l.level))throw new Error('Unknown or duplicate LOD level');seen.add(l.level);}+      const sourceHash=d.source?.sha256,validSource=/^[a-f0-9]{64}$/i.test(sourceHash||'');+      const rows=order.map(level=>{+        const l=d.levels.find(x=>x.level===level);+        if(!l||!l.path)return {level,available:false,status:l?.review||d.missingReason||'Not generated'};+        const fields=['sizeBytes','decodedBufferBytes','triangles','primitives'];+        if(!/^[a-f0-9]{64}$/i.test(l.sha256||'')||fields.some(k=>!Number.isSafeInteger(l[k])||l[k]<0))throw new Error('Invalid hash or metrics for '+level);+        return {...l,available:true,url:this._url(l.path,src),status:l.review||'Review not recorded'};+      });+      const count=rows.filter(r=>r.available).length,num=n=>Number(n).toLocaleString('en-US'),kib=n=>(n/1024).toFixed(1)+' KiB';+      this._state={ok:true,mode:'lod',mpn:d.mpn,manifest:src,sourceHash:sourceHash||null,sourceHashRecorded:validSource,+        upAxis:d.upAxis||'unverified',units:d.units||'unverified',status:d.status||'unreviewed',levels:rows.map(({url,...r})=>r),+        availableLevels:count,glbRequests:0,hints:['Read this manifest and verify exact source/level hashes before reuse.','This listing does not fetch or decode GLBs. Download size is not GPU memory.','Missing or candidate detail is not visually or physically qualified.',...(d.hints||[])]};+      const sourceLink=d.source?.path?this._url(d.source.path,src):null;+      this.shadowRoot.innerHTML=`<style>${CSS}+        .table-scroll{overflow-x:auto;margin:18px 0}table{border-collapse:collapse;width:100%;text-align:left;font-size:13px;color:#dbe2ec;font-family:system-ui,sans-serif}caption{text-align:left;padding:0 0 10px;color:#a1abb7}th,td{padding:10px 12px;border-bottom:1px solid #30363d;vertical-align:top}th{color:#a1abb7;font-weight:500;white-space:nowrap}th:first-child,td:first-child{padding-left:0}td:nth-child(2),td:nth-child(3),td:nth-child(4),td:nth-child(5){white-space:nowrap;font-variant-numeric:tabular-nums}.lod a{color:#6edfd5}.lod details code{display:block;margin:8px 0}.status{max-width:340px;line-height:1.5}.missing{color:#e8b66f}.lod .manifest-link{display:inline-block;margin-top:8px}+        </style><section class="cache lod"><h3>Component LODs</h3><div class="muted">${esc(d.mpn)} · Cached levels of detail for board and scaffold viewers.</div>+        <div class="badges"><span class="badge">${count}/4 levels available</span><span class="badge">${esc(d.upAxis?d.upAxis+'-up':'Orientation unverified')} · ${esc(d.units||'units unverified')}</span><span class="badge">${esc(d.status||'Review not recorded')}</span></div>+        <div class="table-scroll"><table><caption>Start with super-low; load more detail when inspection needs it.</caption><thead><tr><th scope="col">Detail</th><th scope="col">Download</th><th scope="col">Decoded buffers*</th><th scope="col">Triangles</th><th scope="col">Primitives</th><th scope="col">Review</th></tr></thead><tbody>${rows.map(r=>r.available?`<tr data-level="${r.level}"><th scope="row">${esc(r.level)}</th><td><a href="${esc(r.url)}" download="${esc(r.path.split('/').pop())}">${kib(r.sizeBytes)} GLB</a></td><td>${kib(r.decodedBufferBytes)}</td><td>${num(r.triangles)}</td><td>${num(r.primitives)}</td><td class="status">${esc(r.status)}${r.detailVisibility?`<div class="muted">MPN: ${r.detailVisibility.virtualMpn?'included':'omitted at this scale'}</div>`:''}</td></tr>`:`<tr data-level="${r.level}"><th scope="row">${esc(r.level)}</th><td colspan="5" class="missing">${esc(r.status)}</td></tr>`).join('')}</tbody></table></div>+        <p class="muted">*Manifest-reported attribute/index bytes. Excludes textures, the board, renderer overhead and driver allocations. This widget loads no 3D models.</p>+        <details><summary>Source hashes and reuse contract</summary><p class="muted">${validSource?'Source SHA-256 is recorded; verify it before reuse.':'Source hash is missing; resolve provenance before generation.'}</p>${sourceLink?`<a href="${esc(sourceLink)}" download>Source component asset</a>`:''}<code>${esc(sourceHash||'No source hash recorded')}</code>${rows.filter(r=>r.available).map(r=>`<code>${esc(r.level)}: ${esc(r.sha256)}</code>`).join('')}<pre>${esc((d.hints||[]).join('\n'))}</pre></details>${src?`<a class="manifest-link" href="${esc(new URL(src,location.href).href)}" download>LOD manifest for AI and viewer reuse</a>`:''}</section>`;+      this.shadowRoot.querySelector('details').addEventListener('toggle',()=>this._height());+    }     _cache(d,src){       if(!Array.isArray(d.assets)||d.assets.length>100)throw new Error('Invalid asset list');       const assets=d.assets.filter(a=>a.kind!=='chip-shaded').map(a=>({...a,url:this._url(a.path,src)}));@@ -53,7 +80,8 @@       if(src){const a=document.createElement('a');a.href=src;a.download='';a.textContent='Cache manifest and source hashes';files.append(a);}       this.shadowRoot.querySelector('details').addEventListener('toggle',()=>this._height());     }-    selfCheck(){const s=structuredClone(this._state||{ok:false,status:'loading'}),img=this.shadowRoot?.querySelector('img');s.previewLoaded=!!(img?.complete&&img.naturalWidth>0);return s;}+    selfCheck(){const s=structuredClone(this._state||{ok:false,status:'loading'}),img=this.shadowRoot?.querySelector('img');if(s.mode!=='lod')s.previewLoaded=!!(img?.complete&&img.naturalWidth>0);return s;}   }   if(!customElements.get('adom-chip-thumbnail'))customElements.define('adom-chip-thumbnail',AdomChipThumbnail);+  if(!customElements.get('adom-component-lod'))customElements.define('adom-component-lod',class extends AdomChipThumbnail {}); })();