# Q CadLab — Development Specification

**A specification for an autonomous Claude Code session.**

Working title: *Q CadLab* (rename freely). A local, browser-based learning
platform that lets a single user discover and master CadQuery — the Python
dialect on top of the OpenCASCADE geometry kernel.

This document is self-contained. Hand it to Claude Code in an empty folder as
your first message; the agent should be able to build, install, run and
verify the application end-to-end without further input.

---

## 1. Context and goal

Q CadLab is a **local learning environment for CadQuery**. The user writes
CadQuery code in a browser editor; a Python web server executes the code
using CadQuery (built on top of the OpenCASCADE kernel) and returns the
resulting geometry to a three.js viewer. The user can load and save scripts
locally, and export the current model to STEP.

It is explicitly a **teaching platform**: alongside the free-form editor, the
application ships with a progressive set of worked examples. A new user can
walk from a first `box()` to selectors, boolean operations,
`revolve` / `sweep` / `loft` and parametric profiles, one step at a time.

**Target user.** One technical user, running locally on their own machine —
no multi-user setup, no public hosting. This simplifies security and
deployment.

**Positioning.** Q CadLab is a stand-alone tool, but it is part of a family
of small research applications. The CadQuery competence and the STEP-export
pipeline it exercises are directly relevant for downstream work on frame
structures and variable-detail profile rendering.

---

## 2. Technology choices

| Component       | Technology                              | Rationale                                              |
|-----------------|-----------------------------------------|--------------------------------------------------------|
| Web server      | Python 3.11+ / FastAPI + uvicorn        | Async, lightweight, consistent with the broader stack  |
| CAD kernel      | CadQuery (≥ 2.4) → OpenCASCADE via OCP  | BREP modelling, reliable STEP export, `tessellate()`   |
| 3D viewer       | three.js (r0.160+) + OrbitControls      | The standard for web 3D; **served locally**            |
| Code editor     | CodeMirror 6                            | Python syntax, light footprint; **served locally**     |
| Frontend        | Vanilla JS (ES modules), HTML, CSS      | No build step                                          |
| Transport       | HTTP/JSON (fetch)                       | Simple; realtime not required                          |

**No build step, no runtime CDN dependency.** The frontend is a set of static
files served by FastAPI. All third-party libraries (three.js including
`OrbitControls`, CodeMirror 6) are **stored locally** in the static-vendor
folder and served by the Python server. After the one-time setup the
application must run fully **offline** — fitting a self-hosted layout. The
libraries are fetched once during Phase 0 (see §4.1 and Phase 0).

---

## 3. Architecture

```
Browser (static SPA)                   Python web server (FastAPI)
┌───────────────────────────┐          ┌──────────────────────────────┐
│  Toolbar                  │          │  GET  /            index.html │
│  ┌─────────┬───────────┐  │  fetch   │  GET  /static/*               │
│  │ three.js│ CodeMirror │  │ ───────▶ │  POST /api/render             │
│  │ viewer  │  editor    │  │ ◀─────── │  POST /api/export/step        │
│  └─────────┴───────────┘  │   JSON   │  GET  /api/examples           │
│  Console / output pane    │          │  GET  /api/examples/{id}      │
│  Examples pane            │          │  GET  /api/files              │
└───────────────────────────┘          │  GET  /api/file?name=         │
                                        │  POST /api/file               │
                                        └───────────────┬──────────────┘
                                                        │
                                              CadQuery + OpenCASCADE
                                       (execute → tessellate / STEP export)
```

The server is **stateless per request**, except for the workspace folder
(user-saved scripts on disk).

---

## 4. File layout

The session must create exactly this structure:

```
q-cadlab/
├── server/
│   ├── __init__.py
│   ├── main.py              # FastAPI app, routes, static files
│   ├── cq_runner.py         # execute CadQuery code, tessellate, STEP export
│   └── examples_loader.py   # load and index the worked examples
├── static/
│   ├── index.html
│   ├── css/
│   │   └── style.css
│   ├── js/
│   │   ├── app.js           # init, toolbar actions, split layout
│   │   ├── api.js           # fetch wrappers around the backend
│   │   ├── editor.js        # CodeMirror 6 setup
│   │   ├── viewer.js        # three.js viewer (scene, camera, mesh build)
│   │   └── examples.js      # examples panel and explanation rendering
│   └── vendor/              # locally served libraries (see 4.1) — NOT via CDN
│       ├── three/           # three.module.js + OrbitControls.js (+ any extras)
│       └── codemirror/      # CodeMirror 6 bundle + Python language
├── scripts/
│   └── fetch_vendor.py      # one-time: fetches vendor libraries (Phase 0)
├── examples/
│   ├── examples.json        # index: id, title, category, file, short description
│   ├── 01_first_box.py
│   ├── 02_basic_shapes.py
│   ├── ...                  # see §8
│   └── 12_parametric_profile.py
├── workspace/               # user-saved scripts (runtime; start empty with .gitkeep)
├── requirements.txt
├── run.sh                   # convenience: activate venv + start uvicorn
├── .gitignore               # workspace/* (except .gitkeep), __pycache__, venv
└── README.md
```

`requirements.txt`:

```
cadquery>=2.4
fastapi>=0.110
uvicorn[standard]>=0.29
```

> **Note for the session.** Installing `cadquery` pulls in the OCP wheels
> (OpenCASCADE). They are large and the download can be slow. Plan time for
> this and verify the install with a minimal `import cadquery` test before
> building anything on top.

### 4.1 Locally served libraries (static vendor folder)

The frontend may **never reach out to a CDN at runtime**. three.js and
CodeMirror 6 are stored locally in the static-vendor folder and served by
FastAPI (the `static/` mount covers it). The application must work fully
offline once setup is complete.

**The `scripts/fetch_vendor.py` helper** is a one-time utility that fetches
the required files and places them in the vendor folder. It must be
**idempotent** (skip files that already exist) and report clearly which
artefacts it fetched. What to download:

- **three.js** (r0.160+): at minimum `three.module.js` and `OrbitControls.js`.
  Source: the official `three` npm distribution or the GitHub release
  (`build/three.module.js` and `examples/jsm/controls/OrbitControls.js`).
  Note: `OrbitControls.js` imports from `three` — rewrite the import path so
  it points to the local `three.module.js`, or provide a tiny import map
  that resolves only to local paths (no external URLs).
- **CodeMirror 6**: the editor with the Python-language extension. Because
  CodeMirror 6 is split across many tiny ES modules, a pre-built bundle is
  the simplest option — for example a `codemirror` bundle that exports
  `EditorView`, `basicSetup` and the Python language. The script may fetch a
  ready-made ESM bundle, or generate a minimal bundle with esbuild. Document
  the choice in the README.

**Allowed sources for the fetch script.** The official npm registry
(`registry.npmjs.org`) or GitHub releases. The script runs on the developer
machine with internet; the **application itself** stays offline after that.

**Acceptance criterion.** After `fetch_vendor.py` runs, the vendor folder
contains every library, and the application loads correctly with the network
connection disabled. No `import` in the frontend may resolve to an
`http(s)://` URL outside the page origin.

> **Note for the session.** Pin the library versions (record the exact
> versions in the README). If a specific variant fails to download, choose a
> working equivalent and document the substitution — the hard requirement is
> a working offline application, not a specific version number.

---

## 5. Backend specification

### 5.1 `server/main.py`

- FastAPI app that serves `static/index.html` on `GET /` and mounts the
  `static/` folder at `/static`.
- CORS not needed (same origin).
- Start via `run.sh` on `http://127.0.0.1:8000` (host pinned to `127.0.0.1` —
  this is a local tool). If port 8000 is taken locally, choose a stable
  alternative and document it.
- All `/api/*` routes below.

### 5.2 Endpoints

**`POST /api/render`** — execute CadQuery code and return tessellated
geometry.

```jsonc
// request
{ "code": "import cadquery as cq\nresult = cq.Workplane('XY').box(10, 10, 10)" }

// response (success)
{
  "ok": true,
  "meshes": [
    {
      "name": "result",
      "vertices": [x0, y0, z0, x1, y1, z1, ...],   // flat float array
      "triangles": [i0, i1, i2, ...],              // flat int array
      "color": [0.36, 0.52, 0.78]                  // RGB 0..1
    }
  ],
  "log": "script stdout",
  "error": null,
  "render_ms": 142
}

// response (error)
{ "ok": false, "meshes": [], "log": "", "error": "Traceback (most recent call last): ...", "render_ms": 0 }
```

**`POST /api/export/step`** — same request body as `/api/render`. Executes
the code, exports the result to STEP, and returns the file as a download
(`Content-Disposition: attachment; filename="model.step"`, media type
`application/step` or `model/step`). On error: HTTP 400 with
`{ "error": "..." }`.

**`GET /api/examples`** — returns the index from `examples/examples.json`: a
list of `{ id, title, category, description }`.

**`GET /api/examples/{id}`** — returns one example:
`{ id, title, category, description, explanation_md, code }`.
`explanation_md` is Markdown explanation text (see §8).

**`GET /api/files`** — list of `*.py` filenames in the workspace folder.

**`GET /api/file?name=<name>`** — content of one file from the workspace
folder. Prevent path traversal: accept only a bare filename, force the `.py`
extension, reject `/`, `\`, `..` and the null byte.

**`POST /api/file`** —

```jsonc
{ "name": "my_script.py", "code": "..." }
```

Writes to the workspace folder. Same name validation. Returns
`{ "ok": true, "name": "my_script.py" }`.

### 5.3 `cq_runner.py` — execution and tessellation

This is the technically sensitive piece. Implement it like this.

**Execution model.** Run user code with `exec()` in a prepared namespace
containing:

- `cadquery` and the alias `cq`;
- a `show_object(obj, name=None, options=None)` function that collects shown
  objects in a list (the CadQuery convention, identical to CQ-editor's
  behaviour). `options` may include a `color`.

After `exec()`, determine what to render:

1. all objects passed to `show_object(...)`; otherwise
2. a variable named `result`; otherwise
3. the last-defined variable that is a `cadquery.Workplane` or
   `cadquery.Shape`.

Capture stdout (redirect to a buffer) and return it as `log`. Catch any
exception and return the full traceback as `error` (with `ok: false`).

**Tessellation.** For every shown object:

- if it is a `Workplane`, take `obj.vals()` (a list of `Shape`s); otherwise
  use the `Shape` directly.
- per `Shape`: `vertices, triangles = shape.tessellate(tolerance=0.1, angularTolerance=0.2)`.
  `vertices` is a list of `Vector` (use `.x, .y, .z`); `triangles` is a list
  of index triplets.
- merge multiple solids under one shown object into a single mesh with
  correct index offsets.
- result per shown object: one mesh record in the JSON response.

Reference skeleton (the session may refine this; this is the intended
approach):

```python
import io, sys, traceback, time
import cadquery as cq

def run_code(code: str):
    shown = []
    def show_object(obj, name=None, options=None):
        shown.append({"obj": obj, "name": name, "options": options or {}})

    ns = {"cadquery": cq, "cq": cq, "show_object": show_object}
    buf = io.StringIO()
    t0 = time.perf_counter()
    old_stdout = sys.stdout
    try:
        sys.stdout = buf
        exec(compile(code, "<cadlab>", "exec"), ns)
    except Exception:
        sys.stdout = old_stdout
        return {"ok": False, "meshes": [], "log": buf.getvalue(),
                "error": traceback.format_exc(), "render_ms": 0}
    finally:
        sys.stdout = old_stdout

    targets = shown
    if not targets:
        candidate = ns.get("result")
        if candidate is None:
            for v in reversed(list(ns.values())):
                if isinstance(v, (cq.Workplane, cq.Shape)):
                    candidate = v
                    break
        if candidate is not None:
            targets = [{"obj": candidate, "name": "result", "options": {}}]

    meshes = []
    for i, t in enumerate(targets):
        obj = t["obj"]
        shapes = obj.vals() if isinstance(obj, cq.Workplane) else [obj]
        verts, tris = [], []
        for sh in shapes:
            if not isinstance(sh, cq.Shape):
                continue
            v, f = sh.tessellate(0.1, 0.2)
            offset = len(verts) // 3
            for p in v:
                verts += [p.x, p.y, p.z]
            for a, b, c in f:
                tris += [a + offset, b + offset, c + offset]
        color = t["options"].get("color") or [0.36, 0.52, 0.78]
        meshes.append({"name": t["name"] or f"object_{i}",
                       "vertices": verts, "triangles": tris, "color": color})

    return {"ok": True, "meshes": meshes, "log": buf.getvalue(),
            "error": None, "render_ms": int((time.perf_counter() - t0) * 1000)}
```

**STEP export.** Collect the same target objects. For one object:
`cq.exporters.export(obj, path, exportType="STEP")`. For multiple objects:
combine into `cq.Compound.makeCompound([...])` of the underlying shapes and
export that. Write to a temporary file and stream it back.

**Security note.** `exec()` of arbitrary code is intentional: this is a
**local tool for one trusted user**. The server binds to `127.0.0.1`. Add a
short warning in the README that the application must not be hosted publicly.
No further sandboxing is needed.

---

## 6. Frontend specification

### 6.1 Layout

One page, three horizontal zones from top to bottom:

```
┌──────────────────────────────────────────────────────────────┐
│  TOPBAR   Q CadLab            [examples] [about]             │  dark
├──────────────────────────────────────────────────────────────┤
│  TOOLBAR  [New][Open ▼][Save][Save as][Upload]               │  light
│           [▶ Run]  [Export STEP]            render: 142ms    │
├───────────────────────────────┬──────────────────────────────┤
│                               ║                              │
│   VIEWER (three.js)           ║   EDITOR (CodeMirror 6)       │
│   - grid + axes               ║   - Python syntax            │
│   - mini-toolbar:             ║   - line numbers             │
│     [reset view][grid]        ║                              │
│     [edges][wireframe]        ║                              │
│                               ║                              │
│        ◀── draggable splitter (║) ──▶                        │
├───────────────────────────────┴──────────────────────────────┤
│  CONSOLE (collapsible) — stdout, errors, render time          │
└──────────────────────────────────────────────────────────────┘
```

- **Splitter.** A vertical divider between the viewer (left) and editor
  (right), **draggable** with the mouse (handle the
  `mousedown`/`mousemove`/`mouseup` cycle yourself). Minimum panel width
  ≈ 280 px. Viewer left, editor right.
- **Console.** At the bottom, collapsible. Shows `log`, errors (red,
  monospace) and the render time. On error, auto-open.
- **Examples.** Open a panel or overlay with the examples library (see
  §6.4).
- The layout fills the viewport exactly (`100vh`); no page scroll. Only the
  editor and console scroll internally.

### 6.2 Viewer (`viewer.js`)

- `THREE.Scene`, `PerspectiveCamera`, `WebGLRenderer` (antialias),
  `OrbitControls`.
- Light background, consistent with the brand (see §7).
- Lighting: one `AmbientLight` plus one `DirectionalLight`.
- Helpers: `GridHelper` on the XY plane and `AxesHelper`. CadQuery uses
  Z-up; orient the viewer accordingly (camera `up = (0,0,1)`, or rotate the
  scene group). Be consistent.
- On every render response: dispose of old meshes, then for each mesh record
  build a `BufferGeometry` from the `vertices` and `triangles`, compute
  normals with `computeVertexNormals()`, and apply a `MeshStandardMaterial`
  in the mesh colour.
- **Edges.** On by default — `EdgesGeometry` with a threshold angle of
  ~20°, dark line colour, for a clean CAD look.
- **Auto-fit.** After loading, compute the bounding box and fit the camera
  and `OrbitControls.target` to it.
- Mini-toolbar: *reset view*, *grid on/off*, *edges on/off*,
  *wireframe on/off*.
- Display a tidy message in the canvas while nothing has been rendered yet.

### 6.3 Editor (`editor.js`)

- CodeMirror 6 with the Python-language extension, line numbers,
  current-line highlight, and a theme that matches the brand.
- Keyboard shortcut **Ctrl/Cmd + Enter** = *Run*.
- The editor starts with a working example loaded (the content of
  `examples/01_first_box.py`).

### 6.4 Examples panel (`examples.js`)

- Fetch the index via `GET /api/examples`.
- Display the examples as **cards**, grouped by category (brand style:
  card with title + short description — see §7).
- Click a card → `GET /api/examples/{id}` → load `code` into the editor,
  render `explanation_md` to HTML in an explanation pane next to or above
  the editor, and run the example immediately.
- For Markdown rendering, use a small **hand-written** Markdown-to-HTML
  converter (headings, paragraphs, inline code, code blocks, lists are
  enough). If you really want a library, vendor it locally — no CDN at
  runtime (see §4.1).

### 6.5 Toolbar actions (`app.js` + `api.js`)

| Button         | Behaviour                                                                                                |
|----------------|----------------------------------------------------------------------------------------------------------|
| New            | Clear the editor (confirm if there are unsaved changes).                                                 |
| Open ▼         | Dropdown of `GET /api/files`; selection loads via `GET /api/file?name=`.                                 |
| Save           | `POST /api/file` with the current filename. If still unnamed → behave like *Save as*.                    |
| Save as        | Prompt for a name, then `POST /api/file`.                                                                |
| Upload         | `<input type="file">`; read the file client-side with `FileReader` and load the text into the editor.    |
| Download       | (optional, alongside Upload) make a client-side `Blob` of the editor contents and trigger a download.    |
| ▶ Run          | `POST /api/render`; on success update the viewer + console log; on error open the console with the trace.|
| Export STEP    | `POST /api/export/step`; on success serve the file as a download; on error display the console.          |

This covers "load and save a text file" two ways: via the server-side
workspace folder (Open/Save) and via the local disk (Upload/Download).

---

## 7. Visual style

Q CadLab is not a marketing site but an application; take a **visual
language** suited to a tool layout, kept calm and professional.

**Concrete guidelines:**

- **Colours.** Light working background (`#f4f5f7`), white panels (`#ffffff`),
  dark topbar (`#15181f`), dark text (`#1a1d24`), blue accent (`#2563eb`),
  light-blue signal colour (`#add8e6`), soft borders (`#e2e5ea`). Render the
  3D model in a blue tint with lighter edges.
- **Typography.** Sans-serif system font stack for the UI; monospace for the
  editor and console.
- **Cards.** White card, subtle border, light shadow, rounded corners (~8 px)
  — for the examples library.
- **Buttons.** Calm and tidy; primary action (*Run*) in the accent blue,
  everything else neutral.
- **Tone.** Quiet, functional, no decoration for its own sake. Consistent
  spacing.

Capture the style in `css/style.css` with CSS custom properties
(`:root { --accent: #2563eb; ... }`) so it stays easy to adjust.

---

## 8. Learning content — the examples library

Create **at least 12 examples** in `examples/`, in a progressive order. Each
example is a `.py` file with working CadQuery code; the explanation lives in
`examples.json` (under `explanation_md`) or in a sibling `.md` file — pick
one approach and document it.

Every example must contain: correct, **directly runnable** CadQuery code,
generous comments inside the code itself, and a clear `explanation_md` that
explains *which CadQuery concept* is on stage and *why*. Use either
`show_object(...)` or a `result` variable, consistently across examples.

Suggested progression (category → example):

| #  | Category         | Example                  | Core concept                                              |
|----|------------------|--------------------------|-----------------------------------------------------------|
| 01 | Basics           | First box                | `Workplane`, `box()`, the `result` convention             |
| 02 | Basics           | Basic shapes             | `sphere()`, `cylinder()`, dimensions                      |
| 03 | 2D sketches      | Sketching a profile      | `moveTo`, `lineTo`, `close`, `extrude`                    |
| 04 | 2D sketches      | Arcs and splines         | `threePointArc`, `spline`                                 |
| 05 | Selectors        | Selecting faces          | `faces(">Z")`, `workplane()` on a face                    |
| 06 | Selectors        | Selecting edges          | `edges("|X")`, `edges(">Z")`                              |
| 07 | Operations       | Holes                    | `hole()`, `cboreHole`, multi-hole through the stack       |
| 08 | Operations       | Fillets and chamfers     | `fillet()`, `chamfer()`                                   |
| 09 | Operations       | Shell                    | `shell()` — hollow part                                   |
| 10 | Boolean          | Combining solids         | `union`, `cut`, `intersect`                               |
| 11 | Advanced         | Revolve / sweep / loft   | rotation- and path-based volumes                          |
| 12 | Parametric       | Parametric profile       | top-of-file variables, reusable cross-section, STEP-ready |

The `examples.json` index contains, per example, at minimum: `id`, `title`,
`category`, `description` (one sentence), and a reference to the code file.
Example 12 deliberately points toward the kind of profile sketching needed
for frame structures, and encourages exporting the result to STEP.

---

## 9. Development phases

Work the phases **sequentially**. Mark a phase complete only when all
acceptance criteria are met. Keep every phase description visible in the
project documentation after completion.

### Phase 0 — Project skeleton

**Deliverables.** Folder layout (§4), `requirements.txt`, virtual
environment, `run.sh`, `.gitignore`, a stub README, `scripts/fetch_vendor.py`
and the fetched libraries in the static-vendor folder (§4.1).

**Acceptance.** `pip install -r requirements.txt` succeeds;
`python -c "import cadquery"` runs without error;
`python scripts/fetch_vendor.py` populates the vendor folder with three.js
and CodeMirror 6; running the script again is a no-op (idempotent).

### Phase 1 — Backend skeleton and CadQuery runner

**Deliverables.** `cq_runner.py` (execute, tessellate, STEP export),
`main.py` with all `/api/*` routes, `examples_loader.py`.

**Acceptance.** `POST /api/render` with a simple box returns a valid
`meshes` response with non-empty `vertices` and `triangles`. A script with
an error returns `ok: false` plus a traceback. `POST /api/export/step`
yields a valid STEP file (verifiable: the file begins with `ISO-10303-21`).

### Phase 2 — Frontend skeleton and layout

**Deliverables.** `index.html`, `style.css`, `app.js` with the three zones,
a draggable splitter, a collapsible console. Brand style from §7 applied.

**Acceptance.** The page loads via `GET /`; topbar, toolbar, split window
and console are visible; the splitter is draggable and panels resize
correctly; the layout fills the viewport without a page scroll.

### Phase 3 — Editor + viewer + render pipeline

**Deliverables.** `editor.js` (CodeMirror 6), `viewer.js` (three.js),
`api.js`. *Run* connects editor → backend → viewer.

**Acceptance.** Running code in the editor displays the 3D model in the
viewer; OrbitControls works; auto-fit centres the model; edge display
works; a bad script shows the traceback in the console; Ctrl/Cmd+Enter
triggers Run.

### Phase 4 — Open, save, STEP export

**Deliverables.** All toolbar actions from §6.5 are functional.

**Acceptance.** Saving a script in the workspace folder and reopening it
yields identical content; uploading a local `.py` file fills the editor;
*Export STEP* downloads a valid STEP file; name validation prevents path
traversal.

### Phase 5 — Learning platform

**Deliverables.** ≥ 12 examples plus `examples.json`; examples panel with
cards per category; explanation display (Markdown → HTML).

**Acceptance.** Every example loads, runs correctly, and renders without
error; the explanation text appears; the progression is visibly graded
from simple to advanced.

### Phase 6 — Finishing

**Deliverables.** Robust error handling everywhere, tidy empty states, a
complete `README.md` (install incl. `python scripts/fetch_vendor.py`, start,
usage, offline operation, security warning about local-only use, a brief
CadQuery intro), code comments.

**Acceptance.** The full acceptance test in §10 passes; the README walks a
new user from zero to a rendered model.

---

## 10. Acceptance test (end-to-end)

Execute this manual test in full at the end:

1. `bash run.sh` starts the server without errors;
   `http://127.0.0.1:8000` shows the application.
2. The editor contains a working starter example on launch; *Run* shows a
   3D model.
3. OrbitControls (rotate, pan, zoom), *reset view*, *grid*, *edges* and
   *wireframe* all work.
4. A script with a syntax error opens the console with a readable
   traceback; a subsequent correct script restores the view.
5. *Save as* → name → file appears in *Open ▼*; reopening restores the same
   content.
6. *Upload* a local `.py` file fills the editor.
7. *Export STEP* downloads a file that begins with `ISO-10303-21` and opens
   in mainstream CAD software (Fusion / Inventor).
8. The examples panel shows cards per category; each of the ≥ 12 examples
   loads, renders, and shows its explanation.
9. The splitter is draggable; the console is collapsible; the layout
   remains intact under window resizing.
10. **Offline test.** With the network connection disabled, the application
    still loads fully — three.js and CodeMirror 6 come from the static
    vendor folder. The browser's network tab shows no external
    (`http(s)://`) requests.
11. The README covers install (including `fetch_vendor.py`), start, and
    usage, with the local-use security warning.

---

## 11. Out of scope (possible later extensions)

Do not build in this session; note them in the README as future directions:

- Export to STL / glTF alongside STEP.
- Variable levels of detail (LOD) for tessellation, configurable in the UI.
- Server-side export of sharp-edge polylines for an even cleaner CAD look.
- Measurement and section tools in the viewer.
- Autocompletion / CadQuery API hints in the editor.
- Integration with broader profile libraries.

---

## 12. Working method for the autonomous session

- Work Phase 0 → 6 in order; close each phase against its acceptance
  criteria before moving on.
- Keep the frontend **buildless and CDN-free at runtime**: ES modules,
  three.js and CodeMirror 6 served locally from the static-vendor folder
  (§4.1). Any import maps may only resolve to local paths within the
  page origin.
- Test `cq_runner.py` in isolation (a small script or `python -m`) before
  the frontend integration — this is the largest technical risk.
- Write the example code yourself and **verify** that each example
  actually renders; do not ship examples that have not been run.
- Commit logically per phase if a git repository is desired.
- Document any deviation from this specification explicitly in the README.
