- Cloudflare Worker + Durable Object server (packages/collab/) - One DO per room with WebSocket Hibernation for near-zero idle cost - Yjs sync protocol for document state (CRDT merge) - Yjs awareness protocol for cursors, selection, presence - Persists doc state to DO storage - CORS headers for cross-origin WebSocket upgrade - Client composable (src/composables/use-collab.ts) - Y.Doc ↔ SceneGraph bidirectional binding - Awareness: broadcasts cursor position, selection, name, color - Auto-join from /share/:roomId URL - Suppresses echo loops between Yjs and graph updates - CollabPanel component (src/components/CollabPanel.vue) - Share button generates room link, copies to clipboard - Join flow: paste link or room ID - Avatar stack with colored initials and tooltips - Name input persisted to localStorage - Remote cursor rendering in CanvasKit - Colored arrow cursor + name label pill per remote peer - Remote selection outlines in peer's color - Only shows cursors on the same page - SPA redirect for /share/* routes (Cloudflare Pages _redirects)
80 lines
2.6 KiB
JSON
80 lines
2.6 KiB
JSON
{
|
|
"name": "open-pencil-app",
|
|
"private": true,
|
|
"version": "0.1.0",
|
|
"type": "module",
|
|
"workspaces": [
|
|
"packages/*"
|
|
],
|
|
"scripts": {
|
|
"dev": "vite",
|
|
"build": "tsc && vite build",
|
|
"preview": "vite preview",
|
|
"tauri": "tauri",
|
|
"lint": "oxlint -c oxlint.json src/",
|
|
"format": "oxfmt --write src/",
|
|
"typecheck": "tsgo --noEmit",
|
|
"check": "bun run lint && bun run typecheck",
|
|
"test": "playwright test --project=openpencil",
|
|
"test:update": "playwright test --project=openpencil --update-snapshots",
|
|
"test:figma": "playwright test --project=figma",
|
|
"figma:debug": "open -a Figma --args --remote-debugging-port=9222",
|
|
"test:unit": "bun test ./tests/engine",
|
|
"test:coverage": "bun test --coverage ./tests/engine",
|
|
"test:dupes": "jscpd packages/core/src packages/cli/src src --min-lines 5 --min-tokens 50 --format typescript",
|
|
"open-pencil": "bun packages/cli/src/index.ts",
|
|
"docs:dev": "bun --filter @open-pencil/docs dev",
|
|
"docs:build": "bun --filter @open-pencil/docs build",
|
|
"docs:preview": "bun --filter @open-pencil/docs preview"
|
|
},
|
|
"dependencies": {
|
|
"@ai-sdk/openai": "^3.0.37",
|
|
"@ai-sdk/valibot": "^2.0.17",
|
|
"@ai-sdk/vue": "^3.0.105",
|
|
"@open-pencil/cli": "workspace:*",
|
|
"@open-pencil/core": "workspace:*",
|
|
"@openrouter/ai-sdk-provider": "^2.2.3",
|
|
"@tailwindcss/vite": "^4.2.1",
|
|
"@tanstack/vue-table": "^8.21.3",
|
|
"@tauri-apps/api": "^2",
|
|
"@tauri-apps/plugin-dialog": "^2.6.0",
|
|
"@tauri-apps/plugin-fs": "^2.4.5",
|
|
"@tauri-apps/plugin-opener": "^2",
|
|
"@vueuse/core": "^14.2.1",
|
|
"ai": "^6.0.105",
|
|
"canvaskit-wasm": "^0.40.0",
|
|
"culori": "^4.0.2",
|
|
"dedent": "^1.7.1",
|
|
"fflate": "^0.8.2",
|
|
"fzstd": "^0.1.1",
|
|
"lib0": "^0.2.117",
|
|
"prismjs": "^1.30.0",
|
|
"reka-ui": "^2.8.2",
|
|
"tailwindcss": "^4.2.1",
|
|
"valibot": "^1.2.0",
|
|
"vue": "^3.5.29",
|
|
"vue-stream-markdown": "^0.6.0",
|
|
"y-protocols": "^1.0.7",
|
|
"yjs": "^13.6.29",
|
|
"yoga-layout": "^3.2.1"
|
|
},
|
|
"devDependencies": {
|
|
"@iconify-json/lucide": "^1.2.94",
|
|
"@playwright/test": "^1.58.2",
|
|
"@tauri-apps/cli": "^2",
|
|
"@types/culori": "^4.0.1",
|
|
"@types/node": "^25.3.2",
|
|
"@types/prismjs": "^1.26.6",
|
|
"@typescript/native-preview": "^7.0.0-dev.20260228.1",
|
|
"@vitejs/plugin-vue": "^6.0.4",
|
|
"esbuild": "^0.27.3",
|
|
"jscpd": "^4.0.8",
|
|
"oxfmt": "^0.35.0",
|
|
"oxlint": "^1.50.0",
|
|
"tw-animate-css": "^1.4.0",
|
|
"typescript": "~5.8.3",
|
|
"unplugin-icons": "^23.0.1",
|
|
"unplugin-vue-components": "^31.0.0",
|
|
"vite": "^7.0.4"
|
|
}
|
|
} |