skill
Adom Wiki Skill Pack
Public Made by Adomby adom
The Adom Wiki Skill Pack : one main skill plus the focused sub-skills that teach the AI to publish to wiki.adom.inc without breaking pages, name the wiki repo archetypes (Page, Skillpack, Family), and publish rich component pages (chips, molecules, boards). The wiki is six tools in one: git, package manager, releases, discussions, pull requests, and discovery. One package manager install drops the whole set onto your container. Includes reviewed Z-up component artwork, surface-selected MPN marki
← Commit history
Publish 0.5.0
5 files changed
+432−43
package.json+3−1@@ -1,6 +1,6 @@ { "slug": "adom-wiki-skillpack",- "version": "0.4.0",+ "version": "0.5.0", "type": "skill", "description": "The Adom Wiki Skill Pack — one main skill plus 14 focused sub-skills that teach the AI to publish to wiki.adom.inc without breaking pages, name the wiki repo archetypes (Page, Skillpack, Family), and publish rich component pages (chips, molecules, boards). The wiki is six tools in one: git, adompkg, releases, discussions, pull requests, and discovery. One adompkg install drops the whole set onto your container.", "discovery_triggers": [@@ -57,6 +57,8 @@ "skills/wiki-visibility/SKILL.md", "skills/wiki-repo-styles/SKILL.md", "skills/wiki-component/SKILL.md",+ "skills/wiki-component/component-page-generator/gen_chip.py",+ "skills/wiki-component/component-page-generator/template.html", "skills/wiki-family/SKILL.md", "skills/wiki-skillpack/SKILL.md", "skills/wiki-hero/SKILL.md",
page.json+84−41@@ -1,46 +1,89 @@ {- "type": "skill", "slug": "adom-wiki-skillpack",- "title": "Adom Wiki Skill Pack",- "brief": "How to use the Adom Wiki without breaking pages — the wiki is six tools in one (git, adompkg, releases, discussions, PRs, discovery), and three repo archetypes have emerged from real use: Page, Skillpack, and Family. Plus how to publish rich component pages (chips, molecules, boards). One install, 15 skills, plus a write-up of the design patterns for the people who build the wiki.",- "hero": {- "type": "image",- "path": "screenshots/hero.png"+ "version": "0.5.0",+ "type": "skill",+ "description": "The Adom Wiki Skill Pack — one main skill plus 14 focused sub-skills that teach the AI to publish to wiki.adom.inc without breaking pages, name the wiki repo archetypes (Page, Skillpack, Family), and publish rich component pages (chips, molecules, boards). The wiki is six tools in one: git, adompkg, releases, discussions, pull requests, and discovery. One adompkg install drops the whole set onto your container.",+ "discovery_triggers": [+ "publish to the wiki",+ "the wiki",+ "adompkg publish",+ "what kind of wiki repo",+ "wiki repo archetype",+ "publish a chip",+ "chip component page",+ "component readme",+ "molecule page",+ "tape and reel",+ "pin 1 orientation",+ "skillpack",+ "super skill",+ "wiki family repo",+ "parent child wiki",+ "how to make a wiki readme",+ "wiki page presentation",+ "hero in readme",+ "hero shows twice",+ "duplicate hero",+ "don't repeat the hero",+ "hero unlinked",+ "hero disappeared",+ "name a child page",+ "family child title",+ "private source public app",+ "proprietary wiki page",+ "wiki breadcrumb",+ "fork a wiki page",+ "wiki discovery",+ "page not surfacing",+ "discover endpoint",+ "wiki scoring",+ "wiki trending"+ ],+ "dependencies": {+ "john/adom-wiki-hero-image": "^0.2.1" },- "visibility": {- "public": true+ "files": [+ "SKILL.md",+ "README.md",+ "page.json",+ "install.sh",+ "uninstall.sh",+ "screenshots/hero.png",+ "screenshots/archetypes.png",+ "skills/wiki-anatomy/SKILL.md",+ "skills/wiki-api/SKILL.md",+ "skills/wiki-publish-safely/SKILL.md",+ "skills/wiki-readme/SKILL.md",+ "skills/wiki-visibility/SKILL.md",+ "skills/wiki-repo-styles/SKILL.md",+ "skills/wiki-component/SKILL.md",+ "skills/wiki-component/component-page-generator/gen_chip.py",+ "skills/wiki-component/component-page-generator/template.html",+ "skills/wiki-family/SKILL.md",+ "skills/wiki-skillpack/SKILL.md",+ "skills/wiki-hero/SKILL.md",+ "skills/wiki-breadcrumbs/SKILL.md",+ "skills/wiki-forking/SKILL.md",+ "skills/wiki-discovery/SKILL.md",+ "skills/wiki-scoring/SKILL.md"+ ],+ "scripts": {+ "install": "./install.sh",+ "uninstall": "./uninstall.sh" },- "metadata": {- "tags": [- "wiki",- "adompkg",- "publishing",- "skillpack",- "guidelines",- "documentation",- "repo-archetypes",- "open-source",- "mit"- ],- "discovery_triggers": [- "publish to the wiki",- "the wiki",- "adompkg publish",- "what kind of wiki repo",- "wiki repo archetype",- "skillpack",- "super skill",- "wiki family repo",- "parent child wiki",- "wiki readme",- "hero disappeared",- "hero unlinked",- "private source public app",- "proprietary wiki page",- "wiki breadcrumb",- "fork a wiki page",- "wiki scoring",- "wiki trending"- ]+ "tags": [+ "wiki",+ "adompkg",+ "publishing",+ "skillpack",+ "guidelines",+ "documentation",+ "repo-archetypes",+ "open-source",+ "mit"+ ],+ "author": {+ "name": "John Lauer",+ "email": "[email protected]" }-}+}
skills/wiki-component/SKILL.md+15−1@@ -96,12 +96,26 @@ images + tables that complement the native viewers — not for a custom app shel **To get the full custom layout** (tabs, accordions, panels, the Open-in toolbar, provenance lightbox) you have two options:-1. **Prototype it as a standalone page in the container** (the dogfic path today):+1. **Prototype it as a standalone page in the container** (the working path today): one self-contained HTML file, **base64-inline the fonts + images**, inline CSS + JS, embed the live `/viewer/...` iframes, then serve it with `python3 -m http.server <port>` and hand the user the proxy URL `https://<slug>.adom.cloud/proxy/<port>/`. This is how you show the design and iterate before the platform can host it.++ > **A reference data-driven generator ships with this skill** at+ > `component-page-generator/gen_chip.py` (+ `template.html`). Point it at a+ > chip-fetcher dir and it emits a compliant standalone page: it reads+ > `info.json` / `stock.json` / `*-symbol.extracted.json` /+ > `*-footprint.extracted.json` / `*-extraction.provenance.json` + the rendered+ > SVGs, **detects the component class** (MCU / MOSFET / op-amp / sensor / power /+ > passive) to pick the right Specs block, builds the **real Provenance** tab+ > (per-pin + per-dimension datasheet page citations, cropping the cited pages+ > with `pdftoppm`/ImageMagick into a click-to-enlarge lightbox), and enforces the+ > Adom-UI rules (tokens, brand fonts, monochrome icons, NO emoji / em-dashes,+ > 44px header, tooltips). Validated across 6 classes; adapt the absolute paths.+ > Use it as the blueprint for either a standalone preview or the future+ > custom-HTML page. 2. **The platform build ask** (below): a custom-HTML page mode so the repo's `readme.html` renders as the page body in a sandboxed iframe (`srcdoc`, `allow-scripts`, assets served with real content-types) instead of sanitized
skills/wiki-component/component-page-generator/gen_chip.pyadded+244@@ -0,0 +1,244 @@+#!/usr/bin/env python3+# Data-driven Adom component-page generator.+# Reads a chip-fetcher chip dir (info/stock/extracted/provenance + SVGs + CAD) and+# renders a self-contained, Adom-UI-compliant standalone HTML page with class-aware+# specs and REAL provenance (per-pin + per-dimension datasheet page citations, with+# cropped page images). No emoji, no em/en-dashes, brand fonts, monochrome icons.+import json, os, re, base64, glob, subprocess, sys++LIBROOT="/home/adom/project/chip-fetcher/library"+FONTS="/home/adom/datasheet-visualizer/public/fonts"+OUT="/tmp/nrf-site" # served dir++def b64f(p): return base64.b64encode(open(p,'rb').read()).decode()+def rd(p):+ try: return open(p,encoding='utf-8').read()+ except: return ""+def jload(p):+ try: return json.load(open(p,encoding='utf-8'))+ except: return None+def scrub(s):+ if s is None: return ""+ s=str(s)+ for a,b in [("—","-"),("–","-"),("−","-"),("…","..."),("·"," ")]:+ s=s.replace(a,b)+ return s++FAM5=b64f(f"{FONTS}/familjen-grotesk-500-normal.woff2")+FAM7=b64f(f"{FONTS}/familjen-grotesk-700-normal.woff2")+SAT4=b64f(f"{FONTS}/satoshi-400-normal.woff2")+SAT5=b64f(f"{FONTS}/satoshi-500-normal.woff2")+SAT7=b64f(f"{FONTS}/satoshi-700-normal.woff2")++P={+ "cube":'<path d="M12 2l9 5v10l-9 5-9-5V7z"/><path d="M12 22V12M21 7l-9 5L3 7"/>',+ "symbol":'<rect x="4" y="4" width="16" height="16" rx="1"/><path d="M4 9h3M4 15h3M17 9h3M17 15h3"/>',+ "footprint":'<rect x="4" y="4" width="16" height="16" rx="1"/><circle cx="8" cy="8" r="1"/><circle cx="16" cy="8" r="1"/><circle cx="8" cy="16" r="1"/><circle cx="16" cy="16" r="1"/>',+ "doc":'<path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><path d="M14 3v6h6M8 13h8M8 17h5"/>',+ "cart":'<circle cx="9" cy="20" r="1"/><circle cx="18" cy="20" r="1"/><path d="M2 3h3l2.4 12.4a1 1 0 0 0 1 .8h8.9a1 1 0 0 0 1-.8L21 7H6"/>',+ "download":'<path d="M12 3v12M7 11l5 5 5-5M5 21h14"/>',+ "list":'<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/>',+ "grid":'<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>',+ "dollar":'<path d="M12 1v22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>',+ "box":'<rect x="3" y="7" width="18" height="13" rx="1"/><path d="M3 11h18M9 7V4h6v3"/>',+ "building":'<path d="M4 21V5a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v16M16 8h3a1 1 0 0 1 1 1v12"/><path d="M8 8h.01M12 8h.01M8 12h.01M12 12h.01M8 16h.01M12 16h.01"/>',+ "layers":'<path d="M12 2l9 5-9 5-9-5z"/><path d="M3 12l9 5 9-5M3 17l9 5 9-5"/>',+ "link":'<path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/>',+ "open":'<path d="M15 3h6v6M10 14 21 3M21 14v7H3V3h7"/>',+ "warn":'<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h16.9a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/><path d="M12 9v4M12 17h.01"/>',+ "shield":'<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/>',+ "chip":'<rect x="6" y="6" width="12" height="12" rx="1"/><path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4"/>',+}+def ic(n,s=18):+ return f'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" width="{s}" height="{s}" style="vertical-align:middle;flex:none">{P[n]}</svg>'++def detect_class(info, sym):+ t=((info.get('family') or '')+' '+(info.get('description') or '')+' '+(sym.get('category') or '')).lower()+ if 'mosfet' in t or ('fet' in t and 'channel' in t): return 'mosfet'+ if 'op-amp' in t or 'op amp' in t or 'opamp' in t or 'amplifier' in t: return 'opamp'+ if 'resistor' in t or 'capacitor' in t or 'inductor' in t: return 'passive'+ if 'sensor' in t or 'imu' in t or 'gyro' in t or 'accel' in t: return 'sensor'+ if any(k in t for k in ('pd controller','type-c','charger','buck','boost','dc-dc','dc/dc','ldo','regulator','power switch','gate driver','pmic')): return 'power'+ if 'adc' in t or 'converter' in t: return 'converter'+ if 'mcu' in t or 'soc' in t or 'cortex' in t or 'microcontroller' in t or 'radio' in t: return 'mcu'+ return 'generic'++def find(d, *pats):+ s=(d.get('description') or '')+' '+(d.get('family') or '')+ for p in pats:+ m=re.search(p, s, re.I)+ if m: return scrub(m.group(0))+ return None++def spec_rows(cls, info, sym):+ d=info; desc=scrub(d.get('description') or '')+ rows=[]+ if cls=='mcu':+ rows=[("Class","Wireless MCU / SoC"),("Core",find(d,r'cortex[- ]m\w+[^,.;]*') or "see datasheet"),+ ("Memory",find(d,r'\d+\.?\d*\s?MB\s?(NVM|flash)[^,.;]*') or find(d,r'\d+\s?KB[^,.;]*') or "see datasheet"),+ ("Radio",find(d,r'2\.4\s?GHz[^,.;]*') or find(d,r'(BLE|Bluetooth)[^,.;]*') or "2.4 GHz"),+ ("Package",scrub(d.get('package'))),("Pins",str(len(sym.get('pins',[])))+" extracted")]+ elif cls=='mosfet':+ rows=[("Class","MOSFET"),("Channel",find(d,r'[PN]-channel') or "see datasheet"),+ ("Vds",find(d,r'-?\d+\.?\d*\s?V\s?Vds') or find(d,r'Vds[^,.;]*') or "see datasheet"),+ ("Id (cont.)",find(d,r'-?\d+\.?\d*\s?A\s?(continuous)?[^,.;]*') or "see datasheet"),+ ("Rds(on)",find(d,r'Rds\(on\)[^,.;]*') or "see datasheet"),+ ("Vgs(th)",find(d,r'(low-?vgs\(th\)|Vgs\(th\)[^,.;]*)') or "see datasheet"),+ ("Package",scrub(d.get('package')))]+ elif cls=='opamp':+ rows=[("Class","Operational amplifier"),("Channels",find(d,r'(dual|single|quad)') or "see datasheet"),+ ("GBW",find(d,r'[\d.]+\s?MHz\s?GBW') or find(d,r'GBW[^,.;]*') or "see datasheet"),+ ("Slew rate",find(d,r'[\d.]+\s?V/[uµ]s') or "see datasheet"),+ ("Supply",find(d,r'(single|split)?\s?[±]?[\d.]+\s?V[^,.;]*') or "see datasheet"),+ ("Package",scrub(d.get('package')))]+ elif cls=='sensor':+ rows=[("Class","Sensor"),("Type",scrub(d.get('family') or sym.get('category') or "see datasheet")),+ ("Interface",find(d,r'(I.?2.?C|SPI|UART|analog)[^,.;]*') or "see datasheet"),+ ("Supply",find(d,r'[\d.]+\s?V[^,.;]*') or "see datasheet"),+ ("Package",scrub(d.get('package'))),("Pins",str(len(sym.get('pins',[])))+" extracted")]+ elif cls=='passive':+ rows=[("Class","Passive"),("Value",find(d,r'[\d.]+\s?(m?[ΩROhm]|[munpf]F|[munp]?H)[^,.;]*') or find(d,r'[\d.]+\s?m[ΩO][^,.;]*') or "see datasheet"),+ ("Tolerance",find(d,r'[±]?[\d.]+\s?%') or "see datasheet"),+ ("Rating",find(d,r'[\d.]+\s?W[^,.;]*') or find(d,r'[\d.]+\s?V[^,.;]*') or "see datasheet"),+ ("Package",scrub(d.get('package'))) ]+ elif cls=='power':+ rows=[("Class","Power / controller"),("Function",scrub(d.get('family') or sym.get('category') or "see datasheet")),+ ("Vin",find(d,r'[\d.]+\s?V?\s?to\s?[\d.]+\s?V[^,.;]*') or find(d,r'[\d.]+\s?V\s?(input|Vin)[^,.;]*') or "see datasheet"),+ ("Iout",find(d,r'[\d.]+\s?A[^,.;]*') or "see datasheet"),+ ("Package",scrub(d.get('package'))),("Pins",str(len(sym.get('pins',[])))+" extracted")]+ else:+ rows=[("Class",scrub(sym.get('category') or info.get('family') or "Component")),+ ("Package",scrub(d.get('package'))),("Pins",str(len(sym.get('pins',[])))+" extracted")]+ return rows++def crop_pages(pdf, pages, mpn):+ """Crop cited datasheet pages to base64 PNGs. Returns {page:b64}. Cap at 5."""+ out={}+ if not pdf or not os.path.exists(pdf): return out+ for pg in sorted(set(pages))[:5]:+ png=f"/tmp/gen/{mpn}-p{pg}.png"+ try:+ subprocess.run(["pdftoppm","-png","-r","120","-f",str(pg),"-l",str(pg),pdf,f"/tmp/gen/{mpn}-pp{pg}"],+ check=True, capture_output=True, timeout=60)+ src=glob.glob(f"/tmp/gen/{mpn}-pp{pg}*.png")+ if not src: continue+ subprocess.run(["convert",src[0],"-resize","850x",png],check=True,capture_output=True,timeout=60)+ out[pg]=b64f(png)+ except Exception as e:+ pass+ return out++def gen(mpn, slug):+ D=f"{LIBROOT}/{mpn}"+ info=jload(f"{D}/info.json") or {}+ stock=jload(f"{D}/stock.json")+ sym=jload(glob.glob(f"{D}/*-symbol.extracted.json")[0]) if glob.glob(f"{D}/*-symbol.extracted.json") else {}+ prov=jload(glob.glob(f"{D}/*provenance*.json")[0]) if glob.glob(f"{D}/*provenance*.json") else {}+ sym_svg=scrub(re.sub(r'<\?xml[^>]*\?>','', rd(glob.glob(f"{D}/{mpn}-symbol.svg")[0]) if glob.glob(f"{D}/{mpn}-symbol.svg") else ""))+ fp_svg=scrub(re.sub(r'<\?xml[^>]*\?>','', rd(glob.glob(f"{D}/{mpn}-footprint.svg")[0]) if glob.glob(f"{D}/{mpn}-footprint.svg") else ""))+ pdf=(glob.glob(f"{D}/*.pdf") or [None])+ pdf=sorted(pdf, key=lambda p:-(os.path.getsize(p) if p and os.path.exists(p) else 0))[0] if pdf and pdf[0] else None+ cls=detect_class(info, sym)+ pins=sym.get('pins',[])+ pin_prov={str(c.get('pin_number')):c for c in (prov.get('symbol',{}).get('pin_provenance') or [])}+ dim_prov=prov.get('footprint',{}).get('dimension_provenance') or []+ review=prov.get('review_required') or []+ cited=set()+ for c in pin_prov.values():+ if c.get('page'): cited.add(int(c['page']))+ for dp in dim_prov:+ if dp.get('page'): cited.add(int(dp['page']))+ crops=crop_pages(pdf, cited, mpn)+ tooling=prov.get('tooling',{})+ ds=prov.get('datasheet',{})++ # ---- build pin rows grouped ----+ groups={}+ for p in pins:+ groups.setdefault(p.get('group','OTHER'),[]).append(p)+ pin_html=""+ for g,ps in groups.items():+ pin_html+=f'<tr><td colspan="4" style="color:var(--purple);font-weight:600;padding-top:12px">{scrub(g)} ({len(ps)})</td></tr>'+ for p in ps:+ num=str(p.get('number','')); c=pin_prov.get(num,{})+ pg=c.get('page'); cap=scrub(c.get('table_caption') or '')+ cite=f'<span class="evlink" onclick="LB(EV[{pg}],\'{scrub(cap)} (p.{pg})\')">p.{pg}</span>' if pg and pg in crops else (f'p.{pg}' if pg else '')+ pin_html+=f'<tr><td class="k">{scrub(p.get("name"))}</td><td class="k">{num}</td><td>{scrub(p.get("description"))[:200]}</td><td class="ev">{cite}</td></tr>'++ # ---- dimension provenance rows ----+ dim_html=""+ for dp in dim_prov:+ pg=dp.get('page')+ cite=f'<span class="evlink" onclick="LB(EV[{pg}],\'{scrub(dp.get("figure_label"))} (p.{pg})\')">p.{pg}</span>' if pg and pg in crops else (f'p.{pg}' if pg else '')+ dim_html+=f'<tr><td class="k">{scrub(dp.get("field"))}</td><td class="k">{scrub(dp.get("value"))}</td><td>{scrub(dp.get("figure_label"))}</td><td class="ev">{cite}</td></tr>'++ # ---- review flags ----+ rev_html=""+ for r in review:+ rev_html+=f'<li>{scrub(r.get("reason"))}: {scrub(", ".join(r.get("pins",[])) if r.get("pins") else r.get("package",""))[:160]}</li>'++ # ---- spec rows ----+ specs=spec_rows(cls, info, sym)+ spec_html="".join(f'<tr><th>{scrub(k)}</th><td>{scrub(v)}</td></tr>' for k,v in specs)++ # ---- sourcing ----+ src_html=""+ if stock and stock.get('mouser'):+ m=stock['mouser']; tiers=m.get('price_tiers',[])+ qty="".join(f'<th>{t["qty"]}</th>' for t in tiers); pr="".join(f'<td>{scrub(t["price"])}</td>' for t in tiers)+ src_html=f'''<div class="ctxrow"><span class="ctx">Mouser {scrub(m.get("mouser_pn"))}, <b>{m.get("stock")} in stock</b>, lifecycle {scrub(m.get("lifecycle"))}, {scrub(m.get("rohs"))}</span><span class="src real">real, {scrub(stock.get("checked_at","")[:10])}</span></div>+ <table><tr><th>Qty</th>{qty}</tr><tr><td>Unit</td>{pr}</tr></table>+ <p style="margin-top:8px"><a href="{m.get("product_url")}" target="_blank">Buy on Mouser</a></p>'''+ else:+ src_html='<p class="src mock">No stock.json for this part. TODO: fetch live price and stock from Mouser / DigiKey.</p>'++ # ---- provenance evidence images ----+ ev_js="var EV={"+",".join(f'{pg}:"data:image/png;base64,{b}"' for pg,b in crops.items())+"};"+ prov_imgs="".join(f'<div style="margin-top:12px"><div class="viewer-cap"><span>Datasheet p.{pg}</span></div><img class="provimg" src="data:image/png;base64,{b}" onclick="LB(EV[{pg}],\'Datasheet p.{pg}\')"></div>' for pg,b in list(crops.items())[:3])++ cls_label={'mcu':'Wireless MCU / SoC','mosfet':'MOSFET','opamp':'Op-amp','converter':'Data converter','passive':'Passive','sensor':'Sensor','power':'Power / controller','generic':'Component'}[cls]+ desc=scrub(info.get('description') or '')+ pkg=scrub(info.get('package') or '')+ mfr=scrub(info.get('manufacturer') or '')+ has_glb=bool(glob.glob(f"{D}/{mpn}.glb"))+ cad=[("KiCad symbol",f"{mpn}.kicad_sym"),("KiCad footprint",f"{mpn}.kicad_mod"),("STEP",f"{mpn}.step"),("Fusion .lbr",f"{mpn}.lbr")]+ if has_glb: cad.append(("GLB (web 3D)",f"{mpn}.glb"))+ cad_html="".join(f'<div class="dlcard"><div class="n">{f}</div><div class="d">{scrub(lbl)}</div></div>' for lbl,f in cad if glob.glob(f"{D}/{f}"))++ HTML=TEMPLATE+ reps={+ "__MPN__":mpn,"__MFR__":mfr,"__PKG__":pkg,"__DESC__":desc,"__CLS__":cls_label,+ "__PINCOUNT__":str(len(pins)),"__SYM__":sym_svg,"__FP__":fp_svg,+ "__SPECS__":spec_html,"__PINS__":pin_html,"__DIMS__":dim_html or '<tr><td colspan="4" class="src mock">No dimension provenance.</td></tr>',+ "__REVIEW__":rev_html or "<li>None flagged.</li>","__SRC__":src_html,"__CAD__":cad_html,+ "__DSPAGES__":str(ds.get('page_count','?')),"__DSSHA__":scrub((ds.get('sha256') or '')[:16]),+ "__TOOL__":scrub(tooling.get('extractor','ds2sf'))+" "+scrub(tooling.get('extractor_version',''))+", "+scrub(tooling.get('claude_model','')),+ "__PROVIMGS__":prov_imgs,"__EVJS__":ev_js,+ "__GLBNOTE__":"GLB present, live Babylon viewer when published." if has_glb else "STEP present, GLB on publish.",+ "__FAM5__":FAM5,"__FAM7__":FAM7,"__SAT4__":SAT4,"__SAT5__":SAT5,"__SAT7__":SAT7,+ "__IC_CHIP__":ic("chip"),"__IC_OPEN__":ic("open",16),"__IC_SYM__":ic("symbol"),"__IC_FP__":ic("footprint"),+ "__IC_CUBE__":ic("cube"),"__IC_GRID__":ic("grid"),"__IC_LIST__":ic("list"),"__IC_DOLLAR__":ic("dollar"),+ "__IC_BOX__":ic("box"),"__IC_BUILD__":ic("building"),"__IC_LAYERS__":ic("layers"),"__IC_LINK__":ic("link"),+ "__IC_SHIELD__":ic("shield"),"__IC_DL__":ic("download",16),"__IC_DOC__":ic("doc",14),"__IC_WARN__":ic("warn",16),"__IC_CART__":ic("cart",16),+ }+ for k,v in reps.items(): HTML=HTML.replace(k,v)+ bad=[c for c in HTML if c in "—–…−" or ord(c)>0x1F000]+ print(f"{mpn}: forbidden chars:", sorted(set(bad)) if bad else "NONE", "| cited pages cropped:", sorted(crops.keys()), "| class:", cls, "| pins:", len(pins))+ os.makedirs(f"{OUT}/{slug}",exist_ok=True)+ open(f"{OUT}/{slug}/index.html","w",encoding="utf-8").write(HTML)+ # copy CAD downloads into served dir+ for lbl,f in cad:+ srcf=glob.glob(f"{D}/{f}")+ if srcf:+ import shutil; shutil.copy(srcf[0], f"{OUT}/{slug}/{f}")+ print(f" wrote {OUT}/{slug}/index.html ({len(HTML)} bytes)")++TEMPLATE=open('/tmp/gen/template.html',encoding='utf-8').read()++if __name__=="__main__":+ gen("nRF54L15","nrf54l15")+ gen("DMG2305UX-7","dmg2305")+ gen("LM358D","lm358d")+ gen("BME688","bme688")+ gen("TPS25751SRSMR","tps25751")+ gen("WSL2010R0500FEA","wsl2010")
skills/wiki-component/component-page-generator/template.htmladded+86@@ -0,0 +1,86 @@+<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">+<title>__MPN__, Adom component</title>+<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">+<style>+@font-face{font-family:'Familjen Grotesk';font-weight:500;font-display:swap;src:url(data:font/woff2;base64,__FAM5__) format('woff2')}+@font-face{font-family:'Familjen Grotesk';font-weight:700;font-display:swap;src:url(data:font/woff2;base64,__FAM7__) format('woff2')}+@font-face{font-family:'Satoshi';font-weight:400;font-display:swap;src:url(data:font/woff2;base64,__SAT4__) format('woff2')}+@font-face{font-family:'Satoshi';font-weight:500;font-display:swap;src:url(data:font/woff2;base64,__SAT5__) format('woff2')}+@font-face{font-family:'Satoshi';font-weight:700;font-display:swap;src:url(data:font/woff2;base64,__SAT7__) format('woff2')}+:root{--bg:#0d1117;--surface:#161b22;--elevated:#1c2128;--overlay:#21262d;--border:#30363d;--text:#e6edf3;--text-2:#8b949e;--text-3:#484f58;--accent:#00b8b0;--accent-bright:#00e6dc;--purple:#8c6bf7;--blue:#58a6ff;--green:#3fb950;--red:#f85149;--yellow:#d29922}+*{box-sizing:border-box;margin:0;padding:0}+body{font-family:'Satoshi',sans-serif;color:var(--text);background:var(--bg);line-height:1.5;padding-bottom:80px}+h1,h2,h3{font-family:'Familjen Grotesk',sans-serif}.mono{font-family:'JetBrains Mono',monospace}+a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}+.hdr{height:44px;display:flex;align-items:center;gap:10px;padding:0 16px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:20}+.hdr .brand{display:flex;align-items:center;gap:8px;color:var(--accent);font-family:'Familjen Grotesk';font-weight:600;font-size:14px}+.hdr .brand .sub{color:var(--text-2);font-weight:500}.hdr .vsep{width:1px;height:22px;background:var(--border)}+.hdr .nm{font-family:'Familjen Grotesk';font-weight:600;font-size:14px}.hdr .meta{font-family:'JetBrains Mono';font-size:11.5px;color:var(--text-2)}+.hdr .actions{margin-left:auto;display:flex;gap:6px}.hdr .actions a{display:flex;color:var(--text-2);padding:4px;border-radius:8px}.hdr .actions a:hover{color:var(--text);background:var(--elevated)}+@media(max-width:720px){.hdr .meta{display:none}}+.wrap{max-width:1080px;margin:0 auto;padding:0 18px}+.intro{padding:16px 0 4px}h1{font-size:36px;letter-spacing:-1.1px;margin:6px 0 2px}+.lead{color:var(--text-2);font-size:16px;max-width:800px}+.badge{display:inline-block;padding:3px 11px;border-radius:999px;font-size:12px;font-weight:600}+.b-type{border:1px solid var(--border);color:var(--purple);background:rgba(140,107,247,.08)}.b-cls{border:1px solid rgba(0,184,176,.5);color:#7fe3dd;background:rgba(0,184,176,.10)}+.legend{display:flex;gap:14px;font-size:12px;color:var(--text-2);margin:10px 0 0;flex-wrap:wrap}.dot{display:inline-block;width:9px;height:9px;border-radius:999px;margin-right:5px;vertical-align:middle}+.src{font-size:11px;color:var(--text-3)}.src.real{color:#6fbf8f}.src.mock{color:#e0a06a}+.todo{display:inline-flex;align-items:center;gap:4px;background:rgba(210,153,34,.15);border:1px solid rgba(210,153,34,.5);color:#e3b765;border-radius:8px;padding:1px 8px;font-size:11px;font-weight:600;margin-left:6px}+.tabs{position:sticky;top:44px;z-index:15;display:flex;gap:4px;flex-wrap:wrap;background:rgba(13,17,23,.95);backdrop-filter:blur(7px);border-bottom:1px solid var(--border);padding:9px 0;margin-top:10px}+.tab{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--text-2);padding:6px 11px;border-radius:8px;cursor:pointer;border:1px solid transparent;user-select:none}.tab:hover{color:var(--text)}.tab.on{color:var(--accent);border-color:var(--border);background:rgba(0,184,176,.08)}+.tabpanel{display:none;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:14px 0}.tabpanel.on{display:block}+.tabpanel h2{font-size:21px;margin-bottom:10px;display:flex;align-items:center;gap:9px}.tabpanel h2 svg{color:var(--accent)}+.render{width:100%;border-radius:8px;border:1px solid var(--border);background:#fff;overflow:hidden}.render svg{display:block;width:100%;height:auto}+.viewer-cap{display:flex;gap:7px;font-size:12px;color:var(--text-2);margin:6px 0}+table{width:100%;border-collapse:collapse;font-size:13.5px;margin:6px 0}th,td{border-top:1px solid var(--border);padding:7px 9px;text-align:left;vertical-align:top}th{color:var(--text-2);font-weight:600}.k{font-family:'JetBrains Mono';color:#7fe3dd;font-size:12.5px}.ev{white-space:nowrap}+.evlink{color:var(--accent);cursor:pointer;font-family:'JetBrains Mono';font-size:12px}+.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px}.dlcard{border:1px solid var(--border);border-radius:8px;padding:11px;background:var(--elevated)}.dlcard .n{font-family:'JetBrains Mono';font-size:12px}.dlcard .d{font-size:11.5px;color:var(--text-3);margin-top:3px}+.todonote{display:flex;gap:9px;background:rgba(210,153,34,.07);border:1px solid rgba(210,153,34,.35);border-radius:8px;color:#e3c089;padding:10px 13px;font-size:13px;margin:10px 0}.todonote svg{color:var(--yellow);flex:none}+.ctxrow{display:flex;gap:9px;flex-wrap:wrap;margin-bottom:8px}.ctx{font-size:12.5px;color:var(--text-2);border:1px solid var(--border);border-radius:8px;padding:5px 10px;background:var(--elevated)}.ctx b{color:var(--accent)}+.provimg{width:100%;border:1px solid var(--border);border-radius:8px;cursor:zoom-in;background:#fff}+.cite{font-size:11.5px;color:var(--text-2);border-top:1px dashed var(--border);margin-top:10px;padding-top:8px}.cite b{color:var(--text)}+#lb{position:fixed;inset:0;background:rgba(2,6,10,.94);z-index:200;display:none;align-items:center;justify-content:center;flex-direction:column;padding:18px}#lb.on{display:flex}#lb img{max-width:100%;max-height:86vh;border-radius:8px;border:1px solid var(--border)}#lb .cap{color:var(--text-2);font-size:13px;margin-top:10px}#lb .x{position:absolute;top:14px;right:18px;color:#fff;font-size:26px;cursor:pointer}+#tip{position:fixed;z-index:99999;background:var(--overlay);border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:6px 9px;border-radius:8px;max-width:240px;pointer-events:none;opacity:0;transition:.18s}#tip.on{opacity:1}+.btn{display:inline-flex;align-items:center;gap:7px;padding:8px 12px;border-radius:8px;font-size:13.5px;font-weight:600;cursor:pointer;border:1px solid var(--border);background:var(--elevated);color:var(--text)}.btn:hover{border-color:var(--accent);color:var(--accent-bright)}.btn.dl{}+.foot{color:var(--text-3);font-size:12.5px;text-align:center;margin-top:26px}+</style></head><body>+<div class="hdr"><div class="brand">__IC_CHIP__ Adom Wiki <span class="sub">. Component</span></div><div class="vsep"></div>+<span class="nm">__MPN__</span><span class="meta">__PKG__</span>+<div class="actions"><a href="#" data-tip="Open the live wiki page (when published)">__IC_OPEN__</a></div></div>+<div class="wrap">+<div class="intro"><div style="display:flex;gap:9px;align-items:center;flex-wrap:wrap"><span class="badge b-type">COMPONENT</span><span class="badge b-cls">__CLS__</span><span class="src" style="margin-left:auto">data-driven from chip-fetcher, standalone preview</span></div>+<h1>__MPN__</h1><p class="lead"><b>__MFR__.</b> __DESC__</p>+<div class="legend"><span><span class="dot" style="background:#6fbf8f"></span>real data (chip-fetcher / ds2sf)</span><span><span class="dot" style="background:#e0a06a"></span>mock / not fetched</span><span class="todo">__IC_WARN__ TODO</span></div></div>++<div class="tabs" id="tabs">+<span class="tab on" data-t="symbol" onclick="T('symbol')">__IC_SYM__ Symbol</span><span class="tab" data-t="fp" onclick="T('fp')">__IC_FP__ Footprint</span><span class="tab" data-t="d3" onclick="T('d3')">__IC_CUBE__ 3D</span><span class="tab" data-t="pins" onclick="T('pins')">__IC_GRID__ Pins</span><span class="tab" data-t="specs" onclick="T('specs')">__IC_LIST__ Specs</span><span class="tab" data-t="src" onclick="T('src')">__IC_DOLLAR__ Sourcing</span><span class="tab" data-t="prov" onclick="T('prov')">__IC_SHIELD__ Provenance</span><span class="tab" data-t="dl" onclick="T('dl')">__IC_DL__ Downloads</span>+</div>++<div class="tabpanel on" id="symbol"><h2>__IC_SYM__ Schematic Symbol</h2><div class="viewer-cap"><span class="src real">adom-symbol render (chip-thumbnailer)</span></div><div class="render">__SYM__</div></div>+<div class="tabpanel" id="fp"><h2>__IC_FP__ Footprint</h2><div class="viewer-cap"><span class="src real">adom-footprint render</span><span>pin 1 = square pad, top left</span></div><div class="render">__FP__</div></div>+<div class="tabpanel" id="d3"><h2>__IC_CUBE__ 3D Model</h2><p style="color:var(--text-2);font-size:14px">__GLBNOTE__ The live wiki page renders an interactive Babylon viewer from the GLB.</p></div>+<div class="tabpanel" id="pins"><h2>__IC_GRID__ Pin map <span class="src real" style="font-size:12px;font-weight:400">__PINCOUNT__ pins, ds2sf extracted</span></h2><table><tr><th>Name</th><th>No.</th><th>Description</th><th class="ev">Cite</th></tr>__PINS__</table></div>+<div class="tabpanel" id="specs"><h2>__IC_LIST__ Specifications <span class="src real" style="font-size:12px;font-weight:400">class-aware</span></h2><table>__SPECS__</table><div class="cite"><b>Source:</b> info.json + datasheet. Parsed fields are best-effort from the description; full parametric pull is a TODO.</div></div>+<div class="tabpanel" id="src"><h2>__IC_DOLLAR__ Sourcing and availability</h2>__SRC__</div>+<div class="tabpanel" id="prov"><h2>__IC_SHIELD__ Provenance</h2><div class="src" style="margin-bottom:10px">Every datapoint traces to a source. Click any citation or page to enlarge the datasheet page.</div>+<table><tr><th>Datasheet</th><td>__DSPAGES__ pages, sha256 <span class="mono">__DSSHA__</span></td></tr><tr><th>Extracted by</th><td class="mono">__TOOL__</td></tr></table>+<h3 style="font-size:15px;margin:14px 0 4px">Dimension provenance (footprint)</h3><table><tr><th>Field</th><th>Value</th><th>Figure</th><th class="ev">Cite</th></tr>__DIMS__</table>+<h3 style="font-size:15px;margin:14px 0 4px">Review flags (ds2sf honest uncertainty)</h3><ul style="color:var(--text-2);font-size:13px;padding-left:18px">__REVIEW__</ul>+<div class="todonote">__IC_WARN__<div>Per-pin citations are in the Pins tab (click a page number). ds2sf refuses any datasheet without a pinout, so a refusal means we need a fuller PDF, not a fabricated answer.</div></div>+__PROVIMGS__</div>+<div class="tabpanel" id="dl"><h2>__IC_DL__ CAD downloads <span class="src real" style="font-size:12px;font-weight:400">real files</span></h2><div class="cards">__CAD__</div></div>++<div class="foot">Adom Wiki, component page, data-driven from chip-fetcher. Real data + ds2sf provenance; class-aware specs. Tabs, tooltips, and citation lightbox are live.</div>+</div>+<div id="lb"><span class="x" onclick="document.getElementById('lb').classList.remove('on')">✕</span><img alt="source page"><div class="cap"></div></div>+<div id="tip"></div>+<script>+__EVJS__+function T(id){document.querySelectorAll('.tabpanel').forEach(p=>p.classList.toggle('on',p.id===id));document.querySelectorAll('.tab').forEach(t=>t.classList.toggle('on',t.dataset.t===id));var y=document.getElementById('tabs').getBoundingClientRect().top+window.scrollY-50;window.scrollTo({top:y,behavior:'smooth'})}+function LB(src,cap){var l=document.getElementById('lb');l.querySelector('img').src=src;l.querySelector('.cap').textContent=cap||'';l.classList.add('on')}+document.getElementById('lb').addEventListener('click',function(e){if(e.target.id==='lb')this.classList.remove('on')});+var tip=document.getElementById('tip'),tmr;+document.addEventListener('mouseover',function(e){var el=e.target.closest('[data-tip]');if(!el)return;clearTimeout(tmr);tmr=setTimeout(function(){tip.textContent=el.getAttribute('data-tip');var r=el.getBoundingClientRect();tip.style.left=Math.min(r.left,innerWidth-250)+'px';tip.style.top=(r.bottom+8)+'px';tip.classList.add('on')},600)});+document.addEventListener('mouseout',function(e){if(e.target.closest('[data-tip]')){clearTimeout(tmr);tip.classList.remove('on')}});+</script>+</body></html>