Playground examples
SkillProductivityThis skill should be used when adding, editing, or fixing an example in apps/playground — when a task says "add a playground example", "example fails check:examples", "playgroundExamples.test.ts is failing", "regenerate ambient types", "generate-types", "example thumbnail is missing", "run npm run thumbs", "example renders wrong / blank viewer", "example works in tests but breaks in the browser", or when a new example needs to pass its three gates (types, geometry, thumbnail) before merge.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Playground examples skill
What this skill tells your AI
The instructions your AI receives, as published by andymai/brepjs in .claude/skills/playground-examples/SKILL.md and read by ahel’s review.
Add or fix an example in apps/playground and clear its three gates: types, geometry, thumbnail. Every example is a self-contained code string that (1) type-checks against the editor's ambient types, (2) evaluates and meshes against the OCCT kernel in the root test suite, and (3) ships a committed .webp thumbnail. Miss any one and CI or the gallery breaks.
For bulk import from an OpenSCAD reference library, use the /scad-to-playground workflow instead — it encodes the same validate→render→repair loop for many examples at once. This skill is the manual, single-example counterpart.
Example anatomy
An example is an Example { id, label, description, code } (apps/playground/src/lib/examples/types.ts). Examples live in category files and are aggregated by a barrel:
| File | Category | Notes |
|---|---|---|
apps/playground/src/lib/examples/basics.ts | Basics | Calibration for house comment style |
apps/playground/src/lib/examples/mechanical.ts | Mechanical | Largest set |
apps/playground/src/lib/examples/sheetMetal.ts | Sheet Metal | imports brepjs-sheetmetal |
apps/playground/src/lib/examples/bim.ts | BIM | imports brepjs-bim, uses top-level await |
apps/playground/src/lib/examples/families.ts | Families | imports brepjs-families (+ brepjs-bim for the IFC projection) |
apps/playground/src/lib/examples/index.ts | barrel | builds CATEGORIES + flat EXAMPLES |
To add an example: append an Example to the appropriate category array. To add a new category: create a file exporting an Example[], then register it in CATEGORIES (index.ts).
Code-string rules (hard constraints)
The code field becomes the Monaco editor buffer verbatim AND is executed by both the browser worker and the root test harness. It must obey (types.ts):
- Self-contained. No shared helpers, no imports of other examples, no TS-only constructs the harness's sucrase strip can't handle (
transforms: ['typescript'],tests/helpers/playgroundExampleEval.ts). - Named imports only, from the recognized specifiers. The eval harness rewrites only the
import { … } from '<spec>'form for these specifiers:brepjs,brepjs/quick,brepjs/playground,brepjs-sheetmetal,brepjs-bim,brepjs-families(playgroundExampleEval.ts). Namespace (import * as) and default imports are NOT rewritten and will fail at runtime. Prefer'brepjs/quick'. - Ends in
export default <shape | shape[]>. Return one shape or an array; an array renders each shape. The harness turnsexport defaultintoreturn(playgroundExampleEval.ts). color()/present()come from'brepjs/playground', not published API.color(shape, css)tags a color;present(shape, { dxf, ifc, bimTree, overlay2d })attaches downloadable artifacts. Both are stripped back to the shape before meshing (playgroundExampleEval.ts, 108-113).unwrap()finishing ops — neverx.ok ? x.value : base. See Gate 2; the silent-fallback ban is enforced by regex.
Comment style
Match basics.ts: one punchy header line, aligned trailing dimension comments, terse one-line section notes. Example from basics.ts:
const drilled = unwrap(cut(box(30, 20, 10), cylinder(5, 15, { at: [15, 10, -2] })));
Keep comments concise — they are read in a small Monaco pane. Avoid multi-line walls of prose.
The three gates
Gate 1 — types (check:examples)
cd apps/playground && npm run check:examples
apps/playground/scripts/checkExamples.ts type-checks every example's code against the generated ambient .d.ts files (src/types/brepjs-ambient.d.ts, -sheetmetal-, -bim-, -families-), wrapped into declare module blocks by the same buildBrepjsModuleDts the Monaco editor uses, with the editor's compiler options (ES2022, moduleResolution Bundler, strict, skipLibCheck). Passing == "no red squiggles in the editor". It also checks the docs landing hero snippet docs-hero:PLAYGROUND_PROGRAM extracted from apps/docs/.vitepress/theme/components/CodeCadHero.vue — if that template literal is renamed or moved, the script exits 1 with a pointed message.
On failure, decide the cause:
| Symptom | Cause | Fix |
|---|---|---|
| Error on an API the example uses | Example bug | Fix the code string |
Method/type exists in src but not the ambient .d.ts | Stale ambient types | Rebuild the package(s), run npm run generate-types, commit the regenerated src/types/*-ambient.d.ts |
| "Could not find PLAYGROUND_PROGRAM" | Hero literal moved | Restore the literal or update the PLAYGROUND_PROGRAM regex in checkExamples.ts |
Regenerating types: generate-ambient-types.ts reads each package's built node_modules/<pkg>/dist/index.d.ts (build the package first), and deliberately excludes the experimental implicit/ modules (EXCLUDED_MODULE_RE = /(^|\/)implicit\//, generator lines 60-64) because they re-export core primitives aliased as sdfCylinder etc. that would otherwise overwrite the real cylinder/box/cone. Satellite packages re-emit their brepjs-sourced names as a top-of-file import type { … } from 'brepjs' that resolves against the sibling declare module 'brepjs' at consumption time — leave that mechanism intact. See kernel-abstraction and companion-packages skills for package build order.
Where it runs in CI: the playground build script is tsc -b && npm run check:examples && vite build (package.json), reached through the site-build job's npm run build:site (path-gated on the site filter in .github/workflows/ci.yml). The playground's prebuild hook (build:deps) builds brepjs-families, brepjs-bim, brepjs-sheetmetal, and brepjs-viewer first.
Gate 2 — geometry (tests/playgroundExamples.test.ts)
npx vitest run --project occt-wasm tests/playgroundExamples.test.ts
Run from the repo root. This lives in tests/, so it is part of the root suite and needs no dist build — root vitest aliases brepjs, brepjs-sheetmetal, brepjs-bim, and brepjs-families to live src (vitest.config.ts). Pre-commit's changed-file run (vitest run --project occt-wasm --changed) picks it up when an example file changes, because vitest --changed follows the import graph into apps/playground/src/lib/examples/.
Four assertion families (tests/playgroundExamples.test.ts):
- Unique
idandlabelacross all examples (lines 20-25). - Evals + meshes: each example produces
shapeCount > 0andtotalVertices > 0(lines 27-33). - No silent finishing-op fallback: the regex
/(\.ok\s*\?[^:]*:|isOk\s*\([^)]*\)\s*\?[^:]*:)/must not match — patterns likex.ok ? x.value : baseorisOk(x) ? unwrap(x) : baseare banned (lines 40-48). A swallowed fillet/chamfer failure makes a no-op pass the mesh check while shipping an unfinished part. Useunwrap()so failures throw and get caught. See result-error-handling. - Connected-body check for a hard-coded assembly list
CONNECTED_BODY_EXAMPLES(universal-joint, geneva-drive, bench-vise, scotch-yoke, three-jaw-chuck, worm-gear-drive, lines 55-62): each exported body must havegetSolids().length === 1. A disjoint compound still meshes but detaches on STEP/GLB export. When adding a multi-body mechanism/assembly example, add its id to this list.
If geometry is wrong (see debugging-geometry for the full triage): common example pitfalls are revolve() of a profile whose edge touches the axis (degenerate), features added where they should be cut (inverted boolean), and the silent-fallback pattern above.
Gate 3 — thumbnail (committed .webp)
Each example needs a committed apps/playground/public/example-thumbs/<id>.webp (58 static thumbnails committed today; a further 46 optional .turntable.webp files also live here), consumed by ExampleGallery.tsx. Generating one requires a running dev server:
cd apps/playground
(npm run dev > tmp/pg.log 2>&1 &) ; sleep 6
PORT_URL=$(grep -oE 'http://localhost:[0-9]+' tmp/pg.log | head -1)
npm run thumbs "$PORT_URL" <example-id>
Vite may pick a non-5173 port if one is busy — always sniff the actual URL from the log, don't hardcode. npm run thumbs (shootExamples.ts --thumbs) frames the model (Iso preset, Fit, grid off) and writes a centred square WebP. Commit public/example-thumbs/<id>.webp.
Optional companion: npm run turntables "$PORT_URL" <id> writes an animated <id>.turntable.webp (needs img2webp or ffmpeg on PATH and the DEV-only window.__brepjsOrbit hook). The gallery lazy-loads it on hover and remembers 404s, so a missing turntable is tolerated — many examples ship only the static webp.
Visual-repair loop: npm run shoot "$PORT_URL" tmp/shots <id> writes a full-page PNG; Read it to confirm the shape looks right, edit the code, re-run Gate 2, re-shoot. A shape can pass eval+mesh yet render off-centre, floating, or degenerate — the screenshot is the only thing that catches that.
Symptom → cause → fix
| Symptom | Cause | Fix |
|---|---|---|
| Gates green, browser shows blank/broken viewer | Stale companion dist (worker lazy-imports brepjs-bim/brepjs-sheetmetal/brepjs-families from their built dist, not src) | build:deps runs on predev/prebuild and auto-heals; restart a long-running dev server after editing brepjs-bim/brepjs-sheetmetal/brepjs-families/brepjs-viewer. See companion-packages. |
| Namespace/default import fails at runtime but type-checks | Harness only rewrites import { … } from form | Convert to named imports |
| Example edit not lint/format-checked locally | Playground app code is outside root lint/typecheck/lint-staged | Its own gates are tsc -b + check:examples + vite build, reached through the path-gated site-build CI job |
| Thumbnail command fails to connect | Wrong port | Sniff the port from the dev-server log |
check:examples fails on the hero snippet | Hero literal moved in CodeCadHero.vue | Keep PLAYGROUND_PROGRAM intact or update checkExamples.ts |
Note: the production playground-smoke workflow only checks the deployed engine boots; it does NOT verify examples. Gate 2 is the sole guard that each example runs.
Checklist for a new example
- Add the
Exampleto the right category file (or register a new category inindex.ts). cd apps/playground && npm run check:examples— types green.npx vitest run --project occt-wasm tests/playgroundExamples.test.ts— geometry green (add multi-body assemblies toCONNECTED_BODY_EXAMPLES).- Start dev server,
npm run thumbs "$URL" <id>, commitpublic/example-thumbs/<id>.webp. - Optional:
npm run shoot "$URL" tmp/shots <id>+ Read the PNG to confirm framing.
Additional resources
The in-code file headers are the authoritative depth and stay current with the code; read them rather than a restatement:
apps/playground/src/lib/examples/types.ts— authoring rulesapps/playground/scripts/checkExamples.ts— Gate 1tests/playgroundExamples.test.ts+tests/helpers/playgroundExampleEval.ts— Gate 2 + eval harness mechanicsapps/playground/scripts/shootExamples.ts— Gate 3, audit and turntable modesapps/playground/scripts/generate-ambient-types.ts+apps/playground/src/lib/ambientModule.ts— the editor type surface.claude/workflows/scad-to-playground.js— bulk-import automation precedent
Sibling skills: debugging-geometry (wrong/empty geometry), result-error-handling (unwrap vs fallback), companion-packages (dist build order, stale-dist trap), quality-gates and ci-triage (gate/CI mechanics).
Signals
- GitHub stars
- 101
- Forks
- 9
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
playground-examples- Source
- github.com/andymai/brepjs