diff --git a/packages/docs/.vitepress/config.ts b/packages/docs/.vitepress/config.ts
index 1bb9fd202..42c6573d9 100644
--- a/packages/docs/.vitepress/config.ts
+++ b/packages/docs/.vitepress/config.ts
@@ -25,8 +25,21 @@ interface SidebarLabels {
figmaMatrix: string
}
+interface ProgrammableLabels {
+ cli: string
+ inspecting: string
+ exporting: string
+ analyzing: string
+ scripting: string
+ jsxRenderer: string
+ mcpServer: string
+ aiChat: string
+ collaboration: string
+}
+
interface NavLabels {
userGuide: string
+ programmable: string
reference: string
development: string
openApp: string
@@ -52,7 +65,6 @@ const userGuideSidebar = (prefix: string, l: SidebarLabels): DefaultTheme.Sideba
text: l.organizing,
items: [
{ text: l.layers, link: `${prefix}/user-guide/layers-and-pages` },
- { text: l.contextMenu, link: `${prefix}/user-guide/context-menu` },
{ text: l.exporting, link: `${prefix}/user-guide/exporting` },
],
},
@@ -66,6 +78,34 @@ const userGuideSidebar = (prefix: string, l: SidebarLabels): DefaultTheme.Sideba
},
]
+const programmableSidebar = (prefix: string, p: ProgrammableLabels): DefaultTheme.SidebarItem[] => [
+ {
+ text: p.cli,
+ items: [
+ { text: p.inspecting, link: `${prefix}/programmable/cli/inspecting` },
+ { text: p.exporting, link: `${prefix}/programmable/cli/exporting` },
+ { text: p.analyzing, link: `${prefix}/programmable/cli/analyzing` },
+ { text: p.scripting, link: `${prefix}/programmable/cli/scripting` },
+ ],
+ },
+ {
+ text: p.jsxRenderer,
+ link: `${prefix}/programmable/jsx-renderer`,
+ },
+ {
+ text: p.mcpServer,
+ link: `${prefix}/programmable/mcp-server`,
+ },
+ {
+ text: p.aiChat,
+ link: `${prefix}/programmable/ai-chat`,
+ },
+ {
+ text: p.collaboration,
+ link: `${prefix}/programmable/collaboration`,
+ },
+]
+
const guideSidebar = (prefix: string, l: SidebarLabels): DefaultTheme.SidebarItem[] => [
{
text: l.guide,
@@ -80,16 +120,15 @@ const guideSidebar = (prefix: string, l: SidebarLabels): DefaultTheme.SidebarIte
},
]
-const referenceSidebar = (prefix: string, label: string): DefaultTheme.SidebarItem[] => [
+const referenceSidebar = (prefix: string, label: string, l: SidebarLabels): DefaultTheme.SidebarItem[] => [
{
text: label,
items: [
{ text: 'Keyboard Shortcuts', link: `${prefix}/reference/keyboard-shortcuts` },
+ { text: l.contextMenu, link: `${prefix}/user-guide/context-menu` },
{ text: 'Node Types', link: `${prefix}/reference/node-types` },
- { text: 'MCP Tools', link: `${prefix}/reference/mcp-tools` },
{ text: 'Scene Graph', link: `${prefix}/reference/scene-graph` },
{ text: 'File Format', link: `${prefix}/reference/file-format` },
- { text: 'Eval Command', link: `${prefix}/eval-command` },
],
},
]
@@ -106,20 +145,31 @@ const developmentSidebar = (prefix: string, label: string): DefaultTheme.Sidebar
},
]
+const EN_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Inspecting Files', exporting: 'Exporting', analyzing: 'Analyzing Designs', scripting: 'Scripting', jsxRenderer: 'JSX Renderer', mcpServer: 'MCP Server', aiChat: 'AI Chat', collaboration: 'Collaboration' }
+const DE_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Dateien inspizieren', exporting: 'Exportieren', analyzing: 'Designs analysieren', scripting: 'Skripte', jsxRenderer: 'JSX-Renderer', mcpServer: 'MCP-Server', aiChat: 'KI-Chat', collaboration: 'Zusammenarbeit' }
+const IT_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Ispezione file', exporting: 'Esportazione', analyzing: 'Analisi design', scripting: 'Scripting', jsxRenderer: 'Renderer JSX', mcpServer: 'Server MCP', aiChat: 'Chat IA', collaboration: 'Collaborazione' }
+const FR_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Inspecter les fichiers', exporting: 'Exporter', analyzing: 'Analyser les designs', scripting: 'Scripts', jsxRenderer: 'Moteur JSX', mcpServer: 'Serveur MCP', aiChat: 'Chat IA', collaboration: 'Collaboration' }
+const ES_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Inspeccionar archivos', exporting: 'Exportar', analyzing: 'Analizar diseños', scripting: 'Scripts', jsxRenderer: 'Renderizador JSX', mcpServer: 'Servidor MCP', aiChat: 'Chat IA', collaboration: 'Colaboración' }
+const PL_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Inspekcja plików', exporting: 'Eksportowanie', analyzing: 'Analiza projektów', scripting: 'Skrypty', jsxRenderer: 'Renderer JSX', mcpServer: 'Serwer MCP', aiChat: 'Czat AI', collaboration: 'Współpraca' }
+const RU_PROG: ProgrammableLabels = { cli: 'CLI', inspecting: 'Инспекция файлов', exporting: 'Экспорт', analyzing: 'Анализ дизайна', scripting: 'Скрипты', jsxRenderer: 'JSX-рендерер', mcpServer: 'MCP-сервер', aiChat: 'ИИ-чат', collaboration: 'Совместная работа' }
+
const localeThemeConfig = (
prefix: string,
nav: NavLabels,
sidebar: SidebarLabels,
+ prog: ProgrammableLabels,
): DefaultTheme.Config => ({
nav: [
{ text: nav.userGuide, link: `${prefix}/user-guide/` },
+ { text: nav.programmable, link: `${prefix}/programmable/` },
{ text: nav.reference, link: `${prefix}/reference/keyboard-shortcuts` },
{ text: nav.development, link: `${prefix}/development/contributing` },
{ text: nav.openApp, link: 'https://app.openpencil.dev' },
],
sidebar: {
[`${prefix}/user-guide/`]: userGuideSidebar(prefix, sidebar),
- [`${prefix}/reference/`]: referenceSidebar(prefix, nav.reference),
+ [`${prefix}/programmable/`]: programmableSidebar(prefix, prog),
+ [`${prefix}/reference/`]: referenceSidebar(prefix, nav.reference, sidebar),
[`${prefix}/`]: [
...guideSidebar(prefix, sidebar),
...developmentSidebar(prefix, nav.development),
@@ -251,37 +301,37 @@ export default defineConfig({
label: 'Deutsch',
lang: 'de',
description: 'Open-Source, KI-nativer Design-Editor. Figma-Alternative.',
- themeConfig: localeThemeConfig('/de', { userGuide: 'Benutzerhandbuch', reference: 'Referenz', development: 'Entwicklung', openApp: 'App öffnen' }, DE),
+ themeConfig: localeThemeConfig('/de', { userGuide: 'Benutzerhandbuch', programmable: 'Programmierbar', reference: 'Referenz', development: 'Entwicklung', openApp: 'App öffnen' }, DE, DE_PROG),
},
it: {
label: 'Italiano',
lang: 'it',
description: 'Editor di design open-source, IA-nativo. Alternativa a Figma.',
- themeConfig: localeThemeConfig('/it', { userGuide: 'Guida utente', reference: 'Riferimento', development: 'Sviluppo', openApp: 'Apri app' }, IT),
+ themeConfig: localeThemeConfig('/it', { userGuide: 'Guida utente', programmable: 'Programmabile', reference: 'Riferimento', development: 'Sviluppo', openApp: 'Apri app' }, IT, IT_PROG),
},
fr: {
label: 'Français',
lang: 'fr',
description: 'Éditeur de design open-source, IA-natif. Alternative à Figma.',
- themeConfig: localeThemeConfig('/fr', { userGuide: 'Guide utilisateur', reference: 'Référence', development: 'Développement', openApp: "Ouvrir l'app" }, FR),
+ themeConfig: localeThemeConfig('/fr', { userGuide: 'Guide utilisateur', programmable: 'Programmable', reference: 'Référence', development: 'Développement', openApp: "Ouvrir l'app" }, FR, FR_PROG),
},
es: {
label: 'Español',
lang: 'es',
description: 'Editor de diseño open-source, IA-nativo. Alternativa a Figma.',
- themeConfig: localeThemeConfig('/es', { userGuide: 'Guía del usuario', reference: 'Referencia', development: 'Desarrollo', openApp: 'Abrir app' }, ES),
+ themeConfig: localeThemeConfig('/es', { userGuide: 'Guía del usuario', programmable: 'Programable', reference: 'Referencia', development: 'Desarrollo', openApp: 'Abrir app' }, ES, ES_PROG),
},
pl: {
label: 'Polski',
lang: 'pl',
description: "Open-source'owy edytor graficzny z natywnym AI. Alternatywa dla Figmy.",
- themeConfig: localeThemeConfig('/pl', { userGuide: 'Podręcznik', reference: 'Referencja', development: 'Rozwój', openApp: 'Otwórz app' }, PL),
+ themeConfig: localeThemeConfig('/pl', { userGuide: 'Podręcznik', programmable: 'Programowalny', reference: 'Referencja', development: 'Rozwój', openApp: 'Otwórz app' }, PL, PL_PROG),
},
ru: {
label: 'Русский',
lang: 'ru',
description: 'Дизайн-редактор с открытым исходным кодом. Альтернатива Figma с встроенным ИИ.',
- themeConfig: localeThemeConfig('/ru', { userGuide: 'Руководство', reference: 'Справочник', development: 'Разработка', openApp: 'Открыть приложение' }, RU),
+ themeConfig: localeThemeConfig('/ru', { userGuide: 'Руководство', programmable: 'Программируемость', reference: 'Справочник', development: 'Разработка', openApp: 'Открыть приложение' }, RU, RU_PROG),
},
},
@@ -290,6 +340,7 @@ export default defineConfig({
nav: [
{ text: 'User Guide', link: '/user-guide/' },
+ { text: 'Programmable', link: '/programmable/' },
{ text: 'Reference', link: '/reference/keyboard-shortcuts' },
{ text: 'Development', link: '/development/contributing' },
{ text: 'Open App', link: 'https://app.openpencil.dev' },
@@ -297,19 +348,8 @@ export default defineConfig({
sidebar: {
'/user-guide/': userGuideSidebar('', EN),
- '/reference/': [
- {
- text: 'Reference',
- items: [
- { text: 'Keyboard Shortcuts', link: '/reference/keyboard-shortcuts' },
- { text: 'Node Types', link: '/reference/node-types' },
- { text: 'MCP Tools', link: '/reference/mcp-tools' },
- { text: 'Scene Graph', link: '/reference/scene-graph' },
- { text: 'File Format', link: '/reference/file-format' },
- { text: 'Eval Command', link: '/eval-command' },
- ],
- },
- ],
+ '/programmable/': programmableSidebar('', EN_PROG),
+ '/reference/': referenceSidebar('', 'Reference', EN),
'/': [
...guideSidebar('', EN),
{
diff --git a/packages/docs/programmable/ai-chat.md b/packages/docs/programmable/ai-chat.md
new file mode 100644
index 000000000..864480015
--- /dev/null
+++ b/packages/docs/programmable/ai-chat.md
@@ -0,0 +1,47 @@
+---
+title: AI Chat
+description: Built-in AI assistant with 87 tools for creating and modifying designs.
+---
+
+# AI Chat
+
+Press ⌘J (Ctrl + J) to open the AI assistant. Describe what you want — it creates shapes, sets styles, manages layout, works with components, and analyzes your design.
+
+## Setup
+
+1. Open the AI chat panel (⌘J)
+2. Click the settings icon
+3. Enter your OpenRouter API key
+4. Choose a model (Claude, GPT-4, Gemini, etc.)
+
+No backend, no subscription — your key talks directly to OpenRouter.
+
+## What It Can Do
+
+The assistant has 87 tools across these categories:
+
+- **Create** — frames, shapes, text, components, pages. Renders JSX for complex layouts.
+- **Style** — fills, strokes, effects, opacity, corner radius, blend modes.
+- **Layout** — auto-layout, alignment, spacing, sizing.
+- **Components** — create components, instances, component sets. Manage overrides.
+- **Variables** — create/edit variables, collections, modes. Bind to fills.
+- **Query** — find nodes, read properties, list pages, fonts, selection.
+- **Analyze** — color palette, typography audit, spacing consistency, cluster detection.
+- **Export** — PNG, SVG, JSX with Tailwind classes.
+- **Vector** — boolean operations, path manipulation.
+
+## Example Prompts
+
+- "Create a card with a title, description, and a blue button"
+- "Make all buttons on this page use the same border radius"
+- "What fonts are used in this file?"
+- "Change the background of the selected frame to a gradient from blue to purple"
+- "Export the selected frame as SVG"
+- "Find all text nodes with font size less than 12"
+
+## Tips
+
+- Select nodes before asking — the assistant knows what's selected.
+- Be specific about colors, sizes, and positions for precise results.
+- The assistant can modify multiple nodes in one message.
+- Use "undo" in the editor if you don't like the result.
diff --git a/packages/docs/programmable/cli/analyzing.md b/packages/docs/programmable/cli/analyzing.md
new file mode 100644
index 000000000..da8ab307b
--- /dev/null
+++ b/packages/docs/programmable/cli/analyzing.md
@@ -0,0 +1,65 @@
+---
+title: Analyzing Designs
+description: Audit colors, typography, spacing, and repeated patterns in .fig files.
+---
+
+# Analyzing Designs
+
+The `analyze` commands audit an entire design system from the terminal — find inconsistencies, extract the real palette, spot components waiting to be extracted.
+
+## Colors
+
+```sh
+open-pencil analyze colors design.fig
+```
+
+Finds every color in the file, counts usage, and shows a visual histogram:
+
+```
+#1d1b20 ██████████████████████████████ 17155×
+#49454f ██████████████████████████████ 9814×
+#ffffff ██████████████████████████████ 8620×
+#6750a4 ██████████████████████████████ 3967×
+```
+
+## Typography
+
+```sh
+open-pencil analyze typography design.fig
+```
+
+Lists every font family, size, and weight combination with usage counts. Useful for spotting one-off text styles that should be consolidated.
+
+## Spacing
+
+```sh
+open-pencil analyze spacing design.fig
+```
+
+Audits gap and padding values across auto-layout frames. Helps identify spacing scale inconsistencies — e.g. a stray `13px` gap among otherwise `8/16/24` values.
+
+## Clusters
+
+```sh
+open-pencil analyze clusters design.fig
+```
+
+Finds repeated node patterns that could be extracted into components:
+
+```
+3771× frame "container" (100% match)
+ size: 40×40, structure: Frame > [Frame]
+
+2982× instance "Checkboxes" (100% match)
+ size: 48×48, structure: Instance > [Frame]
+```
+
+## JSON Output
+
+All analyze commands support `--json` for machine-readable output:
+
+```sh
+open-pencil analyze colors design.fig --json
+```
+
+Pipe into `jq`, feed into CI checks, or use in scripts that enforce design token budgets.
diff --git a/packages/docs/programmable/cli/exporting.md b/packages/docs/programmable/cli/exporting.md
new file mode 100644
index 000000000..058bb6988
--- /dev/null
+++ b/packages/docs/programmable/cli/exporting.md
@@ -0,0 +1,59 @@
+---
+title: Exporting
+description: Render .fig files to PNG, JPG, WEBP, SVG, or JSX with Tailwind classes.
+---
+
+# Exporting
+
+Export designs from the terminal — raster images, vectors, or JSX code.
+
+## Image Export
+
+```sh
+open-pencil export design.fig # PNG (default)
+open-pencil export design.fig -f jpg -s 2 -q 90 # JPG at 2×, quality 90
+open-pencil export design.fig -f webp -s 3 # WEBP at 3×
+open-pencil export design.fig -f svg # SVG vector
+```
+
+Options:
+
+- `-f` — format: `png`, `jpg`, `webp`, `svg`, `jsx`
+- `-s` — scale: `1`–`4`
+- `-q` — quality: `0`–`100` (JPG/WEBP only)
+- `-o` — output path
+- `--page` — page name
+- `--node` — specific node ID
+
+## JSX Export
+
+Export as JSX with Tailwind utility classes:
+
+```sh
+open-pencil export design.fig -f jsx --style tailwind
+```
+
+Output:
+
+```html
+
+
Card Title
+
Description text
+
+```
+
+Also supports `--style openpencil` for the native JSX format (see [JSX Renderer](../jsx-renderer)).
+
+## Thumbnails
+
+```sh
+open-pencil export design.fig --thumbnail --width 1920 --height 1080
+```
+
+## Live App Mode
+
+Omit the file to export from the running app:
+
+```sh
+open-pencil export -f png # screenshot the current canvas
+```
diff --git a/packages/docs/programmable/cli/inspecting.md b/packages/docs/programmable/cli/inspecting.md
new file mode 100644
index 000000000..af4f17aae
--- /dev/null
+++ b/packages/docs/programmable/cli/inspecting.md
@@ -0,0 +1,102 @@
+---
+title: Inspecting Files
+description: Browse node trees, search by name or type, and dig into properties from the terminal.
+---
+
+# Inspecting Files
+
+The CLI lets you explore `.fig` files without opening the editor. Every command also works on the live app — just omit the file argument.
+
+## Install
+
+```sh
+bun add -g @open-pencil/cli
+```
+
+Or with Homebrew:
+
+```sh
+brew install open-pencil/tap/open-pencil
+```
+
+## Document Info
+
+Get a quick overview — page count, total nodes, fonts used, file size:
+
+```sh
+open-pencil info design.fig
+```
+
+## Node Tree
+
+Print the full node hierarchy:
+
+```sh
+open-pencil tree design.fig
+```
+
+```
+[0] [page] "Getting started" (0:46566)
+ [0] [section] "" (0:46567)
+ [0] [frame] "Body" (0:46568)
+ [0] [frame] "Introduction" (0:46569)
+ [0] [frame] "Introduction Card" (0:46570)
+ [0] [frame] "Guidance" (0:46571)
+```
+
+## Find Nodes
+
+Search by type:
+
+```sh
+open-pencil find design.fig --type TEXT
+```
+
+Search by name:
+
+```sh
+open-pencil find design.fig --name "Button"
+```
+
+Both flags can be combined to narrow results further.
+
+## Node Details
+
+Inspect all properties of a specific node by its ID:
+
+```sh
+open-pencil node design.fig --id 1:23
+```
+
+## Pages
+
+List all pages in the document:
+
+```sh
+open-pencil pages design.fig
+```
+
+## Variables
+
+List design variables and their collections:
+
+```sh
+open-pencil variables design.fig
+```
+
+## Live App Mode
+
+When the desktop app is running, omit the file argument — the CLI connects via RPC and operates on the live canvas:
+
+```sh
+open-pencil tree # inspect the live document
+open-pencil eval -c "..." # query the editor
+```
+
+## JSON Output
+
+All commands support `--json` for machine-readable output — pipe into `jq`, feed to CI scripts, or process with other tools:
+
+```sh
+open-pencil tree design.fig --json | jq '.[] | .name'
+```
diff --git a/packages/docs/programmable/cli/scripting.md b/packages/docs/programmable/cli/scripting.md
new file mode 100644
index 000000000..8c668bc5f
--- /dev/null
+++ b/packages/docs/programmable/cli/scripting.md
@@ -0,0 +1,70 @@
+---
+title: Scripting
+description: Execute JavaScript with the Figma Plugin API — query nodes, batch-modify designs, create frames.
+---
+
+# Scripting
+
+`open-pencil eval` gives you the full Figma Plugin API in the terminal. Read nodes, modify properties, create shapes — then write changes back to the file.
+
+## Basic Usage
+
+```sh
+open-pencil eval design.fig -c "figma.currentPage.children.length"
+```
+
+The `-c` flag takes JavaScript. The `figma` global works like the Figma Plugin API.
+
+## Query Nodes
+
+```sh
+open-pencil eval design.fig -c "
+ figma.currentPage.findAll(n => n.type === 'FRAME' && n.name.includes('Button'))
+ .map(b => ({ id: b.id, name: b.name, w: b.width, h: b.height }))
+"
+```
+
+## Modify and Save
+
+```sh
+open-pencil eval design.fig -c "
+ figma.currentPage.children.forEach(n => n.opacity = 0.5)
+" -w
+```
+
+`-w` writes changes back to the input file. Use `-o output.fig` to write to a different file instead.
+
+## Read from Stdin
+
+For longer scripts:
+
+```sh
+cat transform.js | open-pencil eval design.fig --stdin -w
+```
+
+## Live App Mode
+
+Omit the file to run against the running desktop app:
+
+```sh
+open-pencil eval -c "figma.currentPage.name"
+```
+
+## Available API
+
+The `figma` object supports:
+
+- `figma.currentPage` — the active page
+- `figma.root` — the document root
+- `figma.createFrame()`, `figma.createRectangle()`, `figma.createEllipse()`, `figma.createText()`, etc.
+- `.findAll()`, `.findOne()` — search descendants
+- `.appendChild()`, `.insertChild()` — tree manipulation
+- All property setters: `.fills`, `.strokes`, `.effects`, `.opacity`, `.cornerRadius`, `.layoutMode`, `.itemSpacing`, etc.
+
+This is the same API Figma plugins use, so existing knowledge and code snippets transfer directly.
+
+## JSON Output
+
+```sh
+open-pencil eval design.fig -c "..." --json
+```
diff --git a/packages/docs/programmable/collaboration.md b/packages/docs/programmable/collaboration.md
new file mode 100644
index 000000000..b7e2b1de1
--- /dev/null
+++ b/packages/docs/programmable/collaboration.md
@@ -0,0 +1,38 @@
+---
+title: Collaboration
+description: Real-time collaborative editing via P2P WebRTC — no server, no account.
+---
+
+# Collaboration
+
+Edit designs together in real time. Peers connect directly — no server relays your data, no account required.
+
+## Sharing a Room
+
+1. Click the share button in the top-right corner
+2. Copy the generated link (`app.openpencil.dev/share/`)
+3. Send it to your collaborators
+
+Anyone with the link can join. The room stays active as long as at least one participant has the page open.
+
+## What Syncs
+
+- **Document changes** — every edit (shapes, text, properties, layout) syncs instantly
+- **Cursors** — see where each collaborator is pointing, with their name and color
+- **Selections** — highlighted selections are visible to everyone
+
+## Follow Mode
+
+Click a collaborator's avatar in the top bar to follow their viewport. Your canvas pans and zooms to match their view. Click again to stop following.
+
+## How It Works
+
+Peers connect directly via WebRTC — your design data goes straight from browser to browser, never through a central server. The document state uses a CRDT (conflict-free replicated data type), so concurrent edits merge automatically without conflicts.
+
+The room persists locally — if you refresh the page, you rejoin with the same state.
+
+## Tips
+
+- Works in the browser and the desktop app
+- Room IDs are cryptographically random — only people with the link can join
+- Stale cursors are cleaned up automatically when someone disconnects
diff --git a/packages/docs/programmable/index.md b/packages/docs/programmable/index.md
new file mode 100644
index 000000000..a013cc51a
--- /dev/null
+++ b/packages/docs/programmable/index.md
@@ -0,0 +1,51 @@
+---
+layout: doc
+title: Programmable
+description: Every operation in OpenPencil is scriptable — CLI, Figma Plugin API, JSX renderer, MCP server, AI chat.
+---
+
+# Programmable
+
+OpenPencil treats design files as data. Every operation available in the editor — creating shapes, setting fills, managing auto-layout, exporting assets — is also available from the terminal, from AI agents, and from code. No plugins to install, no API keys, no waiting list.
+
+This is not a bolt-on feature. The editor UI and the programmatic interfaces use the same engine. If you can do it by clicking, you can do it by scripting.
+
+## CLI
+
+Inspect, export, and analyze `.fig` files without opening the editor. List pages, search nodes, extract design tokens, render to PNG — all from the terminal with machine-readable JSON output.
+
+The CLI also connects to the running desktop app via RPC, so you can script the editor while you're using it.
+
+[Inspecting Files](./cli/inspecting) · [Exporting](./cli/exporting) · [Analyzing Designs](./cli/analyzing) · [Scripting](./cli/scripting)
+
+## JSX Renderer
+
+Describe UI as JSX — the same syntax LLMs already know from React. A single `render_jsx` call can create an entire component tree with frames, text, auto-layout, fills, and strokes. Compact, declarative, and diffable.
+
+Going the other direction, export any selection back to JSX with Tailwind classes — useful for handing off to development or feeding designs back into an LLM.
+
+[JSX Renderer →](./jsx-renderer)
+
+## MCP Server
+
+Connect Claude Code, Cursor, Windsurf, or any MCP-compatible client to OpenPencil. The server exposes 90 tools for reading, creating, and modifying designs — the same tools the built-in AI chat uses. Runs over stdio or HTTP with session support.
+
+[MCP Server →](./mcp-server)
+
+## AI Chat
+
+The built-in assistant has access to 87 tools that cover the full surface of the editor. Describe what you want in natural language — "add a 16px drop shadow to all buttons", "create a card component with dark mode variant", "export every frame on this page at 2×".
+
+[AI Chat →](./ai-chat)
+
+## Collaboration
+
+Real-time multiplayer editing over peer-to-peer WebRTC. No server, no account. Share a room link and edit together with live cursors and follow mode. Document state syncs via CRDT, so edits merge automatically even on flaky connections.
+
+[Collaboration →](./collaboration)
+
+## Why Programmable?
+
+Figma is a closed platform. Their MCP server is read-only. CDP browser access was killed in version 126. Design files live in a proprietary format on someone else's servers. Plugin development requires a custom runtime with limited APIs.
+
+OpenPencil is the alternative: open source, MIT licensed, every operation scriptable, data stored locally. Your design files are yours — inspect them, transform them, pipe them into CI, feed them to an LLM. No permission needed.
diff --git a/packages/docs/programmable/jsx-renderer.md b/packages/docs/programmable/jsx-renderer.md
new file mode 100644
index 000000000..66a1e8cb4
--- /dev/null
+++ b/packages/docs/programmable/jsx-renderer.md
@@ -0,0 +1,120 @@
+---
+title: JSX Renderer
+description: Create designs with JSX — the syntax LLMs already know from millions of React components.
+---
+
+# JSX Renderer
+
+OpenPencil uses JSX as its design creation language. LLMs have seen millions of React components — describing a layout as `` is natural, no special training needed. Every token matters when an AI agent performs dozens of operations, and JSX is the most compact declarative representation.
+
+JSX is also diffable. When an AI modifies a design, the change is a JSX diff — readable, reviewable, version-controllable.
+
+## Creating Designs
+
+The `render` tool (available in AI chat, MCP, and CLI eval) accepts JSX:
+
+```jsx
+
+ Card Title
+ Description text
+
+```
+
+In the CLI:
+
+```sh
+open-pencil eval design.fig -c 'figma.render(``)' -w
+```
+
+## Elements
+
+All node types are available as JSX elements:
+
+| Element | Creates | Aliases |
+|---------|---------|---------|
+| `` | Frame (container, supports auto-layout) | `` |
+| `` | Rectangle | `` |
+| `` | Ellipse / circle | |
+| `` | Text node (children become text content) | |
+| `` | Line | |
+| `` | Star | |
+| `` | Polygon | |
+| `` | Vector path | |
+| `` | Group | |
+| `` | Section | |
+
+## Style Props
+
+Compact shorthand props inspired by Tailwind's naming.
+
+### Layout
+
+| Prop | Description |
+|------|-------------|
+| `flex` | `"row"` or `"col"` — enables auto-layout |
+| `gap` | Space between children |
+| `wrap` | Wrap children to next line |
+| `rowGap` | Counter-axis spacing when wrapping |
+| `justify` | `"start"`, `"end"`, `"center"`, `"between"` |
+| `items` | `"start"`, `"end"`, `"center"`, `"stretch"` |
+| `p`, `px`, `py`, `pt`, `pr`, `pb`, `pl` | Padding |
+
+### Size & Position
+
+| Prop | Description |
+|------|-------------|
+| `w`, `h` | Width/height — number, `"fill"`, or `"hug"` |
+| `minW`, `maxW`, `minH`, `maxH` | Size constraints |
+| `x`, `y` | Position |
+
+### Appearance
+
+| Prop | Description |
+|------|-------------|
+| `bg` | Background fill (hex color) |
+| `fill` | Alias for `bg` |
+| `stroke` | Stroke color |
+| `strokeWidth` | Stroke width (default: 1) |
+| `rounded` | Corner radius (or `roundedTL`, `roundedTR`, `roundedBL`, `roundedBR`) |
+| `cornerSmoothing` | iOS-style smooth corners (0–1) |
+| `opacity` | 0–1 |
+| `shadow` | Drop shadow (e.g. `"0 4 8 #00000040"`) |
+| `blur` | Layer blur radius |
+| `rotate` | Rotation in degrees |
+| `blendMode` | Blend mode |
+| `overflow` | `"hidden"` or `"visible"` |
+
+### Typography
+
+| Prop | Description |
+|------|-------------|
+| `size` / `fontSize` | Font size |
+| `font` / `fontFamily` | Font family |
+| `weight` / `fontWeight` | `"bold"`, `"medium"`, `"normal"`, or number |
+| `color` | Text color |
+| `textAlign` | `"left"`, `"center"`, `"right"`, `"justified"` |
+
+## Exporting to JSX
+
+Convert existing designs back to JSX:
+
+```sh
+open-pencil export design.fig -f jsx # OpenPencil format
+open-pencil export design.fig -f jsx --style tailwind # Tailwind classes
+```
+
+The round-trip works: export a design as JSX, modify the code, render it back.
+
+## Visual Diffing
+
+Because designs are representable as JSX, changes become code diffs:
+
+```diff
+
+- Old Title
++ New Title
+ Description
+
+```
+
+This makes design changes reviewable in pull requests, trackable in version control, and auditable in CI.
diff --git a/packages/docs/programmable/mcp-server.md b/packages/docs/programmable/mcp-server.md
new file mode 100644
index 000000000..634725bf1
--- /dev/null
+++ b/packages/docs/programmable/mcp-server.md
@@ -0,0 +1,251 @@
+# MCP Server
+
+OpenPencil includes an MCP (Model Context Protocol) server that lets AI coding tools — Claude Code, Cursor, Windsurf, etc. — read and modify `.fig` files headlessly.
+
+Two transports: **stdio** for MCP clients, **HTTP** for everything else.
+
+## Install
+
+```sh
+bun add -g @open-pencil/mcp
+```
+
+## Stdio (Claude Code, Cursor, etc.)
+
+Add to your MCP config (e.g. `~/.claude/settings.json` or `.cursor/mcp.json`):
+
+```json
+{
+ "mcpServers": {
+ "open-pencil": {
+ "command": "openpencil-mcp"
+ }
+ }
+}
+```
+
+Or run from source without installing:
+
+::: code-group
+```json [Bun]
+{
+ "mcpServers": {
+ "open-pencil": {
+ "command": "bun",
+ "args": ["/path/to/open-pencil/packages/mcp/src/index.ts"]
+ }
+ }
+}
+```
+```json [Node.js]
+{
+ "mcpServers": {
+ "open-pencil": {
+ "command": "npx",
+ "args": ["tsx", "/path/to/open-pencil/packages/mcp/src/index.ts"]
+ }
+ }
+}
+```
+:::
+
+## HTTP
+
+For browser extensions, scripts, CI, or any HTTP client:
+
+```sh
+openpencil-mcp-http
+```
+
+Or from source: `bun packages/mcp/src/http.ts` / `npx tsx packages/mcp/src/http.ts`
+
+Security defaults (HTTP transport):
+
+- Binds to `127.0.0.1` by default (`HOST` to override)
+- `eval` tool is disabled
+- File operations are limited to `OPENPENCIL_MCP_ROOT` (defaults to current working directory)
+- CORS is disabled by default; set `OPENPENCIL_MCP_CORS_ORIGIN` to allow one origin
+- Optional auth token: `OPENPENCIL_MCP_AUTH_TOKEN` (client sends `Authorization: Bearer ` or `x-mcp-token`)
+
+Server starts on port 3100 (override with `PORT` env var). Endpoints:
+
+- `GET /health` — server status
+- `POST /mcp` — MCP Streamable HTTP (SSE). Sessions via `mcp-session-id` header.
+
+## Workflow
+
+1. **Open** — `open_file` to load an existing `.fig`, or `new_document` for a blank canvas
+2. **Read** — `get_page_tree`, `find_nodes`, `get_node`, `list_pages`
+3. **Create** — `create_shape`, `render` (JSX)
+4. **Modify** — `set_fill`, `set_stroke`, `set_layout`, `update_node`, `set_effects`
+5. **Structure** — `reparent_node`, `group_nodes`, `clone_node`, `delete_node`
+6. **Save** — `save_file` to write back to `.fig`
+
+## AI Agent Skill
+
+Teach your AI coding agent to use OpenPencil tools:
+
+```sh
+npx skills add open-pencil/skills@open-pencil
+```
+
+Works with Claude Code, Cursor, Windsurf, Codex, and any agent that supports [skills](https://skills.sh). The skill covers the CLI, MCP tools, JSX rendering, eval, and the running app's automation bridge.
+
+## Tools (90)
+
+### Document
+
+| Tool | Description |
+|------|-------------|
+| `open_file` | Open a `.fig` file for editing |
+| `save_file` | Save the current document to a `.fig` file |
+| `new_document` | Create a new empty document |
+
+### Read
+
+| Tool | Description |
+|------|-------------|
+| `get_selection` | Get currently selected nodes |
+| `get_page_tree` | Get the full node tree of the current page |
+| `get_current_page` | Get the current page name and ID |
+| `get_node` | Get detailed properties of a node by ID |
+| `find_nodes` | Find nodes by name pattern and/or type |
+| `get_components` | List all components in the document |
+| `list_pages` | List all pages |
+| `list_variables` | List design variables |
+| `list_collections` | List variable collections |
+| `list_fonts` | List fonts used in the current page |
+| `page_bounds` | Get bounding box of all objects on the current page |
+| `node_bounds` | Get bounding box of a node |
+| `node_ancestors` | Get ancestor chain of a node |
+| `node_children` | Get direct children of a node |
+| `node_tree` | Get the subtree rooted at a node |
+| `node_bindings` | Get variable bindings on a node |
+
+### Create
+
+| Tool | Description |
+|------|-------------|
+| `create_shape` | Create a shape (FRAME, RECTANGLE, ELLIPSE, TEXT, LINE, STAR, POLYGON, SECTION) |
+| `create_vector` | Create a vector node from a path string |
+| `create_slice` | Create an export slice |
+| `create_page` | Create a new page |
+| `render` | Render JSX to design nodes — create entire component trees in one call |
+| `create_component` | Convert a frame/group into a component |
+| `create_instance` | Create an instance of a component |
+| `node_to_component` | Convert an existing node into a component in-place |
+
+### Modify
+
+| Tool | Description |
+|------|-------------|
+| `set_fill` | Set fill color (hex) |
+| `set_stroke` | Set stroke color, weight, alignment |
+| `set_effects` | Add shadow or blur effects |
+| `update_node` | Update position, size, opacity, corner radius, text, font |
+| `set_layout` | Set auto-layout (flexbox) — direction, spacing, padding, alignment |
+| `set_constraints` | Set resize constraints |
+| `set_rotation` | Set rotation angle in degrees |
+| `set_opacity` | Set opacity (0–1) |
+| `set_radius` | Set corner radius (uniform or per-corner) |
+| `set_minmax` | Set min/max width and height constraints |
+| `set_text` | Set text content of a TEXT node |
+| `set_font` | Set font family and weight |
+| `set_font_range` | Set font properties on a character range |
+| `set_text_resize` | Set text auto-resize mode (fixed/auto-width/auto-height) |
+| `set_visible` | Show or hide a node |
+| `set_blend` | Set blend mode |
+| `set_locked` | Lock or unlock a node |
+| `set_stroke_align` | Set stroke alignment (inside/center/outside) |
+| `set_text_properties` | Set text layout: alignment, auto-resize, text case, decoration, truncation |
+| `set_layout_child` | Configure auto-layout child: sizing, grow, alignment, absolute positioning |
+| `node_move` | Move a node to a new position |
+| `node_resize` | Resize a node |
+| `node_replace_with` | Replace a node with another node |
+| `arrange` | Align or distribute selected nodes |
+
+### Structure
+
+| Tool | Description |
+|------|-------------|
+| `delete_node` | Delete a node |
+| `clone_node` | Duplicate a node |
+| `rename_node` | Rename a node |
+| `reparent_node` | Move a node into a different parent |
+| `select_nodes` | Select nodes by ID |
+| `group_nodes` | Group nodes |
+| `ungroup_node` | Ungroup a group |
+| `flatten_nodes` | Flatten nodes into a single vector |
+| `boolean_union` | Boolean union of two or more nodes |
+| `boolean_subtract` | Boolean subtraction |
+| `boolean_intersect` | Boolean intersection |
+| `boolean_exclude` | Boolean exclusion |
+
+### Vector Path
+
+| Tool | Description |
+|------|-------------|
+| `path_get` | Get the path data of a vector node |
+| `path_set` | Set the path data of a vector node |
+| `path_scale` | Scale a vector path |
+| `path_flip` | Flip a vector path horizontally or vertically |
+| `path_move` | Translate a vector path |
+
+### Export
+
+| Tool | Description |
+|------|-------------|
+| `export_image` | Export nodes as PNG, JPG, or WEBP. Returns base64-encoded image data |
+| `export_svg` | Export nodes as SVG markup |
+
+### Viewport
+
+| Tool | Description |
+|------|-------------|
+| `viewport_get` | Get current viewport position and zoom level |
+| `viewport_set` | Set viewport position and zoom |
+| `viewport_zoom_to_fit` | Zoom viewport to fit specified nodes |
+
+### Variables
+
+| Tool | Description |
+|------|-------------|
+| `get_variable` | Get a variable by ID or name |
+| `find_variables` | Find variables by name pattern or type |
+| `create_variable` | Create a new variable in a collection |
+| `set_variable` | Set a variable value in a mode |
+| `delete_variable` | Delete a variable |
+| `bind_variable` | Bind a variable to a node property |
+| `get_collection` | Get a variable collection by ID or name |
+| `create_collection` | Create a new variable collection |
+| `delete_collection` | Delete a variable collection |
+
+### Analyze
+
+| Tool | Description |
+|------|-------------|
+| `analyze_colors` | Analyze color palette usage across the document |
+| `analyze_typography` | Analyze font/size/weight distribution |
+| `analyze_spacing` | Analyze gap and padding values |
+| `analyze_clusters` | Detect repeated patterns (potential components) |
+
+### Diff
+
+| Tool | Description |
+|------|-------------|
+| `diff_create` | Create a snapshot of the current document state |
+| `diff_show` | Show differences between the current state and a snapshot |
+
+### Navigation
+
+| Tool | Description |
+|------|-------------|
+| `switch_page` | Switch to a page by name or ID |
+
+### Escape Hatch
+
+| Tool | Description |
+|------|-------------|
+| `eval` | Execute JavaScript with full Figma Plugin API access |
+
+Note: `eval` is available over stdio, but disabled in HTTP mode for security.