diff --git a/.changeset/kind-hats-shop.md b/.changeset/kind-hats-shop.md
new file mode 100644
index 000000000..ee115730f
--- /dev/null
+++ b/.changeset/kind-hats-shop.md
@@ -0,0 +1,5 @@
+---
+'@viamrobotics/motion-tools': patch
+---
+
+Add plugin docs and make plugin dependencies optional
diff --git a/docs/src/content/docs/plugins/draw-service.mdx b/docs/src/content/docs/plugins/draw-service.mdx
index ccb3dbc64..a7a5b93a8 100644
--- a/docs/src/content/docs/plugins/draw-service.mdx
+++ b/docs/src/content/docs/plugins/draw-service.mdx
@@ -1,6 +1,81 @@
---
title:
-description: Stream draw-server output into an embedded motion-tools visualizer.
+description: Stream a running draw server's output into an embedded motion-tools visualizer.
---
-Documentation coming soon.
+`` connects an embedded `` to a running draw server over WebSocket so any `client/api` calls made elsewhere render in this instance live. Skip it if you only render snapshots — snapshots are entirely client-side and don't need the server.
+
+The plugin is **headless**: it provides the connection context and starts the stream; it doesn't render anything itself.
+
+The Connect RPC client is required for the ``.
+
+## Install
+
+```bash
+pnpm add @connectrpc/connect @connectrpc/connect-web
+```
+
+## Usage
+
+```svelte
+
+
+
+
+
+
+
+```
+
+## Props
+
+| Prop | Type | Description |
+| -------- | ---------------------------------------------- | ------------------------------------------------------------- |
+| `config` | `{ backendIP: string; websocketPort: string }` | Host and port of the draw server. Default dev port is `3030`. |
+
+The `config` prop is reactive — change it and the plugin reconnects to the new endpoint.
+
+## Running a draw server
+
+`` is a client — it needs a draw server to talk to. You have three options:
+
+### Use the motion-tools local app
+
+The fastest path: run motion-tools locally (`make up` or `pnpm dev`). The local app already hosts a draw server on port `3030`, and `client/api` calls to the same host land in it.
+
+### Host the Go `DrawService` yourself
+
+Motion-tools exports a ready-made `DrawService` from `github.com/viam-labs/motion-tools/draw` — the same Connect-RPC handler the local app uses. Wire it up to your own HTTP server:
+
+```go
+package main
+
+import (
+ "log"
+ "net/http"
+
+ "connectrpc.com/connect"
+ "github.com/viam-labs/motion-tools/draw"
+ "github.com/viam-labs/motion-tools/draw/v1/drawv1connect"
+)
+
+func main() {
+ svc := draw.NewDrawService("") // "" = use os.TempDir for chunked buffers
+
+ mux := http.NewServeMux()
+ mux.Handle(drawv1connect.NewDrawServiceHandler(svc))
+
+ log.Fatal(http.ListenAndServe(":3030", mux))
+}
+```
+
+Your `client/api` producer code then points at this server, and `` in the browser subscribes to its stream.
+
+### Implement your own server
+
+If you need a custom backend (different language, different storage, extra side effects), the proto definitions are published under `github.com/viam-labs/motion-tools/draw/v1` (Go) and ship as `.proto` files in the source tree under `protos/draw/v1/`. Implement the `DrawService` RPCs (`AddEntity`, `UpdateEntity`, `RemoveEntity`, `StreamEntityChanges`, …) in whichever language you like; `` just needs a Connect-RPC server at the configured host:port.
+
+See the [`client/api`](../../api/client-api/) reference for the producer-side calls regardless of which server you use.
diff --git a/docs/src/content/docs/plugins/selection.mdx b/docs/src/content/docs/plugins/selection.mdx
index 5c2eccef7..d22e6ae4f 100644
--- a/docs/src/content/docs/plugins/selection.mdx
+++ b/docs/src/content/docs/plugins/selection.mdx
@@ -1,6 +1,66 @@
---
title:
-description: Lasso-pick points and geometries inside a motion-tools visualizer.
+description: Lasso- or ellipse-pick points and geometries inside a motion-tools visualizer.
---
-Documentation coming soon.
+`` adds a selection mode to the visualizer — a dashboard toggle plus an on-canvas lasso or ellipse tool that captures the points and geometries inside the drawn region. Selected entities are exposed via the `useSelectionPlugin` hook so you can react to them from your own UI.
+
+Selection mode automatically switches the camera to orthographic so 2D screen-space picking maps cleanly to the scene.
+
+The polygon-triangulation `earcut` library is required for the ``.
+
+## Install
+
+```bash
+pnpm add earcut
+```
+
+## Usage
+
+```svelte
+
+
+
+
+
+
+
+```
+
+A new toggle appears in the top dashboard; clicking it enters selection mode. The popover next to it switches between **Lasso** and **Ellipse** tools.
+
+## Props
+
+| Prop | Type | Default | Description |
+| ----------------------- | --------- | ------- | --------------------------------------------------------------------------------------------- |
+| `enabled` | `boolean` | `false` | Enter selection mode automatically on mount. |
+| `autoSelectNewEntities` | `boolean` | `false` | Auto-select the most recently created selection entity (sets it as the focused entity). |
+| `children` | `Snippet` | — | Rendered only while selection mode is active — useful for overlays scoped to the active tool. |
+
+## Reading the selection
+
+Use `useSelectionPlugin` to read the current set of selected entities from anywhere inside ``:
+
+```svelte
+
+
+
Selected {selection.current.length} entities
+
+```
+
+The hook also exposes `clearSelections()` to remove all selection-marker entities from the world.
+
+## Traits
+
+For lower-level access to selection state (e.g. querying which points fall inside a selected region), the plugin exports its Koota traits under the `selectionTraits` namespace:
+
+```ts
+import { selectionTraits } from '@viamrobotics/motion-tools/plugins'
+```
diff --git a/docs/src/content/docs/plugins/skybox.mdx b/docs/src/content/docs/plugins/skybox.mdx
index dafdbc2ad..6febf183b 100644
--- a/docs/src/content/docs/plugins/skybox.mdx
+++ b/docs/src/content/docs/plugins/skybox.mdx
@@ -3,4 +3,33 @@ title:
description: Render an equirectangular skybox dome around the motion-tools scene.
---
-Documentation coming soon.
+`` wraps the scene in a [grounded skybox dome](https://threejs.org/examples/?q=skybox#webgl_materials_envmaps_groundprojected) loaded from an equirectangular image, anchoring the lower hemisphere to the world XY plane so geometry sits convincingly on the ground.
+
+Use it to add real-world context to a snapshot — a warehouse, a lab, or any 360° photo of the environment the robot operates in.
+
+## Usage
+
+```svelte
+
+
+
+
+
+
+
+```
+
+The image should be a 2:1 equirectangular projection (the standard format for 360° photos). Drop it in your app's `public/` directory or point `url` at any reachable HTTP endpoint.
+
+## Props
+
+| Prop | Type | Default | Description |
+| ---------- | ----------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `url` | `string` | — | URL of the equirectangular image. Required. |
+| `position` | `[x: number, y: number, z: number]` | `[0, 0, height]` | World-space center of the dome. Default anchors the ground plane at world Z=0. |
+| `rotation` | `[x: number, y: number, z: number]` | `[Math.PI / 2, 0, 0]` | Euler rotation in radians. Default aligns the image's vertical axis (+Y) with this scene's vertical axis (+Z); the Z component then acts as yaw. |
+| `height` | `number` | `15` | Camera height above ground when the source photo was taken. Larger values magnify the lower portion of the image. |
+| `radius` | `number` | `100` | Dome radius. Must exceed the scene camera's reach so the camera stays inside the dome. |
diff --git a/package.json b/package.json
index 8b60ba91d..908a69789 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,8 @@
"@ag-grid-community/core": "32.3.9",
"@ag-grid-community/styles": "32.3.9",
"@changesets/cli": "2.29.6",
+ "@connectrpc/connect": "1.7.0",
+ "@connectrpc/connect-web": "1.7.0",
"@dimforge/rapier3d-compat": "0.18.2",
"@eslint/compat": "2.0.2",
"@eslint/js": "10.0.1",
@@ -86,6 +88,7 @@
"@zag-js/tree-view": "1.22.1",
"camera-controls": "3.1.0",
"concurrently": "^9.2.1",
+ "earcut": "^3.0.2",
"esbuild": "^0.27.3",
"eslint": "10.0.2",
"eslint-config-prettier": "10.1.8",
@@ -121,6 +124,8 @@
"@ag-grid-community/client-side-row-model": ">=32.3.0",
"@ag-grid-community/core": ">=32.3.0",
"@ag-grid-community/styles": ">=32.3.0",
+ "@connectrpc/connect": ">=1",
+ "@connectrpc/connect-web": ">=1",
"@dimforge/rapier3d-compat": ">=0.17",
"@tanstack/svelte-query-devtools": ">=6",
"@threlte/core": ">=8",
@@ -139,6 +144,7 @@
"@zag-js/toggle-group": ">=1",
"@zag-js/tree-view": ">=1",
"camera-controls": ">=3",
+ "earcut": ">=3",
"idb-keyval": ">=6",
"lucide-svelte": ">=0.511",
"runed": ">=0.28",
@@ -147,8 +153,17 @@
"svelte-virtuallists": ">=1"
},
"peerDependenciesMeta": {
+ "@connectrpc/connect": {
+ "optional": true
+ },
+ "@connectrpc/connect-web": {
+ "optional": true
+ },
"@tanstack/svelte-query-devtools": {
"optional": true
+ },
+ "earcut": {
+ "optional": true
}
},
"pnpm": {
@@ -193,10 +208,7 @@
],
"dependencies": {
"@bufbuild/protobuf": "1.10.1",
- "@connectrpc/connect": "1.7.0",
- "@connectrpc/connect-web": "1.7.0",
"@neodrag/svelte": "^2.3.3",
- "earcut": "^3.0.2",
"filtrex": "^3.1.0",
"koota": "0.6.5",
"lodash-es": "4.18.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c1317fc9c..920c11f7a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -15,21 +15,12 @@ importers:
'@bufbuild/protobuf':
specifier: 1.10.1
version: 1.10.1
- '@connectrpc/connect':
- specifier: 1.7.0
- version: 1.7.0(@bufbuild/protobuf@1.10.1)
- '@connectrpc/connect-web':
- specifier: 1.7.0
- version: 1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1))
'@neodrag/svelte':
specifier: ^2.3.3
version: 2.3.3(svelte@5.55.7)
'@zag-js/dialog':
specifier: '>=1.31'
version: 1.32.0
- earcut:
- specifier: ^3.0.2
- version: 3.0.2
filtrex:
specifier: ^3.1.0
version: 3.1.0
@@ -58,6 +49,12 @@ importers:
'@changesets/cli':
specifier: 2.29.6
version: 2.29.6(@types/node@25.6.0)
+ '@connectrpc/connect':
+ specifier: 1.7.0
+ version: 1.7.0(@bufbuild/protobuf@1.10.1)
+ '@connectrpc/connect-web':
+ specifier: 1.7.0
+ version: 1.7.0(@bufbuild/protobuf@1.10.1)(@connectrpc/connect@1.7.0(@bufbuild/protobuf@1.10.1))
'@dimforge/rapier3d-compat':
specifier: 0.18.2
version: 0.18.2
@@ -187,6 +184,9 @@ importers:
concurrently:
specifier: ^9.2.1
version: 9.2.1
+ earcut:
+ specifier: ^3.0.2
+ version: 3.0.2
esbuild:
specifier: ^0.27.3
version: 0.27.3