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
add wiki-readme-3d: interactive 3D viewers inside readme.html despite the sandbox CSP (Adom Babylon bundle extension, File loads, repo-PNG image channel, texture shim, render loop/lights/camera) - proven on adom/bq25792-charger
3 files changed
+152−1
SKILL.md+2@@ -92,6 +92,7 @@ important enough to state up front: | **wiki-publish-safely** | The operational pitfalls as hard rules; the verify-in-pup gate. | | **wiki-readme** | How to make a great README — `README.md` vs `readme.html`, sections, embeds, images. | | **wiki-readme-html** | Build a custom `readme.html` — sandbox limits, shadowing, self-containment, theming, verify loop. |+| **wiki-readme-3d** | Put a WORKING interactive 3D viewer in a readme.html despite the sandbox CSP — extend the Adom Babylon bundle, File-object GLB loads, the repo-PNG image channel for full-quality models, texture shim, render loop/lights/camera. | | **wiki-visibility** | Public vs private source, public releases, the private readme, open vs closed pages. | | **wiki-repo-styles** | The archetype taxonomy (Page / Skillpack / Family) and how to choose. | | **wiki-component** | Publish a component page — chip / molecule / board; the tabbed chip CAD-hub README. |@@ -110,6 +111,7 @@ important enough to state up front: - "Make a hero for this page" / "the hero disappeared" → wiki-hero - "Write a README for this page" → wiki-readme - "I want a custom HTML readme / tabs / designed layout" → wiki-readme-html+- "I want interactive 3D / a model viewer / a GLB toggle in the readme" → wiki-readme-3d - "Keep the source private but let people install it" → wiki-visibility - "Bundle these skills into one package" → wiki-skillpack - "Add a child plugin to adom-usb" → wiki-family
package.json+2−1@@ -1,7 +1,7 @@ { "slug": "adom-wiki-skillpack", "title": "Adom Wiki Skill Pack",- "version": "0.7.4",+ "version": "0.7.5", "type": "skill", "hero": { "path": "screenshots/hero.png"@@ -59,6 +59,7 @@ "skills/wiki-publish-safely/SKILL.md", "skills/wiki-readme/SKILL.md", "skills/wiki-readme-html/SKILL.md",+ "skills/wiki-readme-3d/SKILL.md", "skills/wiki-visibility/SKILL.md", "skills/wiki-repo-styles/SKILL.md", "skills/wiki-component/SKILL.md",
skills/wiki-readme-3d/SKILL.mdadded+148@@ -0,0 +1,148 @@+---+name: wiki-readme-3d+description: >+ Put a WORKING interactive 3D viewer (or any data-hungry JS app) inside an Adom Wiki readme.html,+ despite the readme sandbox CSP that blocks ALL fetch (connect-src none), all wasm, and blob:+ images. Covers the complete proven recipe: extend the wiki's own Adom Babylon viewer bundle (never+ roll your own), load GLBs with zero network via File objects, smuggle full-quality models through+ the img-src channel (GLB bytes packed into lossless repo PNGs read back via CORS canvas), the+ blob-to-data-URI texture shim, and the five things the bundle only does inside its own loadModel+ (render loop, lights, camera framing, clip planes, scene clearing). Every rule here failed live+ first on adom/bq25792-charger (2026-07-15). Trigger words: 3d in readme, readme viewer, readme.html+ 3d, babylon in readme, model toggle wiki page, readme csp, connect-src none, readme too large,+ interactive readme, glb in readme, exploded view toggle, custom wiki 3d viewer.+---++# Interactive 3D inside a wiki readme.html (the proven recipe)++Built and verified live on `adom/bq25792-charger` (exploded/assembled stackup toggle). The readme+runs in `<iframe sandbox="allow-scripts allow-downloads">` with CSP:+`default-src 'none'; script-src 'unsafe-inline' https:; style-src 'unsafe-inline' https:;+img-src data: https:; connect-src 'none'; ...` - so: scripts and styles load from https, images+load from https/data, and **nothing else moves: no fetch/XHR (not even data: URIs), no wasm+('wasm-unsafe-eval' absent), no blob: images, no workers.** Plus the `/readme` endpoint refuses+files over **~4-5 MB** ("README too large to render"). Everything below exists to win inside that+box.++## Rule 0: extend the wiki's own viewer - never roll your own++The bundle the wiki itself uses is at+`/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js` (+ `style.css`), loadable+from the readme (script-src https:). It sets `window.Adom3DViewerBabylon9` and `window.BABYLON`.+Wiring, copied from the wiki's own `/viewer/3d/...` page:++```js+const viewer = Adom3DViewerBabylon9.init(el, { zUp:true, showViewCube:true, showGround:true });+// public API: loadModel(url), clearScene(), frameModel(), getScene(), getEngine(), getCamera(), toggleAxes()+```++`viewer.loadModel(url)` fetches -> dead under connect-src 'none'. So append into ITS scene yourself:++```js+const f = new File([bytes], "model.glb", { type: "model/gltf-binary" });+await BABYLON.SceneLoader.AppendAsync("", f, viewer.getScene(), null, ".glb");+```++A **File** rides FileReader (not fetch) - CSP-exempt. The explicit `".glb"` plugin hint is required+(data:/blob: URL strings all fail; the wrapper also chokes on non-strings - `lastIndexOf is not a+function`).++## Getting the model BYTES with no network: the PNG image channel++Embedding base64 in the HTML works but hits the ~4-5 MB readme cap fast. The loophole that removes+the cap AND keeps full quality: **img-src allows https:, `/blob/` serves PNG inline with+`access-control-allow-origin: *`, and a CORS-clean canvas is readable.** So pack the GLB bytes into+a lossless PNG in the page repo and decode in the readme:++- Pack (server side): 4-byte LE length header + raw GLB bytes into **RGB channels only** (alpha+ stays 255 - canvas 2D premultiplies alpha, so bytes stored under alpha<255 corrupt). Width 2048,+ height as needed, PIL `Image.frombytes('RGB', ...)`, save PNG (PNG is lossless).+- Decode (readme): `new Image()` with `crossOrigin="anonymous"`, draw to canvas, `getImageData`,+ take bytes 0-2 of every pixel, read the length header, slice. Then the File/AppendAsync load above.++Result on the BQ page: readme.html fell from 1.89 MB (embedded, quality-crushed) to **13 KB**, with+**zero-simplification** models served from the repo.++## Models must be QUANTIZED, never draco (and never gpu-instanced)++- **Draco cannot decode in the sandbox** (wasm blocked, workers blocked, eval blocked). Use+ `gltf-transform optimize --compress quantize` - `KHR_mesh_quantization` is core Babylon/three+ support, zero decoder, and faster first paint than draco anywhere.+- Measured (BQ board, from ~20 MB raw): draco 956 KB | quantize-only (full quality) 4.36 MB |+ quantize+simplify 0.1% 2.4 MB | quantize+simplify 1% 845 KB. **1% simplification is visibly bad+ on PCB detail (John rejected it)** - with the PNG channel there is no size pressure, so ship+ quantize-only full quality.+- **`--instance false` is mandatory**: the optimizer's `EXT_mesh_gpu_instancing` renders as ONE+ giant mis-scaled instance in this Babylon build (a single machine pin filled the screen).++## Textures: the blob-to-data-URI shim++Babylon hands glTF textures to `<img>` via `URL.createObjectURL(blob)` -> blocked (img-src has no+blob:). Even textures stored as data: URIs inside the glTF get blob-ized. Shim once, before loading:++```js+const orig = URL.createObjectURL.bind(URL);+URL.createObjectURL = (b) => (b && b.__dataUri) || orig(b);+const OrigBlob = window.Blob;+window.Blob = function(parts, opts){+ const bl = new OrigBlob(parts, opts);+ try {+ if (parts && parts.length===1 && (parts[0] instanceof Uint8Array || parts[0] instanceof ArrayBuffer)) {+ const u8 = parts[0] instanceof Uint8Array ? parts[0] : new Uint8Array(parts[0]);+ if (u8.length < 3000000) {+ let s=""; for (let i=0;i<u8.length;i+=0x8000) s+=String.fromCharCode.apply(null,u8.subarray(i,i+0x8000));+ bl.__dataUri = "data:"+((opts&&opts.type)||"application/octet-stream")+";base64,"+btoa(s);+ }+ }+ } catch(e){}+ return bl;+};+window.Blob.prototype = OrigBlob.prototype;+```++img-src data: is allowed, so the stamped data: URI renders. (Also convert the GLB's bufferView+images to data: URIs in its glTF JSON - belt and suspenders.)++## The five things the bundle only does inside its own loadModel (do them yourself)++Bypassing `loadModel` means the viewer is mounted but inert. After every append:++1. **Start the render loop** (else the canvas is pure black at "60fps"):+ `eng.stopRenderLoop(); eng.runRenderLoop(()=>scene.render());`+2. **Add lights** (PBR renders as a black silhouette with zero lights and no environment):+ hemispheric + key + fill `DirectionalLight`s is enough.+3. **Frame the camera on `scene.activeCamera`** - `viewer.getCamera()` can be a DIFFERENT object;+ set target/radius on `scene.activeCamera`, inside a `setTimeout(...,200)` so nothing internally+ reframes over you. Use `scene.getWorldExtends()` (full scene - quantized models carry their scale+ in node matrices, per-mesh bounds lie).+4. **Scale the clip planes**: default `minZ` 0.1 CLIPS a small board entirely. Set+ `minZ = diag*0.005`, `maxZ = diag*200`, `wheelPrecision = 3/diag`.+5. **Track and dispose your own appends on toggle** - `viewer.clearScene()` does not know about+ externally appended meshes (they accumulate): snapshot `scene.meshes.length` before append, keep+ the slice, `mesh.dispose(false,true)` each before the next load.++## Verification (headless puppeteer, since pup/CDP screenshots of nested WebGL lie)++- `page.evaluate`: read your status element + `EngineStore.Instances[0].scenes[0].meshes.length`.+- Pixel truth: `readPixels` INSIDE `scene.onAfterRenderObservable` (outside the frame, non-+ preserveDrawingBuffer canvases read black). If `onAfterRender` never fires, the render loop+ isn't running - see item 1.+- Note a nonblack-pixel test can be fooled by the clear color - verify the MODEL visually via an+ OS-level window capture (`desktop_screenshot_window`) on a real desktop.++## Dead ends (do not retry)++- `viewer.loadModel("/blob/...")`, `fetch()`, XHR, `fetch("data:...")` - connect-src 'none' blocks all.+- data:/blob: URL strings into loadModel or AppendAsync - loader fetches them; blocked.+- Draco/meshopt GLBs - wasm + workers + eval all blocked.+- Nesting the wiki's `/viewer/3d/...` page in an iframe (frame-src https: allows it, and the scene+ runs at 60fps) - but the nested canvas composites BLACK on screen. Unresolved; avoid.+- Embedding >4-5 MB in readme.html - "README too large to render".+- Serving JS/HTML from `/blob` - `application/octet-stream` + `content-disposition: attachment` ++ nosniff; scripts will not execute from there.++## Platform asks that obsolete parts of this skill (file/track on adom/wiki)++`connect-src 'self'` on `/readme` would replace the PNG channel with a plain fetch; `img-src blob:`+would remove the texture shim; correct content-types on `/blob` (html/js/svg/csv) would allow real+runtime assets. Until then, this recipe is the way.