Revert mermaid plugin (crashes VitePress dev), use tables instead

vitepress-plugin-mermaid patches vitepress's client app entry,
which breaks ESM module resolution and renders all pages blank.
Replace mermaid diagrams with offset/content tables and pipeline
code blocks that render correctly.
This commit is contained in:
Danila Poyarkov 2026-03-08 14:33:56 +03:00
parent 8d140eef7c
commit 16033c4c56
10 changed files with 143 additions and 310 deletions

View file

@ -122,9 +122,7 @@
"name": "@open-pencil/docs",
"version": "0.1.0",
"devDependencies": {
"mermaid": "11.4.1",
"vitepress": "^1.6.4",
"vitepress-plugin-mermaid": "^2.0.17",
},
},
"packages/mcp": {
@ -644,8 +642,6 @@
"@libp2p/websockets": ["@libp2p/websockets@9.2.16", "", { "dependencies": { "@libp2p/interface": "^2.10.4", "@libp2p/utils": "^6.7.0", "@multiformats/multiaddr": "^12.4.4", "@multiformats/multiaddr-matcher": "^1.7.2", "@multiformats/multiaddr-to-uri": "^11.0.0", "@types/ws": "^8.18.1", "it-ws": "^6.1.5", "main-event": "^1.0.1", "p-defer": "^4.0.1", "p-event": "^6.0.1", "progress-events": "^1.0.1", "race-signal": "^1.1.3", "ws": "^8.18.2" } }, "sha512-jD96ClKeaZvTs+YyGJxOOQSWVe9e9SHmacJ9uE9dqWZjCbPICdCJnOj2pLg258WxVrc+/MRFKSHE/v5f2ZJGCA=="],
"@mermaid-js/mermaid-mindmap": ["@mermaid-js/mermaid-mindmap@9.3.0", "", { "dependencies": { "@braintree/sanitize-url": "^6.0.0", "cytoscape": "^3.23.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.1.0", "d3": "^7.0.0", "khroma": "^2.0.0", "non-layered-tidy-tree-layout": "^2.0.2" } }, "sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw=="],
"@mermaid-js/parser": ["@mermaid-js/parser@0.3.0", "", { "dependencies": { "langium": "3.0.0" } }, "sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
@ -2058,8 +2054,6 @@
"node-sarif-builder": ["node-sarif-builder@3.4.0", "", { "dependencies": { "@types/sarif": "^2.1.7", "fs-extra": "^11.1.1" } }, "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg=="],
"non-layered-tidy-tree-layout": ["non-layered-tidy-tree-layout@2.0.2", "", {}, "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="],
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"number-allocator": ["number-allocator@1.0.14", "", { "dependencies": { "debug": "^4.3.1", "js-sdsl": "4.3.0" } }, "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA=="],
@ -2506,8 +2500,6 @@
"vitepress": ["vitepress@1.6.4", "", { "dependencies": { "@docsearch/css": "3.8.2", "@docsearch/js": "3.8.2", "@iconify-json/simple-icons": "^1.2.21", "@shikijs/core": "^2.1.0", "@shikijs/transformers": "^2.1.0", "@shikijs/types": "^2.1.0", "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^5.2.1", "@vue/devtools-api": "^7.7.0", "@vue/shared": "^3.5.13", "@vueuse/core": "^12.4.0", "@vueuse/integrations": "^12.4.0", "focus-trap": "^7.6.4", "mark.js": "8.11.1", "minisearch": "^7.1.1", "shiki": "^2.1.0", "vite": "^5.4.14", "vue": "^3.5.13" }, "peerDependencies": { "markdown-it-mathjax3": "^4", "postcss": "^8" }, "optionalPeers": ["markdown-it-mathjax3", "postcss"], "bin": { "vitepress": "bin/vitepress.js" } }, "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg=="],
"vitepress-plugin-mermaid": ["vitepress-plugin-mermaid@2.0.17", "", { "optionalDependencies": { "@mermaid-js/mermaid-mindmap": "^9.3.0" }, "peerDependencies": { "mermaid": "10 || 11", "vitepress": "^1.0.0 || ^1.0.0-alpha" } }, "sha512-IUzYpwf61GC6k0XzfmAmNrLvMi9TRrVRMsUyCA8KNXhg/mQ1VqWnO0/tBVPiX5UoKF1mDUwqn5QV4qAJl6JnUg=="],
"void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="],
"vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
@ -2650,8 +2642,6 @@
"@libp2p/crypto/@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
"@mermaid-js/mermaid-mindmap/@braintree/sanitize-url": ["@braintree/sanitize-url@6.0.4", "", {}, "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="],
"@modelcontextprotocol/sdk/@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="],
"@modelcontextprotocol/sdk/hono": ["hono@4.12.3", "", {}, "sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg=="],

View file

@ -1,5 +1,4 @@
import { defineConfig, type DefaultTheme } from 'vitepress'
import { withMermaid } from 'vitepress-plugin-mermaid'
interface SidebarLabels {
gettingAround: string
@ -207,7 +206,7 @@ const LOCALES: Record<string, { hreflang: string; ogLocale: string; prefix: stri
ru: { hreflang: 'ru', ogLocale: 'ru_RU', prefix: '/ru' },
}
export default withMermaid(defineConfig({
export default defineConfig({
title: 'OpenPencil',
description: 'Open-source, AI-native design editor. Figma alternative built from scratch with full .fig file compatibility.',
cleanUrls: true,
@ -376,4 +375,4 @@ export default withMermaid(defineConfig({
message: 'Released under the MIT License.',
},
},
}))
})

View file

@ -4,41 +4,30 @@
A `.fig` file is a ZIP archive containing a Kiwi-encoded binary message:
```mermaid
block-beta
columns 1
A["Magic header: <code>fig-kiwi</code> (8 bytes)"]
B["Version (4 bytes, uint32 LE)"]
C["Schema length (4 bytes, uint32 LE)"]
D["Compressed Kiwi schema"]
E["Message length (4 bytes, uint32 LE)"]
F["Compressed Kiwi message — NodeChange[]"]
G["Blob data — images, vector networks, fonts"]
```
| Offset | Content |
|--------|---------|
| 0 | Magic header `fig-kiwi` (8 bytes) |
| 8 | Version (4 bytes, uint32 LE) |
| 12 | Schema length (4 bytes, uint32 LE) |
| 16 | Compressed Kiwi schema |
| … | Message length (4 bytes, uint32 LE) |
| … | Compressed Kiwi message — `NodeChange[]` (entire document) |
| … | Blob data — images, vector networks, fonts |
## Import Pipeline
```mermaid
flowchart LR
A[".fig file"] --> B["Parse header"]
B --> C["Decompress Zstd"]
C --> D["Decode Kiwi schema"]
D --> E["Decode message"]
E --> F["NodeChange[]"]
F --> G["Build SceneGraph"]
G --> H["Resolve blob refs"]
H --> I["Render"]
```
.fig file → parse header → decompress Zstd → decode Kiwi schema
→ decode message → NodeChange[] → build SceneGraph
→ resolve blob refs → render on canvas
```
## Export Pipeline
```mermaid
flowchart LR
A["SceneGraph"] --> B["NodeChange[]"]
B --> C["Kiwi encode"]
C --> D["Compress"]
D --> E["Build ZIP"]
E --> F[".fig file"]
```
SceneGraph → NodeChange[] → Kiwi encode → compress (Zstd/deflate)
→ build ZIP (header + schema + message + thumbnail.png)
→ write .fig file
```
Export uses <kbd></kbd><kbd>S</kbd> (Save) and <kbd></kbd><kbd></kbd><kbd>S</kbd> (Save As) with native OS dialogs on the desktop app. The exported file includes a `thumbnail.png` required by Figma for file preview.
@ -77,18 +66,7 @@ Figma's schema uses non-contiguous field IDs (e.g. 1, 2, 5, 10 with gaps). The k
Copy/paste uses the same Kiwi binary encoding:
```mermaid
flowchart LR
subgraph Copy
A["Selected nodes"] --> B["Encode NodeChange[]"]
B --> C["Compress"]
C --> D["Clipboard<br/><code>application/x-figma-design</code>"]
end
subgraph Paste
D --> E["Decompress"]
E --> F["Decode Kiwi"]
F --> G["Create nodes"]
end
```
1. **Copy** — encode selected `NodeChange[]` to Kiwi binary, compress, write to clipboard as `application/x-figma-design` MIME type
2. **Paste** — read clipboard, decompress, decode Kiwi binary, create nodes in scene graph
This enables bidirectional clipboard between OpenPencil and Figma.
Encoding happens synchronously in the copy event handler (not async Clipboard API) for browser compatibility. This enables bidirectional clipboard between OpenPencil and Figma.

View file

@ -4,41 +4,30 @@
A `.fig` file is a ZIP archive containing a Kiwi-encoded binary message:
```mermaid
block-beta
columns 1
A["Magic header: <code>fig-kiwi</code> (8 bytes)"]
B["Version (4 bytes, uint32 LE)"]
C["Schema length (4 bytes, uint32 LE)"]
D["Compressed Kiwi schema"]
E["Message length (4 bytes, uint32 LE)"]
F["Compressed Kiwi message — NodeChange[]"]
G["Blob data — images, vector networks, fonts"]
```
| Offset | Content |
|--------|---------|
| 0 | Magic header `fig-kiwi` (8 bytes) |
| 8 | Version (4 bytes, uint32 LE) |
| 12 | Schema length (4 bytes, uint32 LE) |
| 16 | Compressed Kiwi schema |
| … | Message length (4 bytes, uint32 LE) |
| … | Compressed Kiwi message — `NodeChange[]` (entire document) |
| … | Blob data — images, vector networks, fonts |
## Import Pipeline
```mermaid
flowchart LR
A[".fig file"] --> B["Parse header"]
B --> C["Decompress Zstd"]
C --> D["Decode Kiwi schema"]
D --> E["Decode message"]
E --> F["NodeChange[]"]
F --> G["Build SceneGraph"]
G --> H["Resolve blob refs"]
H --> I["Render"]
```
.fig file → parse header → decompress Zstd → decode Kiwi schema
→ decode message → NodeChange[] → build SceneGraph
→ resolve blob refs → render on canvas
```
## Export Pipeline
```mermaid
flowchart LR
A["SceneGraph"] --> B["NodeChange[]"]
B --> C["Kiwi encode"]
C --> D["Compress"]
D --> E["Build ZIP"]
E --> F[".fig file"]
```
SceneGraph → NodeChange[] → Kiwi encode → compress (Zstd/deflate)
→ build ZIP (header + schema + message + thumbnail.png)
→ write .fig file
```
Export uses <kbd></kbd><kbd>S</kbd> (Save) and <kbd></kbd><kbd></kbd><kbd>S</kbd> (Save As) with native OS dialogs on the desktop app. The exported file includes a `thumbnail.png` required by Figma for file preview.
@ -77,18 +66,7 @@ Figma's schema uses non-contiguous field IDs (e.g. 1, 2, 5, 10 with gaps). The k
Copy/paste uses the same Kiwi binary encoding:
```mermaid
flowchart LR
subgraph Copy
A["Selected nodes"] --> B["Encode NodeChange[]"]
B --> C["Compress"]
C --> D["Clipboard<br/><code>application/x-figma-design</code>"]
end
subgraph Paste
D --> E["Decompress"]
E --> F["Decode Kiwi"]
F --> G["Create nodes"]
end
```
1. **Copy** — encode selected `NodeChange[]` to Kiwi binary, compress, write to clipboard as `application/x-figma-design` MIME type
2. **Paste** — read clipboard, decompress, decode Kiwi binary, create nodes in scene graph
This enables bidirectional clipboard between OpenPencil and Figma.
Encoding happens synchronously in the copy event handler (not async Clipboard API) for browser compatibility. This enables bidirectional clipboard between OpenPencil and Figma.

View file

@ -4,41 +4,30 @@
A `.fig` file is a ZIP archive containing a Kiwi-encoded binary message:
```mermaid
block-beta
columns 1
A["Magic header: <code>fig-kiwi</code> (8 bytes)"]
B["Version (4 bytes, uint32 LE)"]
C["Schema length (4 bytes, uint32 LE)"]
D["Compressed Kiwi schema"]
E["Message length (4 bytes, uint32 LE)"]
F["Compressed Kiwi message — NodeChange[]"]
G["Blob data — images, vector networks, fonts"]
```
| Offset | Content |
|--------|---------|
| 0 | Magic header `fig-kiwi` (8 bytes) |
| 8 | Version (4 bytes, uint32 LE) |
| 12 | Schema length (4 bytes, uint32 LE) |
| 16 | Compressed Kiwi schema |
| … | Message length (4 bytes, uint32 LE) |
| … | Compressed Kiwi message — `NodeChange[]` (entire document) |
| … | Blob data — images, vector networks, fonts |
## Import Pipeline
```mermaid
flowchart LR
A[".fig file"] --> B["Parse header"]
B --> C["Decompress Zstd"]
C --> D["Decode Kiwi schema"]
D --> E["Decode message"]
E --> F["NodeChange[]"]
F --> G["Build SceneGraph"]
G --> H["Resolve blob refs"]
H --> I["Render"]
```
.fig file → parse header → decompress Zstd → decode Kiwi schema
→ decode message → NodeChange[] → build SceneGraph
→ resolve blob refs → render on canvas
```
## Export Pipeline
```mermaid
flowchart LR
A["SceneGraph"] --> B["NodeChange[]"]
B --> C["Kiwi encode"]
C --> D["Compress"]
D --> E["Build ZIP"]
E --> F[".fig file"]
```
SceneGraph → NodeChange[] → Kiwi encode → compress (Zstd/deflate)
→ build ZIP (header + schema + message + thumbnail.png)
→ write .fig file
```
Export uses <kbd></kbd><kbd>S</kbd> (Save) and <kbd></kbd><kbd></kbd><kbd>S</kbd> (Save As) with native OS dialogs on the desktop app. The exported file includes a `thumbnail.png` required by Figma for file preview.
@ -77,18 +66,7 @@ Figma's schema uses non-contiguous field IDs (e.g. 1, 2, 5, 10 with gaps). The k
Copy/paste uses the same Kiwi binary encoding:
```mermaid
flowchart LR
subgraph Copy
A["Selected nodes"] --> B["Encode NodeChange[]"]
B --> C["Compress"]
C --> D["Clipboard<br/><code>application/x-figma-design</code>"]
end
subgraph Paste
D --> E["Decompress"]
E --> F["Decode Kiwi"]
F --> G["Create nodes"]
end
```
1. **Copy** — encode selected `NodeChange[]` to Kiwi binary, compress, write to clipboard as `application/x-figma-design` MIME type
2. **Paste** — read clipboard, decompress, decode Kiwi binary, create nodes in scene graph
This enables bidirectional clipboard between OpenPencil and Figma.
Encoding happens synchronously in the copy event handler (not async Clipboard API) for browser compatibility. This enables bidirectional clipboard between OpenPencil and Figma.

View file

@ -4,41 +4,30 @@
A `.fig` file is a ZIP archive containing a Kiwi-encoded binary message:
```mermaid
block-beta
columns 1
A["Magic header: <code>fig-kiwi</code> (8 bytes)"]
B["Version (4 bytes, uint32 LE)"]
C["Schema length (4 bytes, uint32 LE)"]
D["Compressed Kiwi schema"]
E["Message length (4 bytes, uint32 LE)"]
F["Compressed Kiwi message — NodeChange[]"]
G["Blob data — images, vector networks, fonts"]
```
| Offset | Content |
|--------|---------|
| 0 | Magic header `fig-kiwi` (8 bytes) |
| 8 | Version (4 bytes, uint32 LE) |
| 12 | Schema length (4 bytes, uint32 LE) |
| 16 | Compressed Kiwi schema |
| … | Message length (4 bytes, uint32 LE) |
| … | Compressed Kiwi message — `NodeChange[]` (entire document) |
| … | Blob data — images, vector networks, fonts |
## Import Pipeline
```mermaid
flowchart LR
A[".fig file"] --> B["Parse header"]
B --> C["Decompress Zstd"]
C --> D["Decode Kiwi schema"]
D --> E["Decode message"]
E --> F["NodeChange[]"]
F --> G["Build SceneGraph"]
G --> H["Resolve blob refs"]
H --> I["Render"]
```
.fig file → parse header → decompress Zstd → decode Kiwi schema
→ decode message → NodeChange[] → build SceneGraph
→ resolve blob refs → render on canvas
```
## Export Pipeline
```mermaid
flowchart LR
A["SceneGraph"] --> B["NodeChange[]"]
B --> C["Kiwi encode"]
C --> D["Compress"]
D --> E["Build ZIP"]
E --> F[".fig file"]
```
SceneGraph → NodeChange[] → Kiwi encode → compress (Zstd/deflate)
→ build ZIP (header + schema + message + thumbnail.png)
→ write .fig file
```
Export uses <kbd></kbd><kbd>S</kbd> (Save) and <kbd></kbd><kbd></kbd><kbd>S</kbd> (Save As) with native OS dialogs on the desktop app. The exported file includes a `thumbnail.png` required by Figma for file preview.
@ -77,18 +66,7 @@ Figma's schema uses non-contiguous field IDs (e.g. 1, 2, 5, 10 with gaps). The k
Copy/paste uses the same Kiwi binary encoding:
```mermaid
flowchart LR
subgraph Copy
A["Selected nodes"] --> B["Encode NodeChange[]"]
B --> C["Compress"]
C --> D["Clipboard<br/><code>application/x-figma-design</code>"]
end
subgraph Paste
D --> E["Decompress"]
E --> F["Decode Kiwi"]
F --> G["Create nodes"]
end
```
1. **Copy** — encode selected `NodeChange[]` to Kiwi binary, compress, write to clipboard as `application/x-figma-design` MIME type
2. **Paste** — read clipboard, decompress, decode Kiwi binary, create nodes in scene graph
This enables bidirectional clipboard between OpenPencil and Figma.
Encoding happens synchronously in the copy event handler (not async Clipboard API) for browser compatibility. This enables bidirectional clipboard between OpenPencil and Figma.

View file

@ -10,8 +10,6 @@
"preview": "vitepress preview"
},
"devDependencies": {
"mermaid": "11.4.1",
"vitepress": "^1.6.4",
"vitepress-plugin-mermaid": "^2.0.17"
"vitepress": "^1.6.4"
}
}

View file

@ -4,41 +4,30 @@
A `.fig` file is a ZIP archive containing a Kiwi-encoded binary message:
```mermaid
block-beta
columns 1
A["Magic header: <code>fig-kiwi</code> (8 bytes)"]
B["Version (4 bytes, uint32 LE)"]
C["Schema length (4 bytes, uint32 LE)"]
D["Compressed Kiwi schema"]
E["Message length (4 bytes, uint32 LE)"]
F["Compressed Kiwi message — NodeChange[]"]
G["Blob data — images, vector networks, fonts"]
```
| Offset | Content |
|--------|---------|
| 0 | Magic header `fig-kiwi` (8 bytes) |
| 8 | Version (4 bytes, uint32 LE) |
| 12 | Schema length (4 bytes, uint32 LE) |
| 16 | Compressed Kiwi schema |
| … | Message length (4 bytes, uint32 LE) |
| … | Compressed Kiwi message — `NodeChange[]` (entire document) |
| … | Blob data — images, vector networks, fonts |
## Import Pipeline
```mermaid
flowchart LR
A[".fig file"] --> B["Parse header"]
B --> C["Decompress Zstd"]
C --> D["Decode Kiwi schema"]
D --> E["Decode message"]
E --> F["NodeChange[]"]
F --> G["Build SceneGraph"]
G --> H["Resolve blob refs"]
H --> I["Render"]
```
.fig file → parse header → decompress Zstd → decode Kiwi schema
→ decode message → NodeChange[] → build SceneGraph
→ resolve blob refs → render on canvas
```
## Export Pipeline
```mermaid
flowchart LR
A["SceneGraph"] --> B["NodeChange[]"]
B --> C["Kiwi encode"]
C --> D["Compress"]
D --> E["Build ZIP"]
E --> F[".fig file"]
```
SceneGraph → NodeChange[] → Kiwi encode → compress (Zstd/deflate)
→ build ZIP (header + schema + message + thumbnail.png)
→ write .fig file
```
Export uses <kbd></kbd><kbd>S</kbd> (Save) and <kbd></kbd><kbd></kbd><kbd>S</kbd> (Save As) with native OS dialogs on the desktop app. The exported file includes a `thumbnail.png` required by Figma for file preview.
@ -77,18 +66,7 @@ Figma's schema uses non-contiguous field IDs (e.g. 1, 2, 5, 10 with gaps). The k
Copy/paste uses the same Kiwi binary encoding:
```mermaid
flowchart LR
subgraph Copy
A["Selected nodes"] --> B["Encode NodeChange[]"]
B --> C["Compress"]
C --> D["Clipboard<br/><code>application/x-figma-design</code>"]
end
subgraph Paste
D --> E["Decompress"]
E --> F["Decode Kiwi"]
F --> G["Create nodes"]
end
```
1. **Copy** — encode selected `NodeChange[]` to Kiwi binary, compress, write to clipboard as `application/x-figma-design` MIME type
2. **Paste** — read clipboard, decompress, decode Kiwi binary, create nodes in scene graph
This enables bidirectional clipboard between OpenPencil and Figma.
Encoding happens synchronously in the copy event handler (not async Clipboard API) for browser compatibility. This enables bidirectional clipboard between OpenPencil and Figma.

View file

@ -4,41 +4,30 @@
A `.fig` file is a ZIP archive containing a Kiwi-encoded binary message:
```mermaid
block-beta
columns 1
A["Magic header: <code>fig-kiwi</code> (8 bytes)"]
B["Version (4 bytes, uint32 LE)"]
C["Schema length (4 bytes, uint32 LE)"]
D["Compressed Kiwi schema"]
E["Message length (4 bytes, uint32 LE)"]
F["Compressed Kiwi message — NodeChange[]"]
G["Blob data — images, vector networks, fonts"]
```
| Offset | Content |
|--------|---------|
| 0 | Magic header `fig-kiwi` (8 bytes) |
| 8 | Version (4 bytes, uint32 LE) |
| 12 | Schema length (4 bytes, uint32 LE) |
| 16 | Compressed Kiwi schema |
| … | Message length (4 bytes, uint32 LE) |
| … | Compressed Kiwi message — `NodeChange[]` (entire document) |
| … | Blob data — images, vector networks, fonts |
## Import Pipeline
```mermaid
flowchart LR
A[".fig file"] --> B["Parse header"]
B --> C["Decompress Zstd"]
C --> D["Decode Kiwi schema"]
D --> E["Decode message"]
E --> F["NodeChange[]"]
F --> G["Build SceneGraph"]
G --> H["Resolve blob refs"]
H --> I["Render"]
```
.fig file → parse header → decompress Zstd → decode Kiwi schema
→ decode message → NodeChange[] → build SceneGraph
→ resolve blob refs → render on canvas
```
## Export Pipeline
```mermaid
flowchart LR
A["SceneGraph"] --> B["NodeChange[]"]
B --> C["Kiwi encode"]
C --> D["Compress"]
D --> E["Build ZIP"]
E --> F[".fig file"]
```
SceneGraph → NodeChange[] → Kiwi encode → compress (Zstd/deflate)
→ build ZIP (header + schema + message + thumbnail.png)
→ write .fig file
```
Export uses <kbd></kbd><kbd>S</kbd> (Save) and <kbd></kbd><kbd></kbd><kbd>S</kbd> (Save As) with native OS dialogs on the desktop app. The exported file includes a `thumbnail.png` required by Figma for file preview.
@ -77,18 +66,7 @@ Figma's schema uses non-contiguous field IDs (e.g. 1, 2, 5, 10 with gaps). The k
Copy/paste uses the same Kiwi binary encoding:
```mermaid
flowchart LR
subgraph Copy
A["Selected nodes"] --> B["Encode NodeChange[]"]
B --> C["Compress"]
C --> D["Clipboard<br/><code>application/x-figma-design</code>"]
end
subgraph Paste
D --> E["Decompress"]
E --> F["Decode Kiwi"]
F --> G["Create nodes"]
end
```
1. **Copy** — encode selected `NodeChange[]` to Kiwi binary, compress, write to clipboard as `application/x-figma-design` MIME type
2. **Paste** — read clipboard, decompress, decode Kiwi binary, create nodes in scene graph
This enables bidirectional clipboard between OpenPencil and Figma.
Encoding happens synchronously in the copy event handler (not async Clipboard API) for browser compatibility. This enables bidirectional clipboard between OpenPencil and Figma.

View file

@ -4,41 +4,30 @@
A `.fig` file is a ZIP archive containing a Kiwi-encoded binary message:
```mermaid
block-beta
columns 1
A["Magic header: <code>fig-kiwi</code> (8 bytes)"]
B["Version (4 bytes, uint32 LE)"]
C["Schema length (4 bytes, uint32 LE)"]
D["Compressed Kiwi schema"]
E["Message length (4 bytes, uint32 LE)"]
F["Compressed Kiwi message — NodeChange[]"]
G["Blob data — images, vector networks, fonts"]
```
| Offset | Content |
|--------|---------|
| 0 | Magic header `fig-kiwi` (8 bytes) |
| 8 | Version (4 bytes, uint32 LE) |
| 12 | Schema length (4 bytes, uint32 LE) |
| 16 | Compressed Kiwi schema |
| … | Message length (4 bytes, uint32 LE) |
| … | Compressed Kiwi message — `NodeChange[]` (entire document) |
| … | Blob data — images, vector networks, fonts |
## Import Pipeline
```mermaid
flowchart LR
A[".fig file"] --> B["Parse header"]
B --> C["Decompress Zstd"]
C --> D["Decode Kiwi schema"]
D --> E["Decode message"]
E --> F["NodeChange[]"]
F --> G["Build SceneGraph"]
G --> H["Resolve blob refs"]
H --> I["Render"]
```
.fig file → parse header → decompress Zstd → decode Kiwi schema
→ decode message → NodeChange[] → build SceneGraph
→ resolve blob refs → render on canvas
```
## Export Pipeline
```mermaid
flowchart LR
A["SceneGraph"] --> B["NodeChange[]"]
B --> C["Kiwi encode"]
C --> D["Compress"]
D --> E["Build ZIP"]
E --> F[".fig file"]
```
SceneGraph → NodeChange[] → Kiwi encode → compress (Zstd/deflate)
→ build ZIP (header + schema + message + thumbnail.png)
→ write .fig file
```
Export uses <kbd></kbd><kbd>S</kbd> (Save) and <kbd></kbd><kbd></kbd><kbd>S</kbd> (Save As) with native OS dialogs on the desktop app. The exported file includes a `thumbnail.png` required by Figma for file preview.
@ -77,18 +66,7 @@ Figma's schema uses non-contiguous field IDs (e.g. 1, 2, 5, 10 with gaps). The k
Copy/paste uses the same Kiwi binary encoding:
```mermaid
flowchart LR
subgraph Copy
A["Selected nodes"] --> B["Encode NodeChange[]"]
B --> C["Compress"]
C --> D["Clipboard<br/><code>application/x-figma-design</code>"]
end
subgraph Paste
D --> E["Decompress"]
E --> F["Decode Kiwi"]
F --> G["Create nodes"]
end
```
1. **Copy** — encode selected `NodeChange[]` to Kiwi binary, compress, write to clipboard as `application/x-figma-design` MIME type
2. **Paste** — read clipboard, decompress, decode Kiwi binary, create nodes in scene graph
This enables bidirectional clipboard between OpenPencil and Figma.
Encoding happens synchronously in the copy event handler (not async Clipboard API) for browser compatibility. This enables bidirectional clipboard between OpenPencil and Figma.