Find a file
Anton Soldatov e939025f7d
Fix auto-layout overflow, grid layout, AI chat UX (#86)
* Fix auto-layout overflow: MeasureFunc, min/max, absolute positioning, text wrap

Layout engine (layout.ts):
- Use Yoga MeasureFunc for text nodes instead of static pre-measurement,
  so text wraps correctly when width is determined by flex layout
- Add min/max width/height constraint support (minWidth, maxWidth, etc.)
- Translate counterAxisAlignContent to Yoga setAlignContent for wrap layouts
- Extract configureFlexContainer to share between root and nested frames

JSX renderer (render/renderer.ts):
- Auto-set layoutPositioning ABSOLUTE for elements with x/y inside auto-layout
- Fix text auto-resize: set HEIGHT when text fills parent in auto-layout

Text measurer (renderer/renderer.ts):
- Accept optional maxWidth parameter for constraint-based measurement

* AI prompt: prefer describe over export_image for post-render verification

* Strip TS casts from AI-generated JSX before sucrase parse

AI models sometimes emit `as any`, `as const` etc. in JSX props.
Sucrase with jsx-only transform can't handle these, causing parse errors.

- Strip TypeScript cast expressions before passing to sucrase
- Add forbidden patterns to AI prompt: as any, template literals for
  sizes, Math.random()

* Cache text MeasureFunc results to avoid repeated buildParagraph calls

Yoga calls MeasureFunc multiple times per node during calculateLayout.
Each call was creating a full CanvasKit Paragraph — expensive for layouts
with many text nodes. Cache by rounded constraint width.

* AI chat: fix provider settings popover, add max output tokens, fix paste in chat input

- Fix ProviderSettings popover not visible (remove tooltip/popover trigger
  conflict, add collision-padding, isolate z-[51])
- Add configurable max output tokens (default 16384) in provider settings
- Pass maxOutputTokens to ToolLoopAgent to prevent truncated tool calls
- Add system prompt size limits: split render calls at ~40 elements
- Fix paste/copy/cut in chat input (stop event propagation to canvas handler)

* Fix 6 layout engine bugs: hidden size, FILL basis, SPACE_EVENLY, alignSelf range, grid stretch, absolute children

Layout engine (layout.ts):
- Guard applyYogaLayout to preserve hidden children dimensions
- Add setFlexBasis(0) for FILL sizing so children share space from zero
- Add SPACE_EVENLY to mapJustify via Justify.SpaceEvenly
- Replace hardcoded STRETCH checks with mapAlignSelf for full range
- Use setWidthStretch/setHeightStretch for grid children instead of flexGrow
- Insert absolute children in Yoga tree via configureAbsoluteChild with
  PositionType.Absolute, keeping manual x/y (no write-back)

Types (scene-graph.ts):
- Add SPACE_EVENLY to LayoutAlign union
- New LayoutAlignSelf type: AUTO | MIN | CENTER | MAX | STRETCH | BASELINE

Import (kiwi-convert.ts):
- Map SPACE_EVENLY correctly instead of collapsing to SPACE_BETWEEN
- New mapAlignSelf function for full StackCounterAlign range

Tools (modify.ts):
- Expand set_layout align enum with SPACE_EVENLY
- Expand set_layout_child align_self enum with MIN/CENTER/MAX/BASELINE

Tests: 86 pass (+13 new covering all 6 fixes)

* Grid layout support in JSX renderer, auto-height grids, flex children stretch in cells

Renderer:
- Parse grid/columns/rows/columnGap/rowGap/gap props
- Parse grid child positioning: colStart/col/rowStart/row/colSpan/rowSpan
- Grid prop takes precedence over padding-triggered auto-layout
- SPACE_EVENLY added to ALIGN_MAP
- Numeric columns shorthand (columns={3} → 3×1fr)
- Auto-height: grid without rows sets height=0 for Yoga auto-sizing
- fill sizing in grid children → layoutAlignSelf: STRETCH
- Component builders accept variadic children: Frame(props, ...children)

Layout:
- Grid auto-height: skip setHeight when no gridTemplateRows
- Write back computed height for auto-height grids
- Grid children with layoutMode stretch width to fill cell
- recomputeGridChild: re-run flex layout after grid assigns cell size
  (temporarily sets sizing to FIXED so HUG doesn't override grid width)

Export:
- Skip height export for auto-height grids (no rows template)

Tests: 18 new grid render tests covering all paths

* Auto-enable flex for justify/items props, discourage export_image in AI prompt

* Address review: use sucrase typescript transform, don't reset chat on maxOutputTokens change

* Fix lint errors: extract applyFrameSize, wrap nested ternary, remove unnecessary cast

* Remove SPACE_EVENLY — not a valid Figma layout value

* CI: debug LFS pull from R2

* Apply maxOutputTokens dynamically via prepareCall instead of at agent creation

* Fix heavy .fig parse: restore GUID/defID guards, fix optional types

Commit f3eac5a removed null guards from fig-import and
instance-overrides as 'unnecessary conditions', but Kiwi-decoded data
can have NodeChanges without GUIDs and ComponentPropAssignments/Refs
without defIDs. Made the types optional to match reality.

Also fix CI LFS: add git lfs install --force before pull (R2 endpoint
needs the filter registered first).

* Fix Google AI model IDs: gemini-3.1-pro → gemini-3.1-pro-preview

Closes #91

---------

Co-authored-by: Danila Poyarkov <dev@dannote.net>
2026-03-10 15:43:04 +03:00
.github Fix auto-layout overflow, grid layout, AI chat UX (#86) 2026-03-10 15:43:04 +03:00
.pi Code review: svg-export/ folder, restore comments, cleanup 2026-03-09 15:22:14 +03:00
.vscode Initial commit: Tauri + CanvasKit + React editor scaffold 2026-02-27 15:48:29 +03:00
desktop Release v0.9.0 2026-03-09 17:43:11 +03:00
docs Merge docs PR, fix all lint/typecheck errors, update changelog 2026-03-07 23:19:19 +03:00
lint Lint CLI code: no-raw-console-format rule, fix all violations 2026-03-09 12:54:53 +03:00
openspec Merge PR #70: E2E test coverage + mutation unit tests 2026-03-07 23:47:00 +03:00
packages Fix auto-layout overflow, grid layout, AI chat UX (#86) 2026-03-10 15:43:04 +03:00
public feat: mobile layout, PWA support (#27) (#27) 2026-03-05 19:51:51 +03:00
scripts Guard against double destroy and post-unmount rendering 2026-03-04 10:35:44 +03:00
src Fix auto-layout overflow, grid layout, AI chat UX (#86) 2026-03-10 15:43:04 +03:00
tests Fix auto-layout overflow, grid layout, AI chat UX (#86) 2026-03-10 15:43:04 +03:00
.gitattributes Cache effect ImageFilters and reuse layer paint 2026-03-03 10:12:02 +03:00
.gitignore Ignore VitePress build artifacts under docs/ 2026-03-07 21:45:32 +03:00
.lfsconfig Use Cloudflare R2 for Git LFS storage 2026-03-04 01:25:33 +03:00
.oxfmtrc.json Enable Tailwind CSS class sorting in oxfmt 2026-03-09 15:46:20 +03:00
AGENTS.md Enable type-aware linting with tsgo in oxlint 2026-03-08 23:02:25 +03:00
ARCHITECTURE-NEXT.md Adopt motion-v for mobile drawer and toolbar animations 2026-03-06 08:50:38 +03:00
bun.lock Add event emitter to SceneGraph, refactor collab sync 2026-03-09 16:02:54 +03:00
CHANGELOG.md Remove phantom changelog entry (instance work queue didn't ship) 2026-03-09 20:48:27 +03:00
components.d.ts Event-driven requestRender and component sync via SceneGraph emitter 2026-03-09 16:13:03 +03:00
CONTRIBUTING.md feat: mobile layout, PWA support (#27) (#27) 2026-03-05 19:51:51 +03:00
index.html Fix PWA manifest error in dev, handle invalid font data 2026-03-06 21:43:16 +03:00
LICENSE Add MIT license 2026-03-02 08:31:54 +03:00
oxlint.json Reduce cyclomatic complexity below 20 in all core functions 2026-03-09 13:40:57 +03:00
package.json Release v0.9.0 2026-03-09 17:43:11 +03:00
plan-refactor.md Merge docs PR, fix all lint/typecheck errors, update changelog 2026-03-07 23:19:19 +03:00
PLAN.md Release v0.9.0 2026-03-09 17:43:11 +03:00
playwright.config.ts Fix flaky E2E tests: layers panel, zoom-to-fit, snapshot stability 2026-03-07 11:08:22 +03:00
pnpm-workspace.yaml Release v0.8.0 2026-03-08 00:20:47 +03:00
README.md Release v0.9.0 2026-03-09 17:43:11 +03:00
SECURITY.md Add CI for PRs, issue/PR templates, CONTRIBUTING and SECURITY docs 2026-03-03 17:24:45 +03:00
tsconfig.json Fix InvalidCharacterError when copying nodes with non-ASCII text 2026-03-04 01:15:44 +03:00
tsconfig.node.json Initial commit: Tauri + CanvasKit + React editor scaffold 2026-02-27 15:48:29 +03:00
tweet-0.9.0.txt Fix auto-layout overflow, grid layout, AI chat UX (#86) 2026-03-10 15:43:04 +03:00
vite.config.ts Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72) 2026-03-08 22:05:18 +03:00

OpenPencil

Open-source design editor. Opens Figma files, built-in AI, fully programmable.

Status: Active development. Not ready for production use.

Try it online → · Download · Documentation

OpenPencil

Installation

macOS (Homebrew):

brew install open-pencil/tap/open-pencil

Or download from the releases page, or use the web app — no install needed.

What it does

  • Opens .fig files — read and write native Figma files, copy & paste nodes between apps
  • AI builds designs — describe what you want in chat, 90+ tools create and modify nodes. Connect Anthropic, OpenAI, Google AI, OpenRouter, or any compatible endpoint
  • Fully programmable — headless CLI, Figma Plugin API via eval, MCP server for AI agents
  • Real-time collaboration — P2P via WebRTC, no server, no account. Cursors, presence, follow mode
  • Auto layout & CSS Grid — flex and grid layout via Yoga WASM, with gap, padding, alignment, track sizing
  • Tailwind CSS export — export any selection as HTML with Tailwind v4 utility classes
  • ~7 MB desktop app — Tauri v2 for macOS, Windows, Linux. Also runs in the browser as a PWA

CLI

bun add -g @open-pencil/cli

Inspect .fig files

Browse node trees, search by name or type, dig into properties — all without opening the editor:

open-pencil tree design.fig
open-pencil find design.fig --type TEXT
open-pencil node design.fig --id 1:23
open-pencil info 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)

Query with XPath

Use XPath selectors to find nodes by type, attributes, and structure:

open-pencil query design.fig "//FRAME"                              # All frames
open-pencil query design.fig "//FRAME[@width < 300]"                # Frames under 300px
open-pencil query design.fig "//TEXT[contains(@name, 'Button')]"     # Text with 'Button' in name
open-pencil query design.fig "//*[@cornerRadius > 0]"               # Rounded corners
open-pencil query design.fig "//SECTION//TEXT"                       # Text inside sections

Export

Render to PNG, JPG, WEBP, SVG — or export as JSX with Tailwind utility classes:

open-pencil export design.fig                          # PNG
open-pencil export design.fig -f jpg -s 2 -q 90       # JPG at 2x, quality 90
open-pencil export design.fig -f jsx --style tailwind  # Tailwind JSX
<div className="flex flex-col gap-4 p-6 bg-white rounded-xl">
  <p className="text-2xl font-bold text-[#1D1B20]">Card Title</p>
  <p className="text-sm text-[#49454F]">Description text</p>
</div>

Analyze design tokens

Audit an entire design system from the terminal — find inconsistencies, extract the real palette, spot components waiting to be extracted:

open-pencil analyze colors design.fig
open-pencil analyze typography design.fig
open-pencil analyze spacing design.fig
open-pencil analyze clusters design.fig
#1d1b20  ██████████████████████████████ 17155×
#49454f  ██████████████████████████████ 9814×
#ffffff  ██████████████████████████████ 8620×
#6750a4  ██████████████████████████████ 3967×

3771× frame "container" (100% match)
     size: 40×40, structure: Frame > [Frame]

2982× instance "Checkboxes" (100% match)
     size: 48×48, structure: Instance > [Frame]

Script with Figma Plugin API

eval gives you the full Figma Plugin API. Modify the file, write it back:

open-pencil eval design.fig -c "figma.currentPage.children.length"
open-pencil eval design.fig -c "figma.currentPage.selection.forEach(n => n.opacity = 0.5)" -w

Control the running app

When the desktop app is running, omit the file argument — the CLI connects via RPC and operates on the live canvas. Useful for automation scripts, CI pipelines, or AI agents that need to interact with the editor:

open-pencil tree                               # Inspect the live document
open-pencil export -f png                      # Screenshot the current canvas
open-pencil eval -c "figma.currentPage.name"   # Query the editor

All commands support --json for machine-readable output.

AI & MCP

Built-in chat

Press J to open the AI assistant. It has 87 tools that can create shapes, set fills and strokes, manage auto-layout, work with components and variables, run boolean operations, analyze design tokens, and export assets. Bring your own OpenRouter API key — no backend, no account.

MCP server

Connect Claude Code, Cursor, Windsurf, or any MCP client to read and write .fig files headlessly. 90 tools (87 core + 3 file management). Full docs →

Stdio (Claude Code, Cursor, Windsurf):

bun add -g @open-pencil/mcp
{
  "mcpServers": {
    "open-pencil": {
      "command": "openpencil-mcp"
    }
  }
}

HTTP (scripts, CI):

openpencil-mcp-http   # http://localhost:3100/mcp

AI agent skill

Teach your AI coding agent to use OpenPencil — inspect designs, export assets, analyze tokens, modify .fig files:

npx skills add open-pencil/skills@open-pencil

Works with Claude Code, Cursor, Windsurf, Codex, and any agent that supports skills.

Collaboration

Share a link to co-edit in real time. No server, no account — peers connect directly via WebRTC.

  1. Click the share button in the top-right panel
  2. Share the generated link (app.openpencil.dev/share/<room-id>)
  3. Collaborators see your cursor, selection, and edits in real time
  4. Click a peer's avatar to follow their viewport

Why

Figma is a closed platform that actively fights programmatic access. Their MCP server is read-only. figma-use added full read/write automation via CDP — then Figma 126 killed CDP. Your design files are in a proprietary binary format that only their software can fully read. Your workflows break when they decide to ship a point release.

OpenPencil is the alternative: open source (MIT), reads .fig files natively, every operation is scriptable, and your data never leaves your machine.

Roadmap

  • Prototyping — frame transitions, interaction triggers, overlay management, preview mode
  • Shader effects (SkSL) — custom visual effects via GPU shaders
  • Raster tile caching — instant zoom/pan for complex documents
  • Component libraries — publish, share, and consume design systems across files
  • CI tools — design linting, code export, visual regression in pipelines
  • Grid child positioning UI — column/row span controls, grid overlay on canvas
  • Skewing and OkHCL color support
  • Windows code signing (Azure Authenticode certificates)
  • Experimental WebGPU/Graphite rendering backend

Contributing

Setup

bun install
bun run dev        # Dev server at localhost:1420
bun run tauri dev  # Desktop app (requires Rust)

Quality gates

Command Description
bun run check Lint + typecheck
bun run test E2E visual regression
bun run test:unit Unit tests
bun run format Code formatting

Project structure

packages/
  core/           @open-pencil/core — engine (scene graph, renderer, layout, codec)
  cli/            @open-pencil/cli — headless CLI
  mcp/            @open-pencil/mcp — MCP server (stdio + HTTP)
  docs/           Documentation site (openpencil.dev)
src/              Vue app (components, composables, stores)
desktop/          Tauri v2 (Rust + config)
tests/            E2E (188 tests) + unit (764 tests)

Tech stack

Layer Tech
Rendering Skia (CanvasKit WASM)
Layout Yoga WASM (flex + grid via fork)
UI Vue 3, Reka UI, Tailwind CSS 4
File format Kiwi binary + Zstd + ZIP
Collaboration Trystero (WebRTC P2P) + Yjs (CRDT)
Desktop Tauri v2
AI/MCP Multi-provider (Anthropic, OpenAI, Google AI, OpenRouter), MCP SDK, Hono

Desktop builds

Requires Rust and platform-specific prerequisites (Tauri v2 guide).

bun run tauri build

Acknowledgments

Thanks to @sld0Ant (Anton Soldatov) for creating and maintaining the documentation site.

License

MIT