# Implementing Extensions

Extensions let you add new geometric functions to the editor from a hosted JavaScript module, without touching the core codebase. This document covers everything needed to write and publish an extension.

## Quick Start

A template repository is available at [https://github.com/TinyVolt/tv-extension-template](https://github.com/TinyVolt/tv-extension-template) that you can fork to quickly set up your own extension repository.

---

## Table of Contents

1. [Overview](#1-overview)
2. [The ExtensionDef Interface](#2-the-extensiondef-interface)
3. [The `compute` Function Contract](#3-the-compute-function-contract)
4. [Input and Output Types](#4-input-and-output-types)
5. [Auxiliary Nodes](#5-auxiliary-nodes)
6. [Broadcasting](#6-broadcasting)
7. [Differentiable Extensions](#7-differentiable-extensions)
8. [Module Format (Direct .mjs / .js)](#8-module-format-direct-mjs--js)
9. [Manifest Format (.json)](#9-manifest-format-json)
10. [Hosting and Security Requirements](#10-hosting-and-security-requirements)
11. [Loading Extensions in the Editor](#11-loading-extensions-in-the-editor)
12. [Worked Examples](#12-worked-examples)

---

## 1. Overview

Extensions are standard ES modules hosted on the web. When loaded, each exported object that has a `keyword` string and a `compute` function is detected as an extension and registered as a new command in the editor.

The extension's `compute` function runs inside a **sandboxed Web Worker**. It never touches the editor's internal state directly. Inputs arrive as plain objects; outputs must also be plain objects. 

### What You Can Do

- Create new declarative geometric functions (point constructions, transformations, calculations)
- Produce auxiliary nodes (e.g. intermediate points and lines along with a main shape)
- Work with any node type: `Scalar`, `Point`, `Line`, `Circle`, `Polygon`, etc.

### What You Cannot Do

- Perform network requests (fetch, XHR, WebSocket, EventSource are blocked inside the worker)
- Access the DOM or the editor's internal representation.
- Call `importScripts` or load other modules at compute time
- Use imperative/animated functions — those require direct internal state access (not available in workers)

---

## 2. The ExtensionDef Interface

Each exported object in your module must conform to this shape:

```js
{
  name: string,         // PascalCase display name shown in the UI
  keyword: string,      // kebab-case command name (e.g. 'my-function')
  parameters: UserParam[],
  outputType: string,   // The primary output node type (e.g. 'Point', 'Scalar', 'Line', 'Any')
  compute: (inputs) => Record<string, any>,
}
```

Where each `UserParam` is:

```js
{
  argName: string,                  // Name used as the key in the compute inputs object
  type: string,                     // 'Scalar' | 'Point' | 'Line' | 'Circle' | 'Polygon' | etc.
  defaultValue: string | number,    // Default value if the user omits this argument
  variadic: boolean,                // If true, this last parameter captures all remaining args
}
```

---

## 3. The `compute` Function Contract

`compute` receives a single `inputs` object and must return a blueprint object.

```js
compute(inputs) {
  // inputs is Record<string, any> keyed by argName
  // return Record<string, any> blueprint
}
```

### Inputs

Each key in `inputs` corresponds to the `argName` of a parameter. The value is a plain representation of the input node:

| Node type | Passed as |
|-----------|-----------|
| `Scalar` | A plain number |
| `Point` | `{ type: 'Point', x: number, y: number }` |
| `Line` | `{ type: 'Line', p1: { type: 'Point', x, y }, p2: { type: 'Point', x, y } }` |
| `Circle` | `{ type: 'Circle', center: { type: 'Point', x, y }, radius: number }` |
| Other nodes | Recursively plain objects with all numeric fields as plain JS numbers |

### Outputs

Return a `Record<string, any>` where:

- `'main'` is required — it becomes the primary output node.
- Any other key creates an **auxiliary node** saved alongside main.

Use plain objects with plain JS numbers:

| To produce | Return |
|---|---|
| Scalar | `{ type: 'Scalar', value: number }` |
| Point | `{ type: 'Point', x: number, y: number }` |
| Line | `{ type: 'Line', p1: { type: 'Point', x, y }, p2: { type: 'Point', x, y } }` |
| Circle | `{ type: 'Circle', center: { type: 'Point', x, y }, radius: { type: 'Scalar', value: number } }` |

**Critical**: all values must be plain JS numbers — no internal types, no Proxy objects, no class instances. Everything must survive `postMessage` serialization.

### Returning a Degenerate (Undefined) Result

If the computation is undefined (e.g. parallel lines, non-intersecting circles), return `{ main: { type: 'Dummy' } }`. The editor will render nothing for that node.

---

## 4. Input and Output Types

### Reading Scalar inputs

Scalars arrive as plain numbers. Use them directly:

```js
const a = inputs.a;
const b = inputs.b;
const result = a + b;
```

### Reading Point inputs

```js
const { x, y } = inputs.center; // plain numbers
```

### Reading Line inputs

```js
const { p1, p2 } = inputs.line;
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
```

---

## 5. Auxiliary Nodes

Return additional keys in the output to produce auxiliary nodes. These are auto-named and cleaned up automatically when the main node is deleted.

```js
compute({ p1, p2 }) {
  const mx = (p1.x + p2.x) / 2;
  const my = (p1.y + p2.y) / 2;
  const midpoint = { type: 'Point', x: mx, y: my };

  return {
    midpoint,                         // auxiliary node
    main: { type: 'Line', p1, p2 },   // primary output
  };
}
```

---

## 6. Broadcasting

**Broadcasting is handled automatically.** If any input is an array, the adapter calls your `compute` once per element and assembles the results into an output array. You do not need to handle array inputs in your `compute` function.

---

## 7. Differentiable Extensions

`\backprop` traces a JAX gradient through every node on the value path. Extension `compute` runs in a Web Worker and returns asynchronously, so JAX cannot trace through it directly. To make an extension differentiable, write its arithmetic with the **symbolic math builders** injected into the worker scope. Each builder records a graph node instead of computing a number; the host evaluates the recorded graph synchronously under either concrete values (forward) or JAX tracers (backprop).

### The rule

- For any numeric output you want to be differentiable, build it with the symbolic builders below — not with `+`, `-`, `*`, `/`, `Math.sin`, etc.
- Raw JavaScript arithmetic still works for forward evaluation, but the result will not carry gradient information. `\backprop` through such a node will throw a clear error.
- You can freely mix the two: a `Point` whose `x` is symbolic and `y` is a literal `0` is differentiable in `x` and constant in `y`.

### Available builders

All builders are globals in the worker (no import needed). Each returns an opaque symbolic value that flows through other builders.

| Category | Builders |
|---|---|
| Arithmetic | `add(a,b)`, `sub(a,b)`, `mul(a,b)`, `div(a,b)`, `neg(a)`, `mod(a,b)` |
| Powers / roots | `pow(a,b)`, `sqrt(a)` |
| Trig | `sin(a)`, `cos(a)`, `tan(a)`, `asin(a)`, `acos(a)`, `atan(a)`, `atan2(y,x)` |
| Log / exp | `log(a)`, `log10(a)` |
| Rounding / sign | `abs(a)`, `sign(a)`, `floor(a)`, `ceil(a)`, `round(a)` |
| Min / max | `minimum(a,b)`, `maximum(a,b)` |
| Comparisons (return 0 or 1) | `lt(a,b)`, `le(a,b)`, `gt(a,b)`, `ge(a,b)`, `eq(a,b)`, `ne(a,b)` |
| Branching | `where(cond, a, b)` |

### Branching inside differentiable code

`if (x > 0)` works directly on input numbers — inputs arrive as branchable `Number`-instances, so a literal `if`/`else` on them is fine. The IR is rebuilt from scratch on every forward call, so a branch flip just reshapes the graph at the next evaluation.

If you need a smooth selector that JAX can differentiate through, use `where`:

```js
const safe = where(gt(d, 0.001), div(1, d), 0);
```

### Differentiable Midpoint (vs. the non-differentiable one)

Non-differentiable — `\backprop` through `m` throws:
```js
compute({ p1, p2 }) {
  return { main: { type: 'Point', x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 } };
}
```

Differentiable — `\backprop loss` flows gradients into `p1` and `p2`:
```js
compute({ p1, p2 }) {
  return {
    main: {
      type: 'Point',
      x: div(add(p1.x, p2.x), 2),
      y: div(add(p1.y, p2.y), 2),
    }
  };
}
```

### What gradients are propagated

Input leaves are: the numeric fields of each parameter, named by the parameter's `argName`. For a `Point` input named `p`, the available leaves are `p.x` and `p.y`. For `Line`, `Circle`, `Ellipse`, `Arc`, fields recurse the same way (`line.p1.x`, `circle.center.y`, `arc.radius`, etc.). `Scalar` inputs are leaves at their `argName`. Default values are constants — they do not contribute gradients.

### Returning inputs unchanged

Returning an input numeric field directly preserves differentiability — the field arrives as a leaf-tagged value, and the lowering step turns it into a leaf SymExpr on the way out. So `{ main: { type: 'Point', x: p1.x, y: p1.y } }` is differentiable in `p1`.

### Limits

- Backprop only flows through `Scalar`, `Point`, `Line`, `Circle`, `Ellipse`, and `Arc` inputs (their numeric fields are exposed as leaves). Other types are still usable as inputs but their fields are not differentiable in v1.
- Comparison and rounding ops are differentiable only as JAX defines them — `round`/`floor`/`sign` have zero gradient almost everywhere; `where`'s gradient flows through whichever branch the condition selects.

### Things to note
- Don't write `+inputObject.x`. Inputs arrive as `Number`-instances carrying a `__leaf` tag. The unary `+` coerces them to primitives and strips the tag — your output would be a plain number and `\backprop` would refuse it. Read fields directly (or destructure as above).
- No exp builder in v1, so use `pow(Math.E, …)`. `Math.E`, `Math.PI`, and `Math.sqrt(2*Math.PI)` are constants on the JS side — passing them to `mul`/`div`/`pow` auto-lifts them as `const` SymExprs, no special handling needed.

---

## 8. Module Format (Direct .mjs / .js)

A direct module is a single ES module file that exports one or more `ExtensionDef`-shaped objects:

```js
// my-functions.mjs

export const Midpoint = {
  name: 'Midpoint',
  keyword: 'midpoint',
  outputType: 'Point',
  parameters: [
    { argName: 'p1', type: 'Point', defaultValue: 0, variadic: false },
    { argName: 'p2', type: 'Point', defaultValue: 0, variadic: false },
  ],
  compute({ p1, p2 }) {
    return {
      main: {
        type: 'Point',
        x: (p1.x + p2.x) / 2,
        y: (p1.y + p2.y) / 2,
      }
    };
  }
};

export const ScalarSum = {
  name: 'ScalarSum',
  keyword: 'scalar-sum',
  outputType: 'Scalar',
  parameters: [
    { argName: 'a', type: 'Scalar', defaultValue: 0, variadic: false },
    { argName: 'b', type: 'Scalar', defaultValue: 0, variadic: false },
  ],
  compute({ a, b }) {
    return { main: { type: 'Scalar', value: a + b } };
  }
};
```

**Rules:**
- Use `.mjs` or `.js` extension.
- Must be an ES module (use `export`, not `module.exports`).
- Every exported object with both `keyword` and `compute` is loaded. Others are ignored.
- The loader auto-detects this format when the URL ends with `.mjs` or `.js`.

---

## 9. Manifest Format (.json)

A manifest is a `.json` file that lists multiple extensions, each referencing a separate entry module. The manifest is useful for publishing a package of related functions.

```json
{
  "name": "My Geometry Pack",
  "version": "1.0.0",
  "description": "Optional description",
  "author": "Your Name",
  "homepage": "https://your-name.github.io/geo-pack",
  "extensions": [
    {
      "id": "midpoint",
      "name": "Midpoint",
      "keyword": "midpoint",
      "entry": "midpoint.mjs",
      "parameters": [
        { "name": "p1", "type": "Point", "default": 0 },
        { "name": "p2", "type": "Point", "default": 0 }
      ],
      "outputType": "Point"
    },
    {
      "id": "scalar-sum",
      "name": "ScalarSum",
      "keyword": "scalar-sum",
      "entry": "scalar-sum.mjs",
      "parameters": [
        { "name": "a", "type": "Scalar", "default": 0 },
        { "name": "b", "type": "Scalar", "default": 0 }
      ],
      "outputType": "Scalar"
    }
  ]
}
```

**Key differences from the direct module format:**

| Field | Direct module (`UserParam`) | Manifest (`parameters[]`) |
|---|---|---|
| Parameter name | `argName` | `name` |
| Default value | `defaultValue` | `default` |

**Validation rules:**
- `name` and `version` are required at the top level.
- Every entry in `extensions` must have `id`, `name`, `keyword`, and `entry`.
- `entry` is a path relative to the manifest URL, or an absolute HTTPS URL.
- Resolved entry URLs must also pass the domain whitelist check.

---

## 10. Hosting and Security Requirements

### Allowed Hosting Domains

Currently only **GitHub Pages** (`*.github.io`) is whitelisted.

### HTTPS Required

All URLs must use `https://`. Plain HTTP will be rejected.

### URL Constraints

- No `..` path traversal
- No `//` after the protocol portion
- No `@` in the URL (credential injection prevention)
- No credentials (username/password) embedded in the URL
- File must end with `.json`, `.mjs`, or `.js`

### Network Isolation

The worker has `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`, and `importScripts` all set to `undefined`. Any attempt to make a network request inside `compute` will throw.

### Recommended Repository Structure (GitHub Pages)

```
your-name.github.io/geo-pack/
  manifest.json       ← load from this URL
  midpoint.mjs
  scalar-sum.mjs
```

Enable GitHub Pages in repository Settings → Pages (source: `main` branch, `/ (root)` or `/docs`).

---

## 11. Loading Extensions in the Editor

Open the **Extensions** panel and paste the URL:

- Manifest: `https://your-name.github.io/geo-pack/manifest.json`
- Direct module: `https://your-name.github.io/geo-pack/midpoint.mjs`

Loaded extensions are **persisted to `localStorage`** and automatically reloaded on the next session. To remove one, use the remove button in the Extensions panel — this terminates the worker and clears the stored URL.

---

## 12. Worked Examples

### Scalar: distance between two points

```js
export const Distance = {
  name: 'Distance',
  keyword: 'distance',
  outputType: 'Scalar',
  parameters: [
    { argName: 'p1', type: 'Point', defaultValue: 0, variadic: false },
    { argName: 'p2', type: 'Point', defaultValue: 0, variadic: false },
  ],
  compute({ p1, p2 }) {
    const dx = p2.x - p1.x;
    const dy = p2.y - p1.y;
    return { main: { type: 'Scalar', value: Math.sqrt(dx * dx + dy * dy) } };
  }
};
```

### Point: interpolate between two points

```js
export const Lerp = {
  name: 'Lerp',
  keyword: 'lerp',
  outputType: 'Point',
  parameters: [
    { argName: 'p1', type: 'Point',  defaultValue: 0,   variadic: false },
    { argName: 'p2', type: 'Point',  defaultValue: 0,   variadic: false },
    { argName: 't',  type: 'Scalar', defaultValue: 0.5, variadic: false },
  ],
  compute({ p1, p2, t }) {
    return {
      main: {
        type: 'Point',
        x: p1.x + (p2.x - p1.x) * t,
        y: p1.y + (p2.y - p1.y) * t,
      }
    };
  }
};
```

### Point with auxiliary nodes: foot of perpendicular from point to line

```js
export const FootOfPerpendicular = {
  name: 'FootOfPerpendicular',
  keyword: 'foot-of-perpendicular',
  outputType: 'Point',
  parameters: [
    { argName: 'p',    type: 'Point', defaultValue: 0, variadic: false },
    { argName: 'line', type: 'Line',  defaultValue: 0, variadic: false },
  ],
  compute({ p, line }) {
    const { p1, p2 } = line;
    const dx = p2.x - p1.x;
    const dy = p2.y - p1.y;
    const lenSq = dx * dx + dy * dy;
    if (lenSq < 1e-12) return { main: { type: 'Dummy' } };
    const t = ((p.x - p1.x) * dx + (p.y - p1.y) * dy) / lenSq;
    const foot = { type: 'Point', x: p1.x + t * dx, y: p1.y + t * dy };
    const perp = { type: 'Line', p1: p, p2: foot };
    return {
      foot,             // auxiliary
      perp_line: perp,  // auxiliary
      main: foot,
    };
  }
};
```

### Degenerate case handling: line-line intersection

```js
export const LineIntersect = {
  name: 'LineIntersect',
  keyword: 'line-intersect',
  outputType: 'Point',
  parameters: [
    { argName: 'l1', type: 'Line', defaultValue: 0, variadic: false },
    { argName: 'l2', type: 'Line', defaultValue: 0, variadic: false },
  ],
  compute({ l1, l2 }) {
    const { p1: a, p2: b } = l1;
    const { p1: c, p2: d } = l2;
    const denom = (a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x);
    if (Math.abs(denom) < 1e-10) return { main: { type: 'Dummy' } }; // parallel
    const t = ((a.x - c.x) * (c.y - d.y) - (a.y - c.y) * (c.x - d.x)) / denom;
    return {
      main: {
        type: 'Point',
        x: a.x + t * (b.x - a.x),
        y: a.y + t * (b.y - a.y),
      }
    };
  }
};
```
