Web-based generator for ergonomic mechanical keyboards. SvelteKit + TypeScript on top of a heavy CAD/3D pipeline (Three.js + Threlte for rendering, replicad/OpenCascade + Manifold for solids, OpenSCAD for keycaps, MediaPipe for hand scanning).
The user-facing app lives at /beta (src/routes/beta). Most of the interesting logic is in src/lib/worker/, which runs in a Web Worker bridged via comlink.
- Bun is preferred, Node is the fallback. The
Makefiledetectsbunand uses it; otherwise it usesnodewith a custom ESM loader (src/model_gen/register_loader.js). When in doubt, use Bun — Node mode is harder to debug. - Python venv is used only for docs (
mkdocs). Created bymake venv. .nvmrcpins Node v21+. Use that if running without Bun.
make quickstart # one-time: install deps, compile protobufs, generate parts/keycaps geometry
make dev # start vite dev server → http://localhost:5173/beta
npm run check # svelte-check + tsc — the closest thing to a CI signal locally
npm test # runs `bun test` against *.test.ts files
make build # regenerates target/proto/*.ts and target/editorDeclarations.d.tsThe default make target only regenerates protobufs and editor types. make quickstart is heavier — it also runs parts, parts-simple, keycaps2, keycaps-simple2 which produce GLB/STEP files in target/.
| Path | Role |
|---|---|
src/routes/beta/ |
The generator UI (the /beta page). |
src/routes/scan/, scan2/ |
Hand-scanning UI; processing in src/routes/scan/lib/hand.ts. |
src/lib/worker/ |
Worker-side CAD code. The hot files are geometry.ts, cachedGeometry.ts, model.ts, config.ts. |
src/lib/3d/ |
Threlte components (rendering). |
src/lib/loaders/, src/lib/runner/ |
Asset loading and worker bootstrap. |
src/proto/ |
.proto schemas — source of truth for serialized configs. |
src/model_gen/ |
Build-time scripts that produce target/ artifacts (keycaps, switches, microcontrollers, parts). Not run at app runtime. |
target/ |
Generated. Don't hand-edit. Includes target/proto/*.ts (compiled protos), GLB/STEP assets, editor type declarations. |
pro/ |
Pro-only features. Gitignored — usually not present in this clone. Code in src/lib/worker/pro-patch/ references it conditionally; don't try to "fix" missing imports there. |
docs/docs/ |
mkdocs source for the public docs site. |
target/PseudoProfiles/, target/KeyV2/ |
Cloned third-party repos (keycap profiles). |
Three files, in order:
src/lib/worker/geometry.ts(≈2,500 lines) — pure geometry: where every key, web, and screw goes in 3D space. No solid modeling.src/lib/worker/cachedGeometry.ts— memoized higher-level wrapper overgeometry.ts.src/lib/worker/model.ts(≈1,200 lines) — turns geometry into actual solids via replicad/OpenCascade.
If a change affects how the keyboard looks, it almost always lands in one of these three. The split is intentional: geometry.ts should stay free of CAD operations.
- The schema lives in
src/proto/cosmos.proto(and the oldercuttleform.proto,manuform.proto,lightcycle.proto). After editing a.proto, runmake buildto regeneratetarget/proto/*.ts. - TypeScript-side model:
src/lib/worker/config.tsandconfig.cosmos.ts. Serialization helpers inconfig.serialize.ts. - Editor autocompletion types are generated from
config.tsintotarget/editorDeclarations.d.tsbysrc/model_gen/genEditorTypes.ts.
Two test files exist and use bun:test (not vitest):
src/lib/worker/config.test.tssrc/routes/beta/lib/editor/tuple.test.ts
Run with npm test (which calls bun test). Tests are sparse — npm run check (svelte-check + tsc) is the more useful local signal for most changes.
dprint is configured in dprint.json and runs on commit via simple-git-hooks + lint-staged. The Claude PostToolUse hook in .claude/settings.json formats edited files automatically — match this if you script around it.
Quote style: single quotes, no semicolons (TS) — see dprint.json.
src/lib/opencv.jsandsrc/lib/opencv-contrib.jsare generated by Vite plugins invite.config.ts. Don't edit them.target/andpro/look like source but are not.- The Vite dev server proxies
/docs,/blog, etc. tolocalhost:8000(mkdocs). Those routes look broken until you also startmake docs. src/routes/lemonis gitignored — references to it from other files are intentional.- Imports of
pro-patch/*modules are conditional and may be stubbed in OSS clones. - The
tsconfig.jsononly includessrc/routes/**/*directly; the rest is pulled in by SvelteKit's generated.svelte-kit/tsconfig.json. Sotscinvoked outside SvelteKit's wrapper will look like nothing is type-checked — usenpm run checkinstead.
- Don't add new top-level dependencies casually — the build is already heavy. Prefer reusing what's there (
replicad,manifold-3d,three,comlink). - Don't write to
target/directly. If you need a new generated artifact, add a script undersrc/model_gen/and a target in theMakefile. - Worker code (
src/lib/worker/) cannot use DOM APIs. Keep main-thread vs. worker code separate.