Commit graph

58 commits

Author SHA1 Message Date
Danila Poyarkov d93bcab0b6 Add install instructions for @open-pencil/mcp 2026-03-02 14:28:56 +03:00
Danila Poyarkov 4ad90d4819 Add MCP server package with stdio and HTTP transports
29 tools: open_file, save_file, new_document + all 26 from @open-pencil/core.
Stdio for MCP clients (Claude Code, Cursor), HTTP (Hono + Streamable HTTP
with sessions) for scripts, browser extensions, CI.
Runs on both Bun and Node.js (via tsx).
2026-03-02 14:20:49 +03:00
Danila Poyarkov eeaef12c27 Update Penpot comparison to reflect WASM renderer (render-wasm/v1)
Penpot 2.x ships with Rust/Skia WASM rendering enabled by default.
Update architecture diagram, rendering pipeline, and file format sections
to reflect the current state instead of the old SVG-based renderer.
2026-03-02 13:35:09 +03:00
Danila Poyarkov 518ddb0c5b Release v0.3.2 2026-03-02 11:36:18 +03:00
Danila Poyarkov 527be98f2a Re-apply SkPicture scene caching with visual regression tests 2026-03-02 11:33:45 +03:00
Danila Poyarkov 1b414bf680 Release v0.3.1 2026-03-02 11:14:15 +03:00
Danila Poyarkov 38aeade46e Revert SkPicture scene caching (breaks text rendering on hover) 2026-03-02 11:13:46 +03:00
Danila Poyarkov d5ca92e01f Add MIT license 2026-03-02 08:31:54 +03:00
Danila Poyarkov c2e77ff63d Fix Figma MCP timeline, update tool counts to 26, add AI providers to roadmap 2026-03-02 08:19:08 +03:00
Danila Poyarkov f495f4072a Release v0.3.0 2026-03-01 22:46:06 +03:00
Danila Poyarkov a9c096a69d Fix npm publish: pnpm for workspace resolution, update README, read CLI version from package.json 2026-03-01 22:44:56 +03:00
Danila Poyarkov 9431918634 SkPicture scene caching for fast pan/zoom 2026-03-01 22:28:03 +03:00
Danila Poyarkov 80beba3348 Cache vector paths and pen paints across frames 2026-03-01 22:21:20 +03:00
Danila Poyarkov 97f4727e26 Cache ruler paints, disable preserveDrawingBuffer outside tests 2026-03-01 22:16:13 +03:00
Danila Poyarkov 5e209eca1c Fix trackpad pinch-to-zoom on Safari macOS 2026-03-01 22:07:47 +03:00
Danila Poyarkov 13a95bf517 Release v0.2.1 2026-03-01 21:25:52 +03:00
Danila Poyarkov e8b685ad0a Update screenshot 2026-03-01 18:44:52 +03:00
Danila Poyarkov 6e4332a6ba Release v0.2.0 2026-03-01 18:27:12 +03:00
Danila Poyarkov f90e1400b7 P2P collaboration: cursors, follow mode, cleanup
- Broadcast cursor position from canvas mouse move via awareness
- Broadcast selection changes via reactive watcher on selectedIds
- Follow mode: click peer avatar to track their viewport (pan + zoom)
- Zoom broadcast via watcher on store.state.zoom, not just mouse move
- Figma-style cursor arrows: colored fill, white border, name pill
- Stale cursor cleanup: removeAwarenessStates on peer leave
- MQTT signaling (replace Nostr), STUN + TURN ICE servers
- Collab constants extracted to src/constants.ts
- crypto.getRandomValues() replaces Math.random() everywhere
- Provide/inject for collab composable (COLLAB_KEY)
- Update README (collab section, tech stack), CHANGELOG, AGENTS.md
- AGENTS.md: release process, CI workflows, documentation guidelines
2026-03-01 18:11:07 +03:00
Danila Poyarkov 3427b7fd3c Replace Durable Object relay with Trystero P2P + y-indexeddb
- Remove packages/collab/ (CF Worker + Durable Object server)
- Replace WebSocket relay with Trystero (serverless WebRTC P2P)
  - Peers connect via Nostr relays for signaling (free, public)
  - All sync traffic flows directly peer-to-peer
  - Automatic chunking for large Yjs updates
- Add y-indexeddb for local persistence
  - Each client persists Y.Doc to IndexedDB
  - Room survives browser refresh, loads instantly
- Yjs sync protocol over Trystero actions:
  - yjs-update: incremental doc updates
  - sync-step1/sync-reply: full state sync on peer join
  - awareness: cursor, selection, presence
- Zero server cost at any scale
2026-03-01 17:25:46 +03:00
Danila Poyarkov 8695ba6d1b Emphasize no subscription, bring your own API key 2026-03-01 17:24:54 +03:00
Danila Poyarkov 2add3ac4c0 Real-time collaboration with Yjs CRDT over Cloudflare Durable Objects
- 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)
2026-03-01 16:44:37 +03:00
Danila Poyarkov 9f8242bce5 Add download links to README, docs landing, and getting started 2026-03-01 16:32:45 +03:00
Danila Poyarkov 1e873d64b1 Add repository field and provenance to package configs 2026-03-01 16:23:30 +03:00
Danila Poyarkov c4e6514ea2 Update repo references to open-pencil/open-pencil 2026-03-01 16:18:11 +03:00
Danila Poyarkov 74d4b6862e Use app favicon for docs site (symlink to public/favicon-32.png) 2026-03-01 16:14:06 +03:00
Danila Poyarkov 10848d8319 Add app.openpencil.dev links to docs 2026-03-01 16:02:08 +03:00
Danila Poyarkov 0f0ca8db4b Update screenshot, remove duplicate from repo root
Single screenshot lives in packages/docs/public/. README references it
from there. Custom VitePress layout places it between hero and features.
2026-03-01 15:57:01 +03:00
Danila Poyarkov c886daded2 Fix .fig export: map COMPONENT/COMPONENT_SET to SYMBOL (Kiwi enum)
The Kiwi binary schema uses SYMBOL (15) for components, not COMPONENT.
Import already mapped SYMBOL→COMPONENT; export now reverses it.
2026-03-01 15:35:20 +03:00
Danila Poyarkov 1e1feb9cb0 Merge branch 'add-vitepress-docs' 2026-03-01 15:32:45 +03:00
Danila Poyarkov 96d8ab5789 Move docs to packages/docs, add Cloudflare Pages deploy
- Move docs/ → packages/docs/ as @open-pencil/docs workspace package
- Add screenshot.png as hero image on landing page
- Add GitHub Actions workflow for Cloudflare Pages deploy (openpencil.dev)
- Remove vitepress from root devDependencies
- Root docs:* scripts delegate via bun --filter
2026-03-01 15:16:16 +03:00
Danila Poyarkov 3849872b32 Unify tool definitions: define once, adapt for AI/CLI/MCP
Move tool logic to @open-pencil/core/tools/schema.ts as framework-agnostic
ToolDef objects. AI adapter generates valibot schemas + Vercel AI tool()
wrappers automatically.

26 tools (was 10): create_shape, render (JSX), set_fill, set_stroke,
set_effects, set_layout, set_constraints, update_node, delete, clone,
rename, reparent, group/ungroup, find_nodes, get_node, get_page_tree,
get_selection, select, list_pages, switch_page, list_variables,
list_collections, create_component, create_instance, eval.

src/ai/tools.ts: 269→28 lines (adapter only).

Tests for all 3 interfaces:
- 26 core tool tests (FigmaAPI directly)
- 11 AI adapter tests (valibot + Vercel AI SDK tool())
- 12 CLI integration tests (eval command on .fig fixture)
323 total, all passing.
2026-03-01 15:02:05 +03:00
Danila Poyarkov f22bd2e133 Merge eval-command: Figma Plugin API + eval CLI command
FigmaAPI wraps SceneGraph with Figma-compatible interface (Symbol-hidden internals).
CLI eval command runs Plugin API scripts headless on .fig files.
106 new tests (60 figma-api + 17 cli integration + 29 gap-filling).
2026-03-01 13:55:39 +03:00
Danila Poyarkov 123dbe491b Extract DEFAULT_STROKE_MITER_LIMIT constant 2026-03-01 13:53:25 +03:00
Danila Poyarkov 3e9cd236e9 Fix bogus fig-import mappings: expanded and strokeMiterLimit
expanded: was mapped from frameMaskDisabled (unrelated Kiwi field),
now defaults to true. strokeMiterLimit: was mapped from speculative
strokeMiterAngle field that doesn't exist in Kiwi codec, now uses
hardcoded default 4.
2026-03-01 13:48:48 +03:00
Danila Poyarkov 48219af2fb Fill Figma Plugin API gaps: 29 new tests, 30+ new properties and methods
SceneNode: add minWidth/maxWidth/minHeight/maxHeight, isMask, maskType,
counterAxisAlignContent, itemReverseZIndex, strokesIncludedInLayout,
expanded, textTruncation, autoRename, strokeMiterLimit. All with defaults
and .fig import mapping.

FigmaNodeProxy: add clone(), strokeCap, strokeJoin, strokeMiterLimit,
strokeTopWeight/BottomWeight/LeftWeight/RightWeight, minWidth/maxWidth/
minHeight/maxHeight, isMask, maskType, expanded, textTruncation,
autoRename, insertCharacters, deleteCharacters, primaryAxisSizingMode,
counterAxisSizingMode, counterAxisAlignContent, itemReverseZIndex,
strokesIncludedInLayout, layoutAlign, findAllWithCriteria.

FigmaAPI: add createComponentFromNode, getVariableById, getLocalVariables,
getLocalVariableCollections, getVariableCollectionById.

Fix layoutSizingHorizontal/Vertical for self-framing auto-layout nodes
with no auto-layout parent — now checks the node's own layoutMode.
2026-03-01 13:35:02 +03:00
Danila Poyarkov b6e792846f Harden FigmaAPI: hide internals via symbols, freeze arrays, fix layoutSizing
- Replace _id/_graph/_api with Symbol-keyed properties so eval scripts
  can't access SceneGraph internals directly
- fills/strokes/effects return frozen structuredClone copies
- Fix currentPage getter to define selection only once per proxy
- Fix layoutSizingHorizontal/Vertical: respect parent layout direction
  even when the node itself is an auto-layout frame
- Add 6 new tests: layoutSizing, frozen arrays, internals not exposed
2026-03-01 13:12:26 +03:00
Danila Poyarkov 0cba4fb4b7 Add FigmaAPI + eval CLI command with 60 unit tests
FigmaAPI in @open-pencil/core provides a Figma Plugin API-compatible
interface backed by SceneGraph. Covers node creation, property access,
tree operations, auto-layout, text, traversal, grouping, components,
selection, and serialization.

CLI: bun open-pencil eval <file> --code 'figma.createFrame()'
2026-03-01 12:21:07 +03:00
Danila Poyarkov c59d5fc397 Add Code tab with JSX export and syntax highlighting
- sceneNodeToJsx() in @open-pencil/core: converts SceneNode subtree to JSX
- CodePanel.vue with Prism.js highlighting, line numbers, copy button
- Third tab in properties panel: Design | Code | AI
- 14 new tests covering shapes, text, layout, effects, multi-selection
2026-03-01 12:12:45 +03:00
Danila Poyarkov bf8636c677 Merge chat-panel: AI chat with OpenRouter, tool execution, model selector 2026-03-01 11:55:57 +03:00
Danila Poyarkov 4032108be0 Move AI models to @open-pencil/core constants, add Kimi K2.5 (vision+code), benchmark-ranked tags 2026-03-01 11:38:40 +03:00
Danila Poyarkov 6268d8b119 Fix double/triple-click text selection
- Track click count with timing/proximity in onMouseDown
- Double-click in text: select word (was only handled in dblclick)
- Triple-click in text: select all
- Double-click to enter editing: select word at click position
- Add selectLine/selectLineAt to TextEditor
2026-03-01 11:36:49 +03:00
Danila Poyarkov 20827a27d3 Deduplicate fig-kiwi parsing, font weight mapping, layout, style runs, text editor
- Extract buildFigKiwi, parseFigKiwiChunks, decompressFigKiwiDataAsync to kiwi-serialize.ts
- Unify styleToWeight across fonts.ts, fig-import.ts, src/engine/fonts.ts
- Merge applyYogaLayout + applyYogaLayoutNested into single recursive fn
- Extract expandRuns helper in style-runs.ts
- Add prepareMove helper to TextEditor for selection extension boilerplate

jscpd: 18 → 9 clones, 1.7% → 0.62%
2026-03-01 11:29:26 +03:00
Danila Poyarkov e3ae61e99d Consolidate font weight mapping and FIG_KIWI_VERSION 2026-03-01 11:08:59 +03:00
Danila Poyarkov f92a8835f4 Deduplicate kiwi and serialization code
Replace src/kiwi/ copies with re-exports from @open-pencil/core.
Extract shared sceneNodeToKiwi into kiwi-serialize.ts, used by
both fig-export.ts and clipboard.ts.

jscpd: 15.6% → 1.7% duplication (35 → 18 clones, -4459 lines)
2026-03-01 10:59:35 +03:00
Danila Poyarkov a32d80f448 Deduplicate render internals: shared resolveToTree, remove dead code 2026-03-01 10:45:39 +03:00
Danila Poyarkov 13f562adc6 JSX renderer in @open-pencil/core
TreeNode builder functions (Frame, Text, Rectangle, etc.) produce a
lightweight tree structure. Two rendering paths:

- renderTreeNode(): tree → scene graph (browser, no deps)
- renderJsx(): JSX string → esbuild → tree → scene graph (CLI/headless)

Tailwind-like shorthand props: w/h, bg, rounded, flex, gap, p/px/py,
justify, items, shadow, blur, etc.

27 tests covering all node types, layout props, effects, and nesting.
2026-03-01 10:42:06 +03:00
Danila Poyarkov 36e3f14ac4 CLI: add analyze, node, pages, and variables commands
- analyze colors: color palette usage with clustering
- analyze typography: font/size/weight distribution
- analyze spacing: gap/padding values with grid check
- analyze clusters: repeated patterns (potential components)
- node: detailed properties by ID
- pages: list pages with node counts
- variables: list design variables and collections
2026-03-01 10:19:38 +03:00
Danila Poyarkov f24f6e0dc6 Add .fig test fixtures (LFS) and fix O(n²) import bottleneck
- Add Material 3 Design Kit and Nuxt UI v4 community files as test fixtures
- Track tests/fixtures/*.fig with Git LFS
- Fix O(n²) getChildren() in fig-import: build children index upfront
  - material3.fig (87K nodes): 37s → 535ms (69x faster)
  - nuxtui.fig (314K nodes): minutes → 2.3s
- Optimize kiwi ByteBuffer: inline readVarUint, use TextDecoder for strings
2026-03-01 09:55:07 +03:00
Danila Poyarkov 88bfacdc9e Rich text style runs with per-selection bold/italic/underline
StyleRun model: array of {start, length, style} with
CharacterStyleOverride (fontWeight, italic, textDecoration, etc.).

Renderer uses ParagraphBuilder.pushStyle/pop for mixed-style text.
TextEditor delegates paragraph building to renderer.buildParagraph().

⌘B/I/U apply to selection range when text is selected, or toggle
whole-node style when no selection. Style runs adjust on
insert/delete to preserve formatting across edits.

Fig roundtrip: import characterStyleIDs + styleOverrideTable from
TextData, export back with deduped style table.

B/I/U/S toggle buttons in TypographySection.
2026-03-01 09:03:38 +03:00