app
Component Widgets
Public Made by Adomby adom
Reusable component widgets for pinouts, thermal vias, molecule relationships, reviewed chip outlines and lightweight four-level LOD cache listings.
← Commit history
Publish 1.1.8
7 files changed
+153−9
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" ]-}+}
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 {}); })();