From 6fae2e072a1c1ca3ae5ad7dd95ec7035884b9afb Mon Sep 17 00:00:00 2001 From: Yuval Olsha Date: Mon, 7 Sep 2026 20:57:39 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20v0.4-v0.6.1=20=E2=80=94=20guided=20make?= =?UTF-8?q?,=20auto-update,=20Studio,=20and=20a=20browser=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed from a fork's development history so the diff reads as one change rather than 30 auto-commits. Five releases of work, plus a portability pass so none of it assumes the machine it was written on. GUIDED MAKE (v0.4). A print-type grid is the front door: pick what you're making, describe it in a sentence, press one button. The type sets walls, tolerances, resolution and modelling approach; those controls are still there, demoted to a Fine-tune row. Obvious things (an M4 standoff 20 mm long) go straight to a model; matters of taste generate reference pictures and ask "is this the thing?" before spending ten minutes on a mesh. The app always says which it chose and why, and you can override either way. STUDIO (v0.6). That flow moved out of a 280 px scrolling rail above the terminal into a full-window view, switched from the header: Make and Workbench. Thirteen switchable tool groups (Dimensions, Hardware, Fit & tolerance, Mounting, Text, Strength, Material, ...) compile into three separate things — constraints Claude reads, words the image prompt gets, and claw-gen flags. The routing decision (renderer/route.js) is a pure function with its own harness. AUTO-UPDATE (v0.5). electron-updater against GitHub releases: background download, install on quit, never mid-render. Only a staged update is announced — checking and failed checks stay silent, since a notification you cannot act on is noise. scripts/release.ps1 publishes in two phases because `electron-builder --publish always` has a race that ships a release with no latest.yml, which every updater client is blind to, permanently and silently. The script builds with --publish never, asserts the .exe/.blockmap/latest.yml agree on version, path and sha512, uploads with gh one file at a time, then fetches the published manifest back over HTTP before claiming success. CI no longer publishes: exactly one publisher may own an update feed. Full reasoning in docs/releasing.md. BROWSER PORT (v0.6.1). web/ serves the Make view over HTTP, running renderer/studio.js UNMODIFIED — web/entry.js plays renderer.js's role and web/api-shim.js speaks fetch + EventSource where preload.js speaks ipcRenderer. Zero new dependencies: node:http and SSE. main/tools.js, main/categories.js and main/composer.js grew Electron-free exports so the server reuses the same catalog, the same userData-override rules and the same state-merge semantics rather than a second implementation that drifts. - Flow A never needed a pty: `claude -p --permission-mode acceptEdits` runs headless and exits. The .scad it produced is found by diffing workspace .scad mtimes, never by trusting the model to report a path. - Customize parses OpenSCAD's own Customizer syntax and applies values with -D, so a checkpoint is never rewritten to "customize" it. The preview is a server-side render, so it cannot drift from the artifact you download. - What does not work is stated on screen, never faked: no viewport, no terminal, no checkpoint tree. - It binds loopback and has NO authentication of its own. Path handling is the security boundary — every filesystem-touching request is resolved, realpath'd and containment-checked against the workspace rather than dot-dot filtered, and no CORS header is ever sent. FIXED: THE BUNDLED OPENSCAD WAS NEVER FOUND ON WINDOWS. The snapshot zip carries a top-level OpenSCAD--x86-64/ folder, so download-openscad.js left the binary at vendors/openscad-win/OpenSCAD-.../openscad.exe while main.js's openscadBundledPath() looks for vendors/openscad-win/openscad.exe. electron-builder copies that tree into the installer verbatim, so a shipped Windows build carried 65 MB of OpenSCAD it could not resolve and then told the user to go and install one. The download now flattens a single-directory archive and leaves an already-flat one alone; the rule is exported so it can be exercised without a 65 MB download. This predates the rest of this change. PORTABILITY. Nothing about one machine is compiled in any more: - The default workspace is ~/clawscad-workspace on every platform. It used to prefer E:\ then D:\ on Windows, which is one disk layout rather than anything true of a fresh install. Order is now $CLAWSCAD_WORKSPACE, else the workspace last opened if it still exists, else ~ — so an existing install keeps opening its own work instead of silently starting empty. - $CLAWSCAD_CLAUDE_BIN (and --claude on the server) for a Claude CLI the probe cannot find. - build.publish points at this repo. It is both the upload target and the update feed, and electron-updater resolves the newest release IN A REPO, not the newest release of a product — so a fork must repoint it or ship an app that updates itself into someone else's builds. - docs/configuration.md lists every environment variable, flag and override file in one place; docs/releasing.md replaces a private handoff note; and docs/generation-pipeline.md documents the claw-gen CLI contract — the backends JSON, the four actions, the NDJSON event stream — so the Generate panel is an integration point anyone can satisfy rather than a pointer to a CLI that is not publicly released. - The web port's deployment story is now "put an authenticating proxy in front" with three worked examples, rather than naming the one tunnel it happened to be developed against. The README keeps this project's own licence wording (MIT, see LICENSE) — the fork had restated it, and that is not a fork's call to make. TESTS. 328 assertions in `npm run test:harness` (pure Node, no Electron: route, tools, preset merge, composer state, scad-params, render-fault classification, an error-handler survival harness) plus 160 Playwright specs driving the real app. The parser rules that silently produce a wrong model rather than an error — the module/function cutoff, -D quoting — are mutation-proven: removing either turns specific checks red. --- .gitattributes | 1 + .github/workflows/build-linux.yml | 19 +- .github/workflows/build-macos.yml | 25 +- .github/workflows/build-windows.yml | 26 +- .gitignore | 8 + CHANGELOG.md | 139 ++ README.md | 200 ++- docs/configuration.md | 68 + docs/generation-pipeline.md | 90 ++ docs/releasing.md | 117 ++ docs/v04-guided-make-contracts.md | 377 +++++ docs/v06-studio-contracts.md | 398 +++++ icon.ico | Bin 0 -> 15713 bytes index.html | 249 ++- main.js | 966 +++++++++++- main/categories.js | 65 + main/composer.js | 89 ++ main/gallery.js | 191 +++ main/presets.js | 61 + main/registry.js | 20 + main/tools.js | 70 + main/updater.js | 210 +++ main/uploads.js | 576 +++++++ package-lock.json | 1747 ++++++++++----------- package.json | 130 +- playwright.config.js | 18 + preload.js | 93 +- presets/categories.json | 228 +++ presets/machine.json | 72 + presets/presets.json | 544 +++++++ presets/tools.json | 410 +++++ renderer.js | 1308 +++++++++++++++- renderer/bus.js | 75 + renderer/categories-ui.js | 713 +++++++++ renderer/composer.js | 636 ++++++++ renderer/confirm-gate.js | 774 ++++++++++ renderer/gallery.js | 711 +++++++++ renderer/onboarding.js | 268 ++++ renderer/preset-merge.js | 290 ++++ renderer/presets-ui.js | 492 ++++++ renderer/route.js | 503 ++++++ renderer/studio.js | 2222 +++++++++++++++++++++++++++ renderer/tools.js | 259 ++++ renderer/uploads.js | 346 +++++ scripts/check-native-deps.js | 114 ++ scripts/download-openscad.js | 52 +- scripts/probe-launch.js | 72 + scripts/release.ps1 | 258 ++++ scripts/shot.js | 43 + style-categories.css | 369 +++++ style-composer.css | 299 ++++ style-confirm.css | 437 ++++++ style-gallery.css | 400 +++++ style-onboarding.css | 188 +++ style-presets.css | 232 +++ style-studio.css | 1732 +++++++++++++++++++++ style-uploads.css | 118 ++ style.css | 2041 +++++++++++++++++------- tests/app.spec.js | 34 +- tests/categories.spec.js | 388 +++++ tests/claude-md.spec.js | 137 ++ tests/composer-state.js | 115 ++ tests/composer.spec.js | 121 ++ tests/confirm.spec.js | 321 ++++ tests/error-handler-survival.js | 140 ++ tests/fixtures/fake-claw-gen.cs | 115 ++ tests/gallery.spec.js | 174 +++ tests/generate-panel.spec.js | 242 +++ tests/helpers.js | 26 + tests/onboarding.spec.js | 148 ++ tests/preset-merge.js | 351 +++++ tests/presets.spec.js | 182 +++ tests/reconcile.spec.js | 210 +++ tests/render-fault-classify.js | 128 ++ tests/route.js | 856 +++++++++++ tests/scad-params.js | 257 ++++ tests/studio-recreate.spec.js | 102 ++ tests/studio.spec.js | 527 +++++++ tests/tools.js | 1026 +++++++++++++ tests/ui-surfaces.spec.js | 317 ++++ tests/uploads.spec.js | 239 +++ web/.gitignore | 3 + web/README.md | 113 ++ web/api-shim.js | 347 +++++ web/build.js | 48 + web/customize.js | 355 +++++ web/entry.js | 372 +++++ web/index.html | 108 ++ web/scad-params.mjs | 259 ++++ web/server.js | 1130 ++++++++++++++ web/web.css | 323 ++++ 91 files changed, 29627 insertions(+), 1746 deletions(-) create mode 100644 .gitattributes create mode 100644 CHANGELOG.md create mode 100644 docs/configuration.md create mode 100644 docs/generation-pipeline.md create mode 100644 docs/releasing.md create mode 100644 docs/v04-guided-make-contracts.md create mode 100644 docs/v06-studio-contracts.md create mode 100644 icon.ico create mode 100644 main/categories.js create mode 100644 main/composer.js create mode 100644 main/gallery.js create mode 100644 main/presets.js create mode 100644 main/registry.js create mode 100644 main/tools.js create mode 100644 main/updater.js create mode 100644 main/uploads.js create mode 100644 presets/categories.json create mode 100644 presets/machine.json create mode 100644 presets/presets.json create mode 100644 presets/tools.json create mode 100644 renderer/bus.js create mode 100644 renderer/categories-ui.js create mode 100644 renderer/composer.js create mode 100644 renderer/confirm-gate.js create mode 100644 renderer/gallery.js create mode 100644 renderer/onboarding.js create mode 100644 renderer/preset-merge.js create mode 100644 renderer/presets-ui.js create mode 100644 renderer/route.js create mode 100644 renderer/studio.js create mode 100644 renderer/tools.js create mode 100644 renderer/uploads.js create mode 100644 scripts/check-native-deps.js create mode 100644 scripts/probe-launch.js create mode 100644 scripts/release.ps1 create mode 100644 scripts/shot.js create mode 100644 style-categories.css create mode 100644 style-composer.css create mode 100644 style-confirm.css create mode 100644 style-gallery.css create mode 100644 style-onboarding.css create mode 100644 style-presets.css create mode 100644 style-studio.css create mode 100644 style-uploads.css create mode 100644 tests/categories.spec.js create mode 100644 tests/claude-md.spec.js create mode 100644 tests/composer-state.js create mode 100644 tests/composer.spec.js create mode 100644 tests/confirm.spec.js create mode 100644 tests/error-handler-survival.js create mode 100644 tests/fixtures/fake-claw-gen.cs create mode 100644 tests/gallery.spec.js create mode 100644 tests/generate-panel.spec.js create mode 100644 tests/helpers.js create mode 100644 tests/onboarding.spec.js create mode 100644 tests/preset-merge.js create mode 100644 tests/presets.spec.js create mode 100644 tests/reconcile.spec.js create mode 100644 tests/render-fault-classify.js create mode 100644 tests/route.js create mode 100644 tests/scad-params.js create mode 100644 tests/studio-recreate.spec.js create mode 100644 tests/studio.spec.js create mode 100644 tests/tools.js create mode 100644 tests/ui-surfaces.spec.js create mode 100644 tests/uploads.spec.js create mode 100644 web/.gitignore create mode 100644 web/README.md create mode 100644 web/api-shim.js create mode 100644 web/build.js create mode 100644 web/customize.js create mode 100644 web/entry.js create mode 100644 web/index.html create mode 100644 web/scad-params.mjs create mode 100644 web/server.js create mode 100644 web/web.css diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 95223b4..9721329 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -1,15 +1,17 @@ name: Build Linux (AppImage) +# Verification only. This workflow does NOT publish releases — see the note in +# build-windows.yml and docs/releasing.md. `scripts/release.ps1` owns the +# update feed. on: push: branches: [main] - tags: ['v*', '20*'] pull_request: branches: [main] workflow_dispatch: permissions: - contents: write + contents: read env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true @@ -29,13 +31,6 @@ jobs: - name: Install dependencies run: npm ci - - name: Set version from tag - if: startsWith(github.ref, 'refs/tags/') - run: | - D="${GITHUB_REF_NAME#v}" - SEMVER="${D:0:4}.$((10#${D:4:2})).$((10#${D:6:2}))" - npm version --no-git-tag-version "$SEMVER" - - name: Cache OpenSCAD vendor binary uses: actions/cache@v4 with: @@ -59,9 +54,3 @@ jobs: name: ClawSCAD-Linux-AppImage path: release/*.AppImage if-no-files-found: warn - - - name: Upload to release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 - with: - files: release/*.AppImage diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index deffbc3..fa32439 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -1,13 +1,17 @@ name: Build macOS (dmg) -# macOS runners cost 10x — only run on version tags or manual trigger +# Verification only. This workflow does NOT publish releases — see the note in +# build-windows.yml and docs/releasing.md. `scripts/release.ps1` owns the +# update feed. +# +# macOS runners cost 10x, so this stays manual-only. It previously ran on +# version tags, which is exactly the trigger that collided with the release +# script; with the tag trigger gone, manual dispatch is the whole surface. on: - push: - tags: ['v*', '20*'] workflow_dispatch: permissions: - contents: write + contents: read env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true @@ -27,13 +31,6 @@ jobs: - name: Install dependencies run: npm ci - - name: Set version from tag - if: startsWith(github.ref, 'refs/tags/') - run: | - D="${GITHUB_REF_NAME#v}" - SEMVER="${D:0:4}.$((10#${D:4:2})).$((10#${D:6:2}))" - npm version --no-git-tag-version "$SEMVER" - - name: Cache OpenSCAD vendor binary uses: actions/cache@v4 with: @@ -57,9 +54,3 @@ jobs: name: ClawSCAD-macOS-dmg path: release/*.dmg if-no-files-found: warn - - - name: Upload to release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 - with: - files: release/*.dmg diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 1e798f5..39b50dd 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1,15 +1,23 @@ name: Build Windows (exe) +# Verification only. This workflow does NOT publish releases. +# +# It used to trigger on tags: ['v*', '20*'] and upload release/*.exe to the +# GitHub release. That collided with `npm run release`, which creates the tag +# as its first act: CI would then rebuild and overwrite the .exe with a +# different binary whose sha512 no longer matched the published latest.yml, +# permanently breaking that update for every client — silently, and after the +# release script had already printed green. `scripts/release.ps1` owns the +# feed; exactly one publisher may. See docs/releasing.md. on: push: branches: [main] - tags: ['v*', '20*'] pull_request: branches: [main] workflow_dispatch: permissions: - contents: write + contents: read env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true @@ -29,14 +37,6 @@ jobs: - name: Install dependencies run: npm ci - - name: Set version from tag - if: startsWith(github.ref, 'refs/tags/') - shell: bash - run: | - D="${GITHUB_REF_NAME#v}" - SEMVER="${D:0:4}.$((10#${D:4:2})).$((10#${D:6:2}))" - npm version --no-git-tag-version "$SEMVER" - - name: Cache OpenSCAD vendor binary uses: actions/cache@v4 with: @@ -60,9 +60,3 @@ jobs: name: ClawSCAD-Windows-Setup path: release/*.exe if-no-files-found: warn - - - name: Upload to release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 - with: - files: release/*.exe diff --git a/.gitignore b/.gitignore index a65f659..2bed4fe 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,11 @@ vendors/ .DS_Store test-results/ playwright-report/ +# app/test stdout+stderr captures — these got auto-committed once already +*.log +tests/fixtures/*.exe +tests/fixtures/*.pdb + +# web port runtime logs +web-server.log +web-server.err diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..beda2e6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,139 @@ +# Changelog + +All notable changes to ClawSCAD. Versions follow [semver](https://semver.org/). + +`scripts/release.ps1` pulls the release notes for a version straight out of the +matching `## [x.y.z]` section below, so keep the heading format exact. + +## [0.6.1] - 2026-09-07 + +### Added + +- **A browser port of the Make view** (`web/`). A zero-dependency `node:http` + server mounts `renderer/studio.js` **unmodified** against a `fetch` + + `EventSource` shim shaped like `preload.js`, so a phone or tablet on your own + network can drive it. The type grid, tools, routing decision, state + persistence, pictures-first rounds, uploads and recreate all work; the + three.js viewport, the pty terminal and the checkpoint tree do not, and the + UI says so rather than offering a dead control. It binds loopback and has + **no authentication of its own** — see `web/README.md`. +- **Flow A headless in the web port.** "Make it" never needed a pty: + `claude -p --permission-mode acceptEdits` runs in the workspace and exits, + streaming progress over SSE. The `.scad` it produced is found by diffing + workspace `.scad` mtimes rather than by trusting the model to report a path. +- **An OpenSCAD Customizer + export in the web port.** `web/scad-params.mjs` + parses OpenSCAD's own Customizer syntax; values are applied with `-D`, and + the preview is a server-side render so it cannot drift from the exported + artifact. 3MF is the primary export, STL secondary. Checkpoints are never + rewritten to "customize" them. +- **`CLAWSCAD_WORKSPACE` and `CLAWSCAD_CLAUDE_BIN`.** Explicit escape hatches + for the two paths the app otherwise has to guess. Every configuration point + is now listed in `docs/configuration.md`. + +### Fixed + +- **The bundled OpenSCAD was never found on Windows.** The snapshot zip carries + a top-level `OpenSCAD--x86-64/` folder, so `download-openscad.js` + left the binary at `vendors/openscad-win/OpenSCAD-…/openscad.exe` while the + app looks for `vendors/openscad-win/openscad.exe`. `electron-builder` copies + that tree into the installer verbatim, so a shipped Windows build carried + 65 MB of OpenSCAD it could not resolve and told the user to install one. The + download now flattens a single-directory archive, and leaves an + already-flat one alone. + +### Changed + +- **The default workspace is `~/clawscad-workspace` on every platform.** On + Windows it used to prefer `E:\` then `D:\`, which is one machine's disk + layout rather than anything true of a fresh install. The launch default is + now `$CLAWSCAD_WORKSPACE`, else the workspace you last opened if it still + exists, else `~/clawscad-workspace` — so an existing install keeps opening + its own work rather than silently starting empty somewhere new. +- **`docs/releasing.md` replaces `HANDOFF-auto-update.md`**, and documents the + one thing a fork must change before publishing: `build.publish` in + `package.json` is both the upload target and the update feed, and + `electron-updater` resolves the newest release *in a repo*, not the newest + release of a product. + +## [0.6.0] - 2026-08-29 + +### Added + +- **Studio — a new front door.** The window now has two views, switched from the + header: **Make** and **Workbench**. Make is a full-window dashboard — pick what + you're making, describe it in a sentence, press one button. Previously that + same flow lived in a 280 px scrolling rail wedged above the terminal, with the + submit button below the fold. +- **Tools.** Thirteen switchable field groups — Dimensions, Hardware, Fit & + tolerance, Mounting, The part it replaces, Text & engraving, Strength, + Material, Print settings, Quantity, Style, Colour, Mesh detail — for saying + what a sentence can't carry. Picking a print type switches on the ones that + type usually needs; you can add or remove any of them. Each compiles into + three separate things: constraints Claude reads, words the image prompt gets, + and `claw-gen` flags. A tool that doesn't apply to what you're making says so + in plain words instead of greying out. +- **A toggleable pre-image step.** *Pictures first* has three states — automatic + (the app decides, and tells you why), always, and never. When it's on you get + reference images before any mesh work, and you narrow in: **More like this** + adds a round, **Refine…** adds a round with a change ("bigger eyes"), and + earlier rounds stay on screen so you can compare. Approving a picture either + meshes it or hands it to Claude as a reference to build parametrically, + depending on what you're making. +- **Add a picture.** Attach an image as a **reference** for Claude, or + **recreate** it directly as a 3D model. Recreating runs + `claw-gen mesh --image … --new-job` and carries on through prep to a + checkpoint. +- Per-backend availability. If image generation or 3D meshing is unavailable, + the studio says so up front with the reason, and keeps every flow that still + works — rather than failing ten minutes into a job. + +### Fixed + +- **`composer-state.json` was overwritten wholesale on every write.** With a + second writer added this release, the composer's per-keystroke save would have + deleted the studio's state moments after it was written. It now merges by + top-level key. (An empty object still clears the file.) +- **Refine silently did nothing** (`clawscad-gen` 0.3.0). A new round on an + existing job reused the job's cached expanded prompt, so a refinement was + accepted, took a full round to run, and produced the same thing. +- **Every "More like this" started a new job**, whose candidate keys collide + with the previous job's. Rounds now accumulate in one job. +- **An uploaded image was meshed into whatever job was last in flight**, so it + was checkpointed under that job's name with two subjects' files interleaved. + `claw-gen mesh --image` now takes `--new-job`. + +### Changed + +- **The build no longer rebuilds native modules for Electron.** `node-pty` is + N-API, whose ABI is stable across Node and Electron, so the `electron-rebuild` + postinstall was doing nothing but failing — it needed the Spectre-mitigated + MSVC libraries, which is what blocked `npm run release` on Windows. A new + `scripts/check-native-deps.js` fails the test run if a native dependency is + ever added that is *not* N-API, since that one would silently package a binary + that throws only in the installed app. +- **CI no longer publishes releases.** The three build workflows triggered on + version tags and uploaded their own installer over the one `npm run release` + had just published — a different binary, so its hash no longer matched the + update manifest, permanently breaking that update for every client while the + release script still printed green. `scripts/release.ps1` now owns the feed + alone; CI builds on `main` and PRs for verification only. + +## [0.5.1] - 2026-08-26 + +### Added + +- **Auto-update.** ClawSCAD now checks its own GitHub releases, downloads a newer + version in the background, and installs it when you quit — so a long Claude + session or an in-flight render is never interrupted. A staged update shows as a + pill in the status bar; clicking it restarts into the new version immediately. + Checking and downloading stay silent by design. +- `npm run release` — a two-phase publish script that builds with + `--publish never`, asserts the installer, blockmap and `latest.yml` are all + present and mutually consistent, uploads them with `gh`, then reads the + published manifest back over HTTP. This avoids an `electron-builder` race that + can ship a public release with no update manifest at all. + +### Notes + +- The build already installed on your machine has no updater, so **one final + manual install is unavoidable**. Every version after 0.5.1 updates itself. diff --git a/README.md b/README.md index d4b1219..ca29e0a 100644 --- a/README.md +++ b/README.md @@ -6,61 +6,115 @@

AI-powered 3D CAD environment
- OpenSCAD + Claude Code with checkpoint branching, auto-iteration, and multi-viewport support -

- -

- ClawSCAD Screenshot + OpenSCAD + Claude Code with checkpoint branching, auto-iteration, and live PBR viewport

--- -## What is ClawSCAD? +ClawSCAD wraps [OpenSCAD](https://openscad.org/) and [Claude Code](https://github.com/anthropics/claude-code) into a single Electron desktop app. Describe what you want to build, Claude writes the OpenSCAD code, the app renders it in a live 3D viewport, and every iteration is saved as an immutable checkpoint you can branch from at any time. + +![ClawSCAD screenshot](screenshot.png) + +## Making something + +You do not need to know CAD, and you do not need to know which part of the app to use. + +1. **Pick what you're making** — a grid of print types: screws & hardware, brackets & mounts, + boxes & cases, furniture, structural, replacement part, models & figures, home decor, + toys & games, or *something else*. +2. **Describe it in plain English** — *"an M4 standoff 20 mm long"*, *"a squat owl planter with + big round eyes"*. Optional guided fields (thread size, height, what it has to fit) appear for + the type you picked; every one of them is optional. +3. **Press the button.** That's it. -ClawSCAD glues together [OpenSCAD](https://openscad.org/) and [Claude Code](https://github.com/anthropics/claude-code) into a single desktop application. Tell Claude what to build, and it writes OpenSCAD code, renders it, validates the output, and auto-iterates until the model is correct — all while you watch in a live 3D viewport. +Picking a type sets the print settings and the modelling approach for you — walls, tolerances, +resolution, orientation rules, whether it's built parametrically or sculpted. Those controls are +all still there, demoted to a *Fine-tune* row, if you want them. -Every iteration is saved as an immutable checkpoint. You can click any checkpoint to go back, branch from it, and explore different design directions. Claude sees your full history and can reference any previous version. +**Obvious things just get made.** A standoff with a thread and a length has one right answer, so +ClawSCAD goes straight to a printable model. **Things that are a matter of taste get checked +first**: it generates a few reference pictures and asks *"is this the thing?"* before spending ten +minutes on a mesh. For a replacement part it asks for a photo of the real object instead, because +that is what actually makes it fit. + +The app always tells you which of those it chose and why, in one sentence, and you can always +override it — *Show me options first* / *Skip the check, just make it*. ## Features **3D Viewport** -- PBR rendering with environment-mapped reflections -- Orbit, pan, zoom (mouse + touch + keyboard) +- PBR rendering with environment-mapped reflections (Three.js) +- Orbit, pan, zoom — mouse, touch, and keyboard - Wireframe, edge overlay, orthographic/perspective toggle - 7 camera presets (Front/Back/Left/Right/Top/Bottom/Iso) -- Click any part to see dimensions, volume, weight, estimated print cost -- 6 customizable color swatches for instant model coloring +- Click any part to inspect dimensions, volume, weight, and estimated print cost +- 6 colour swatches for instant model recolouring +- Split viewport — open a second independent 3D view - Screenshot export -- Split viewport — open a second 3D view with independent camera **Checkpoint History** -- Every .scad file is an immutable checkpoint in a branching tree -- Click any checkpoint to instantly load its model (cached in memory) -- Branch from any point — Claude creates new files, never overwrites -- Collapsible tree with box-drawing connectors -- Right-click context menu: rename, delete, collapse, view source, resume session -- Hover tooltips showing the change description - -**Source Editor** -- Monaco editor with OpenSCAD syntax highlighting (Monarch grammar) -- Custom dark theme matching the app -- Find (Ctrl+F) and Replace (Ctrl+H) +- Every `.scad` file Claude writes is a permanent, numbered checkpoint +- Claude never overwrites — it always creates a new file +- Click any checkpoint to load it instantly (cached in memory) +- Branch from any point and explore design alternatives without losing previous work +- Right-click context menu: rename, delete, view source, resume session + +**Monaco Editor** +- Full OpenSCAD syntax highlighting (custom Monarch grammar) - Read-only by default, toggle to edit mode -- OpenSCAD error markers (red squiggles on error lines) +- Error markers (red squiggles) on OpenSCAD error lines +- Find / Replace (Ctrl+F / Ctrl+H) **Claude Code Integration** -- Embedded terminal running Claude Code -- OpenSCAD MCP server auto-configured for every workspace -- CLAUDE.md with mandatory rules: never overwrite files, use colors, validate with MCP tools -- Auto-iteration: when a render fails, ClawSCAD writes errors to RENDER_ERRORS.md and nudges Claude to fix them -- Session management: browse, resume, or start new Claude sessions -- Dual terminal support (up to 2 Claude instances) -- Multi-window support (up to 4 projects, Claude sees all workspaces) +- Embedded xterm.js terminal running Claude Code +- OpenSCAD MCP server auto-configured — Claude can render, validate, and inspect models programmatically +- `CLAUDE.md` injects mandatory rules: never overwrite files, use colours, validate with MCP tools +- Auto-iteration: on render failure, ClawSCAD writes errors to `RENDER_ERRORS.md` and prompts Claude to fix them +- Dual terminal support (up to 2 Claude instances simultaneously) +- Multi-window support (up to 4 projects) **Export** -- STL, 3MF, and PNG export buttons in the header -- 3MF export preserves per-part colors (when OpenSCAD supports it) -- Print cost estimation with configurable infill, material, and cost/kg +- 3MF, STL, and PNG export — 3MF is the default because it is the only one that preserves per-part colour +- `--backend=Manifold` is used automatically when the resolved OpenSCAD supports it (~50× on boolean-heavy models) +- Print cost estimation (configurable infill, material, cost/kg) + +## Generation Pipeline (`claw-gen`) + +The **Generate** panel turns a sentence into a 3D sculpt: *text → candidate images → you pick one → +mesh → print-prep → a normal `.scad` checkpoint that `import()`s the mesh*. It is an optional +feature — ClawSCAD works fully without it, and the panel says so rather than failing quietly. + +It is driven entirely by an external CLI called `claw-gen`; the app hardcodes nothing about image or +mesh providers. Backend names, availability, and reasons come only from `claw-gen backends --json`. +The reference implementation is not publicly released, so the panel will report itself unconfigured +until you supply a CLI — the interface it has to satisfy is written up in +[docs/generation-pipeline.md](docs/generation-pipeline.md), and anything meeting it works. + +**Setting it up** + +1. Put a `claw-gen`-compatible CLI on your `PATH` and check that `claw-gen backends --json` runs in + a terminal. +2. In ClawSCAD, open the **Generate** panel and press **Locate claw-gen…** if it is not already on + your `PATH`. The path is remembered per user, not per workspace. +3. Press **Try again** — the panel switches to the prompt box once a backend reports `ok`. + +**What the three unconfigured states mean** + +| The panel says | What is actually true | What to do | +|---|---|---| +| *No generation pipeline configured* | No `claw-gen` on `PATH` and none located | Install one, or press **Locate claw-gen…** | +| *`claw-gen` failed to start* | It ran, but crashed or printed nothing parsable (its stderr is shown) | Fix the install or its `config.toml` | +| *No image backend available right now* | It ran fine, but every image backend reports unavailable — often `busy` under local memory pressure | Wait, or select an API backend instead of the local one | + +**While a job runs** the panel auto-expands and shows a four-stage stepper (images → mesh → prep → +checkpoint) with an elapsed timer; a failed stage stays visibly failed rather than silently +clearing. A mesh job takes roughly ten minutes, so completion also raises an OS notification when +the window is unfocused. + +**The result is a starting point, not a finished part.** A generated checkpoint is mesh-derived — +it is badged `GEN` in the checkpoint tree with a diamond node. Branch it and `difference()` your +parametric features into the import; never edit it in place. Anything tolerance-critical (snap +fits, threads, mating parts) should be modelled parametrically from the start. ## Install @@ -78,52 +132,76 @@ npm start ## Usage -1. Launch ClawSCAD — it creates a workspace at `~/clawscad-workspace/` -2. Claude Code starts in the terminal panel on the right -3. Tell Claude what to build: *"Make a gear with 20 teeth and a shaft hole"* -4. Claude writes a .scad file, ClawSCAD auto-renders it in the 3D viewport +1. Launch ClawSCAD — the workspace is created at `~/clawscad-workspace/`. To put it + somewhere else, set `CLAWSCAD_WORKSPACE`, pass a path (`clawscad D:\parts`), or use + *Open Workspace* in the app; the last workspace you opened is what the next launch uses. +2. Pick a print type in the **Make** panel, then describe what you want: + *"a gear with 20 teeth and a 5 mm shaft hole"* +3. Press **Make it**. (Claude Code runs in the terminal below — you can watch it work, or ignore it.) +4. Claude writes a `.scad` file — ClawSCAD auto-renders it in the viewport 5. If the render fails, ClawSCAD tells Claude to fix it automatically -6. Click any checkpoint in the History panel to go back and branch -7. Use the color swatches to try different colors instantly -8. Export to STL/3MF when you're happy with the design +6. Click any checkpoint in the Checkpoints panel to go back and branch — the strip above the tree always names the checkpoint your next change will branch from +7. Export to STL/3MF when done ## Keyboard Shortcuts | Shortcut | Action | |---|---| | `Ctrl+N` | New viewport (split view) | -| `Ctrl+F` | Find in source editor | -| `Ctrl+H` | Find and replace | | `F5` | Force re-render | -| `1`-`7` | Camera presets (when viewport focused) | +| `1`–`7` | Camera presets | | `R` | Reset view | | `F` | Zoom to fit | | `W` | Toggle wireframe | | `E` | Toggle edges | | `O` | Toggle ortho/perspective | -| `+`/`-` | Zoom in/out | -| `Escape` | Deselect part | ## Architecture ``` -ClawSCAD -├── main.js Electron main process — multi-window, project state, render queue, MCP client -├── renderer.js 3D viewport (three.js), terminal (xterm.js), editor (Monaco), checkpoint tree -├── preload.js IPC bridge between main and renderer -├── index.html Layout -├── style.css Dark theme -└── icon.png App icon +ClawSCAD/ +├── main.js Electron main — multi-window, project state, render queue, MCP client +├── renderer.js Three.js viewport, xterm.js terminal, Monaco editor, checkpoint tree +├── preload.js IPC bridge +├── index.html Layout +├── style.css Dark theme +├── main/ Per-feature main-process modules (register(ipcMain, deps)) +├── renderer/ Per-feature renderer modules, mounted through renderer/bus.js +├── presets/ Product data — print types (categories.json), intent presets, machine profile +├── web/ The browser port of the Make view (see web/README.md) +└── docs/ Design contracts each feature package was built against ``` -- **Rendering**: OpenSCAD CLI (`openscad -o output.3mf input.scad`), tries 3MF first (preserves colors), falls back to STL -- **3D engine**: three.js with MeshStandardMaterial, RoomEnvironment, EdgesGeometry, raycaster picking -- **Terminal**: xterm.js + node-pty, spawns `claude` directly -- **Editor**: Monaco with custom Monarch grammar for OpenSCAD -- **MCP**: Spawns `openscad-mcp-server` as a JSON-RPC subprocess for direct render/validate access +- **Rendering**: OpenSCAD CLI (`openscad -o output.3mf input.scad`), 3MF first, falls back to STL +- **MCP**: `openscad-mcp-server` subprocess, JSON-RPC, exposes render/validate/analyze tools to Claude + +## Configuration + +Nothing about your machine is compiled in. Every path ClawSCAD needs is either probed +or set by you — workspace location, OpenSCAD binary, Claude CLI, `claw-gen`, printer +profile, publish target. They are listed in one place: +**[docs/configuration.md](docs/configuration.md)**. + +## Run it in a browser + +`web/` serves the **Make** view over HTTP, running the same `renderer/studio.js` the +desktop app does against a `fetch` + `EventSource` shim. Useful for driving it from a +phone or a tablet on your own network. + +```sh +node web/build.js +node web/server.js --workspace ~/clawscad-workspace +``` + +It binds loopback and **has no authentication of its own** — see +[web/README.md](web/README.md) before exposing it to anything. + +## Releasing + +`npm run release` builds, verifies and publishes a release that the in-app updater can +actually apply. Forks must repoint `build.publish` in `package.json` first — +[docs/releasing.md](docs/releasing.md). ## License MIT — see [LICENSE](LICENSE). - -OpenSCAD (GPLv2+) and Claude Code (Apache 2.0) are launched as separate subprocesses. ClawSCAD does not incorporate or link against code from either project. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..185b99d --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,68 @@ +# Configuration + +Nothing about anyone's machine is compiled into ClawSCAD. Every external path is +either probed at run time or set by you, and every default is one that works on a +clean install of any of the three platforms. This is the list of places you can +plug your own setup in. + +Everything here is optional. The app runs with none of it set. + +## Environment variables + +| Variable | Used by | Default | What it does | +|---|---|---|---| +| `CLAWSCAD_WORKSPACE` | desktop + web | `~/clawscad-workspace` | Where projects live. On the desktop it is the launch default; a path argument (`clawscad D:\parts`) still wins over it. | +| `OPENSCAD_BINARY` | desktop + web | probed | Full path to the `openscad` executable. Set it when OpenSCAD is installed somewhere the probe misses, or when you want a Nightly build for `--backend=Manifold`. The desktop app's *Locate OpenSCAD…* sets it for the session only. | +| `CLAWSCAD_CLAUDE_BIN` | desktop + web | probed | Full path to the Claude Code CLI. The probe covers the standalone installer, `~/.local/bin`, `/usr/local/bin` and everything on `PATH` (`.exe` / `.cmd` on Windows); this is the escape hatch for anything else. | +| `CLAWSCAD_CLI` | web | probed on `PATH` | Full path to `claw-gen`. The desktop equivalent is *Locate claw-gen…*, which stores it in `pipeline-settings.json`. | +| `CLAWSCAD_STATE_DIR` | web | `/.clawscad-web` | The web port's `userData`: composer state, pipeline settings, and its own `presets/` overrides. | +| `CLAWSCAD_HOST` | web | `127.0.0.1` | Bind address. **Read `web/README.md` before changing this** — the server has no authentication of its own. | +| `PORT` | web | `8730` | Bind port. | +| `CLAWSCAD_DISABLE_CLAUDE` | desktop | — | `1` spawns a plain shell instead of Claude. Used by the test suite; also handy for a UI-only run. | +| `CLAWSCAD_TEST_PROFILE_ROOT` | desktop | — | Redirects `userData` so a test run never touches a real profile. Set by `playwright.config.js`; not for normal use. | + +The web server takes the same settings as flags, which win over the environment: +`--workspace`, `--state`, `--port`, `--host`, `--cli`, `--openscad`, `--claude`. + +## Your printer, your presets + +The product data in `presets/` ships with the app and is **overridable wholesale** +by a same-named file in the user data directory, under `presets/`: + +| File | What it holds | +|---|---| +| `machine.json` | Printer profiles — bed size, nozzle, material, tolerances, exclusion zones. The default is a Bambu P1S; change `active` or add your own machine. | +| `presets.json` | Intent presets — the modelling and print settings each intent implies. | +| `categories.json` | The print-type grid on the front door. | +| `tools.json` | The switchable field groups (Dimensions, Hardware, Fit & tolerance, …). | + +The user data directory is Electron's `userData`: + +| Platform | Path | +|---|---| +| Windows | `%APPDATA%\ClawSCAD` (`%APPDATA%\clawscad` when run from source) | +| macOS | `~/Library/Application Support/ClawSCAD` | +| Linux | `~/.config/ClawSCAD` | + +An override **replaces** the shipped file rather than merging into it, so copy the +whole file before editing one number. That is deliberate: a partial override that +silently dropped half the data shape would be a much worse failure than having to +copy a file. A missing override is normal; a corrupt one falls back to the shipped +default rather than failing the load. + +The web port reads the same `categories.json` and `tools.json` overrides out of +`$CLAWSCAD_STATE_DIR/presets/` — it calls `main/tools.js` and `main/categories.js` +directly rather than reimplementing the rules. + +## Generation pipeline (`claw-gen`) + +Optional — ClawSCAD works fully without it, and says so rather than failing +quietly. The app hardcodes nothing about image or mesh providers: backend names, +availability and reasons all come from `claw-gen backends --json`. Point at it with +*Locate claw-gen…* (desktop, remembered per user) or `CLAWSCAD_CLI` (web). + +## Releasing from a fork + +`package.json` → `build.publish` is both the upload target for +`npm run release` and the update feed every installed copy asks. Repoint it before +you publish anything — see [releasing.md](releasing.md). diff --git a/docs/generation-pipeline.md b/docs/generation-pipeline.md new file mode 100644 index 0000000..c6b93f4 --- /dev/null +++ b/docs/generation-pipeline.md @@ -0,0 +1,90 @@ +# The generation pipeline (`claw-gen`) + +The **Generate** panel turns a sentence into a 3D sculpt: *text → candidate images +→ you pick one → mesh → print-prep → a normal `.scad` checkpoint that `import()`s +the mesh*. + +ClawSCAD does not implement any of that. It shells out to an external CLI called +`claw-gen`, one short-lived child process per action — no daemon, no port, no +SDK. **The app hardcodes nothing about image or mesh providers**: backend names, +availability and the reason a backend is unavailable all come from the CLI. That +is deliberate, and it is what makes this document possible: anything that +satisfies the contract below works, whatever it runs underneath. + +The feature is optional. With no CLI present the panel says *"No generation +pipeline configured"* and everything else in ClawSCAD works normally. + +> **The reference implementation (`clawscad-gen`) is not publicly released.** If +> you are reading this because the Generate panel says it is unconfigured, that +> is why. The contract is documented here so the panel is an integration point +> rather than a dead end. + +## How the CLI is found + +1. The path stored by **Locate claw-gen…**, in + `/pipeline-settings.json` as `cliPath` (remembered per user, not per + workspace). +2. `claw-gen` on `PATH`. + +The web port uses `$CLAWSCAD_CLI` or `--cli` instead — a browser cannot open a +file picker on the server's filesystem, and exposing a remote one would be a bad +idea. + +Every invocation runs with **cwd set to the workspace**. + +## `claw-gen backends --json` + +Called to decide what the panel offers. It must print JSON on stdout; the **last +non-empty line** is parsed, so progress chatter above it is fine. + +```json +{ + "configured": true, + "backends": [ + { "kind": "image", "name": "local-sdxl", "ok": false, "reason": "busy" }, + { "kind": "image", "name": "some-api", "ok": true }, + { "kind": "mesh", "name": "local-mesh", "ok": true } + ] +} +``` + +- `configured: false` → the panel reports the pipeline as not configured. +- A backend with no `kind` is treated as an image backend. +- If no image backend has `ok: true`, the panel says *no image backend available + right now* and shows the reasons rather than failing ten minutes into a job. +- Unparsable output is reported as *`claw-gen` failed to start*, with its stderr + shown. A 15-second timeout applies. + +## Actions + +``` +claw-gen [args…] --json-events [--job ] +``` + +`` is one of `images`, `mesh`, `prep`, `checkpoint` — anything else is +refused before spawning. `--job` continues an existing job, which is how *More +like this* and *Refine…* accumulate rounds instead of starting a new job whose +candidate keys would collide with the previous one's. + +**stdout is NDJSON** — one JSON object per line. Every line is forwarded to the +UI as-is, so a CLI may emit whatever progress events it likes. Two fields are +interpreted: + +| Field | Meaning | +|---|---| +| `job` | Adopted as the current job id, on any event that carries it. | +| `event: "candidate"` with `path` | A generated candidate image. `path` must be absolute, and must live **inside the workspace** — the web port serves candidates only from within the job directory, so a `job_root` elsewhere renders every picture as "Picture unavailable", correctly but confusingly. The job directory is inferred as two levels up from the image (`/img/`). | + +**stderr** is forwarded verbatim to the panel's log. The **exit code** ends the +run; a non-zero code leaves the failed stage visibly failed rather than silently +clearing. + +One pipeline child runs at a time (per window on the desktop, per server in the +web port). A second start returns `already-running`. + +## What ClawSCAD does with the result + +A generated checkpoint is mesh-derived and badged `GEN` in the checkpoint tree +with a diamond node. Branch it and `difference()` your parametric features into +the import; never edit it in place. Anything tolerance-critical — snap fits, +threads, mating parts — should be modelled parametrically from the start. diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 0000000..66a41fa --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,117 @@ +# Releasing + +ClawSCAD auto-updates itself: `main/updater.js` checks GitHub releases, downloads +in the background, and installs on quit. That only works if the release on GitHub +is *complete*, and the ways it can be silently incomplete are the entire reason +this document and `scripts/release.ps1` exist. + +```powershell +npm run release # build, assert, publish, verify +npm run release:dry # build + assert only — publishes nothing +``` + +## Before your first release from a fork + +**Repoint the publish target.** `package.json` → `build.publish`: + +```json +"publish": [ + { "provider": "github", "owner": "levkropp", "repo": "ClawSCAD", "releaseType": "release" } +] +``` + +That block is two things at once: where `npm run release` uploads, and where every +installed copy asks for updates. A fork that leaves it pointing at someone else's +repo ships an app that updates itself *into a different fork's builds*. + +`electron-updater` resolves **the newest Release in a repo**, not the newest +release of a product. `appId` does not scope the feed — only the repo does. So two +products must never publish to one repo, however different their `appId`s are; the +users of each would be offered the other's installer. + +`gh` needs push access to that repo. The app itself needs no token: a public repo's +release assets are fetched anonymously. + +## Why not `electron-builder --publish always` + +It has a race that publishes a release with **no `latest.yml`**, which every +updater client is blind to — permanently, and silently. + +electron-builder creates one publisher per artifact (the `.exe` and its +`.blockmap`). Both ask "does this release exist?", both get "no", and both +`POST /releases` about 25 ms apart. GitHub creates one and rejects the other with +`422 Published releases must have a valid tag`. That rejection throws inside +`PublishManager.awaitTasks()` **before** `writeUpdateInfoFiles()` runs — and that +is the only place `latest.yml` is written. The release page then looks perfectly +normal: an installer, a version, notes. Only the updater knows it is dead. + +`scripts/release.ps1` removes the whole class of failure by decoupling the phases: + +1. **Build** with `--publish never`. `latest.yml` is still generated (its creation + was never gated on publishing), but nothing uploads, so nothing can race. +2. **Assert** the `.exe`, `.blockmap` and `latest.yml` all exist and describe the + same build — matching version, matching path, matching sha512. +3. **Publish** with `gh`, one sequential upload. +4. **Verify** by fetching the published `latest.yml` back over HTTP and checking it + reports this version, then HEAD-ing the installer. The failure being guarded + against is invisible, so the script never trusts its own upload. + +Two assertions in there are worth naming, because each has its own way of +404-ing the updater forever: + +- **`win.artifactName` is pinned** in `package.json`. Left default, electron-builder + writes a hyphenated URL into the manifest while naming the file with spaces. + Step 2 asserts `latest.yml`'s `path` still matches the built file's name. +- **sha512 is recomputed** from the actual installer. The updater refuses any + download whose hash does not match the manifest, so a stale manifest is not a + wrong update — it is no update at all. + +## The CI collision guard + +Step 2b refuses to publish under a tag that matches a workflow which uploads +`release/*` to a GitHub release. This is not hypothetical: + +1. `gh release create v0.6.1` **creates the tag**. +2. A `tags: ['v*']` trigger fires all the build workflows. +3. Minutes later CI uploads its *own* `ClawSCAD-Setup-0.6.1.exe` over yours — same + filename, so it replaces it — and nothing else. No `.blockmap`, no `latest.yml`. +4. It is a different binary, so its sha512 no longer matches the manifest you + published. +5. Every client is now permanently unable to apply that update, and the release + script already printed green, because CI had not finished when it read back. + +The workflows in `.github/workflows/` are therefore **build verification only** — +they build on `main` and on PRs, and upload artifacts to the Actions run, never to +a release. Exactly one publisher may own the update feed, and it is +`scripts/release.ps1`. + +If you do want CI to publish, the guard tells you the two ways out: drop the +release-upload step, or publish under a tag the workflows do not match +(`npm run release -- -Tag release-0.6.1`). Do **not** solve it by having CI upload +all three artifacts — `latest.yml` is per-platform, three parallel jobs would race +on it, and you would lose the read-back verification entirely. + +## The one-time manual install + +A version installed *before* the updater existed cannot update itself. Whoever +cuts the first release has to install that build by hand once, from the release +page. Every version after it arrives on its own. There is no way around this for +any app; it is a one-time cost per machine. + +## Release notes + +`scripts/release.ps1` pulls the body from `CHANGELOG.md` — specifically the +`## [x.y.z]` section matching `package.json`'s version, so keep that heading format +exact. No matching section is not fatal; you just get a bare `ClawSCAD ` +placeholder, which is usually not what you wanted to publish. + +## Build prerequisites (Windows) + +`npmRebuild` is `false` in the build config. `node-pty` is N-API and ships +`win32-x64` prebuilds, so there is nothing to rebuild, and a rebuild is where +Windows release builds usually die. If you re-enable it, expect two failures: + +- `NoDefaultCurrentDirectoryInExePath=1` in the environment makes winpty's gyp step + fail with `'GetCommitHash.bat' is not recognized`. Clear it for the build. +- MSBuild then wants `MSB8040: Spectre-mitigated libraries` — install them from the + Visual Studio Installer (Individual Components) for the v143 x64 toolset. diff --git a/docs/v04-guided-make-contracts.md b/docs/v04-guided-make-contracts.md new file mode 100644 index 0000000..48cecea --- /dev/null +++ b/docs/v04-guided-make-contracts.md @@ -0,0 +1,377 @@ +# ClawSCAD v0.4 — "Guided Make" contracts + +**Status: frozen.** Written by the foundation pass (W6-0) before any feature agent starts. +Three packages build against this document and never edit each other's files. + +## The product change in one paragraph + +Today the composer's first question is technical — *Part / Sculpt / Images*. A non-technical +user does not know which of those a phone stand is. v0.4 puts a **human** question first: +**"What are you making?"** — a grid of print types (Screws & hardware, Brackets & mounts, +Boxes & cases, Furniture, Structural, Replacement part, Models & figures, Home decor, +Toys & games, Something else). Picking one **automatically** sets the pipeline target and the +intent presets, so the technical controls become confirmations rather than decisions. The user +then types plain English and presses one button. + +What happens next depends on how **obvious** the request is: + +- **Obvious** (an M4 × 20 standoff, a 6 mm cable clip, a 3-shelf pin) → straight through to a + parametric `.scad`. No image step. One button, one model. +- **Not obvious** (an owl planter, a mid-century table leg, "the broken knob on my dryer") → + a **visual confirm** step first: generate a few reference images (or use the user's own photo) + and let them say *"yes, that's the thing"* before spending ten minutes on a mesh. + +The decision is always **shown and always overridable**. Never a black box. + +--- + +## Packages and file ownership (exclusive write) + +| # | Package | Agent | Owns | +|---|---|---|---| +| **P7** | Routing engine | `router` | `renderer/route.js`, `tests/route.js` (node harness), `main/categories.js` | +| **P8** | Guided UI | `guided` | `renderer/categories-ui.js`, `style-categories.css`, `tests/categories.spec.js` | +| **P9** | Confirm gate | `confirm` | `renderer/confirm-gate.js`, `style-confirm.css`, `tests/confirm.spec.js` | +| — | Foundation | (done) | `presets/categories.json`, `renderer/bus.js`, `renderer/composer.js`, `renderer/presets-ui.js` (one addition), `renderer.js`, `main.js`, `preload.js`, `index.html`, `package.json`, this doc | + +**Nobody edits a file they do not own.** If you need a foundation change, say so in your report +instead of making it. P8 and P9 may add CSS rules targeting *any* selector from their own +stylesheet — `style-categories.css` and `style-confirm.css` are linked after `style-composer.css`, +so equal-specificity rules win. + +--- + +## `presets/categories.json` — the taxonomy (foundation-owned, read-only to agents) + +```jsonc +{ + "version": 1, + "default": "hardware", // pre-selected id — never null, the grid always has a selection + "categories": [ + { + "id": "hardware", + "label": "Screws & hardware", + "glyph": "◎", // single character, drawn at 18px + "hint": "Bolts, spacers, standoffs, washers, adapters", + "examples": ["M4 × 20 mm hex standoff", "M3 nylon washer, 1 mm thick"], + "presets": ["fits-hardware"], // preset ids auto-applied on select (ctx.presets.setActive) + "target": "part", // target when the router resolves CONFIRM + "target_direct": "part", // target when the router resolves DIRECT (falls back to `target`) + "route": "direct", // 'direct' | 'confirm' | 'auto' + "confirm": "none", // 'none' | 'images' | 'photo' | 'both' + "bias": 25, // added to the obviousness score in 'auto' mode + "prompt": "CATEGORY: …", // prepended to the composed message on the part track + "ask": [ // ≤3 guided fields; ALL optional, never blocking + { "id": "thread", "label": "Thread", "kind": "choice", + "options": ["M2","M2.5","M3","M4","M5","M6","M8"], "unit": "" }, + { "id": "length", "label": "Length", "kind": "number", "unit": "mm", + "placeholder": "20" } + ] + } + ] +} +``` + +Field notes binding on all three packages: + +- `ask[].kind` is `"choice"` | `"number"` | `"text"`. A `choice` renders a ` +
+ +
+
+
+ + +
+ + + + +
+
+ + +
-
- - History + + Checkpoints +
-
+ +
+ -