# Changelog ## Unreleased ### Features - XPath query command — `open-pencil query design.fig "//FRAME[@width < 300]"` to find nodes by type, attributes, and tree structure using XPath selectors - CSS Grid layout mode — select a frame, click the grid icon in the auto layout toolbar to switch from flex to grid. Configure column/row tracks (fr, fixed px, auto), column and row gaps, and per-side padding. Powered by a [Yoga fork](https://github.com/open-pencil/yoga/tree/grid) with cherry-picked CSS Grid PRs from upstream - JSX and Tailwind CSS export for grid layouts — `grid grid-cols-N`, `gap-x-*`/`gap-y-*`, child `col-start-*`/`row-start-*`/`col-span-*`/`row-span-*` - Multi-provider AI support — connect to Anthropic, OpenAI, Google AI, or any OpenAI-compatible endpoint directly, in addition to OpenRouter. Per-provider API key storage, provider settings popover, automatic migration from single OpenRouter key - Anthropic-compatible provider for custom API endpoints - New AI tools: `get_jsx` (JSX roundtrip view), `diff_jsx` (structural diff), `describe` (semantic role, visual style, layout, design issues) - AI visual verification — `export_image` returns image content to the model for vision-based review - API type toggle (Completions/Responses) for OpenAI-compatible providers ### Fixes - Serialize variables, collections, and bindings to `.fig` files — previously lost on save (#65) - Text nodes created via MCP now render in Figma — emit `derivedTextData` with font metadata and layout size (#64) - Double-click on layer tree no longer toggles expand/collapse — use the chevron instead - Page rename input matches layer rename styling - Fix `w="fill"`/`h="fill"` in JSX renderer — now direction-aware based on parent flex axis - Fix text auto-resize defaulting to fixed 100×100 — text without explicit width uses `WIDTH_AND_HEIGHT` - Fix `clipsContent` not propagated to Yoga — frames with clip enabled now set `Overflow.Hidden` - Fix `COUNTER_ALIGN_MAP` mapping stretch to `MIN` instead of `STRETCH` - Fix JSX export omitting x/y for absolute-positioned children - Fix JSX export ignoring `textAutoResize` for text sizing - Fix drag terminating on mouseleave — drags now continue outside the canvas - Fix `export_image` stack overflow on large nodes — chunked base64 encoding - Undo support for auto-layout reorder, layer tree reorder, and drag reparent - Page snapshot undo for AI tool mutations - Fix collab sync for same-parent reorder — `node:reordered` events now propagated to Yjs peers ### Performance - Event-driven rendering and component sync — `SceneGraph` emits typed events on mutations; `requestRender()` calls reduced from 94 to 22, component instance sync uses microtask batching with deduplication - Replace `structuredClone` with typed copy helpers for fills, strokes, effects, and style runs (~24× faster in hot paths) - Filter .fig unzip to only decompress canvas and image entries, skipping metadata cruft - Instance population uses a work queue instead of repeated full graph scans ### Improvements - Padding on a frame auto-enables vertical auto-layout - AI tools run `computeAllLayouts` after execution — layout updates immediately - Enhanced AI system prompt with full JSX prop reference and verification workflow - Chat panel preserves messages when toggling UI visibility - SceneGraph event bus (nanoevents) — `node:created`, `node:updated`, `node:deleted`, `node:reparented`, `node:reordered` events replace monkey-patching in collab sync and manual render invalidation - Dev-only debug toolbar for copying chat logs - Auto-layout icons in layer tree — vertical (rows), horizontal (columns), and grid icons for auto-layout frames; components keep their purple diamond - Frame titles on canvas are now draggable — clicking a selected top-level frame's name label starts a drag - Compact layout controls — icon-based gap (↔/↕) and padding (T/R/B/L) inputs instead of text labels - Auto-detect horizontal vs vertical direction when wrapping in auto layout (Shift+A) - Fix alignment grid for vertical layouts — visual positions now match spatial axes - Fix grid switch from HUG-sized frames — frame expands to fit children - Remove unwanted white fill when wrapping in auto layout ## 0.8.0 — 2026-03-07 ### Features - Mobile layout & PWA — responsive editor with touch-optimized toolbar, swipeable bottom drawer (layers/properties/design/code), HUD overlay, and installable PWA with icons and service worker - Tailwind CSS v4 JSX export — export selections as HTML with Tailwind utility classes (`
`) from the Code panel, CLI (`bun open-pencil export --format jsx --style tailwind`), or programmatically via `sceneNodeToJSX(id, graph, 'tailwind')`. Supports layout, sizing, colors, border radius, opacity, rotation, overflow, shadows, blur, and typography. Uses v4 spacing semantics (px/4 multiplier) with automatic fallback to arbitrary values. - Code panel format toggle — switch between OpenPencil (custom components) and Tailwind (HTML + utility classes) output - Homebrew tap — `brew install open-pencil/tap/open-pencil` for macOS (arm64 + x64), auto-updated on each release - Double-click to rename layers — inline rename in layer panel, shared `useInlineRename` composable - New AI/MCP tools: `analyze_colors`, `analyze_typography`, `analyze_spacing`, `analyze_clusters`, `diff_create`, `diff_show`, `get_components`, `get_current_page`, `arrange`, `node_to_component` - CLI-to-app RPC bridge — all CLI commands work against the running app when no file is specified. Start the app, then run `bun open-pencil tree` to inspect the live document - VitePress docs site — user guide, reference, architecture, and development docs at openpencil.dev with 6 locales (en, de, fr, es, it, pl), SEO (OG tags, hreflang, JSON-LD, sitemap), and dark theme ### Improvements - Refactor mobile drawer tabs, layout sizing dropdowns, and inline rename to use Reka UI primitives - Add shared UI style helpers with tailwind-variants for menus, selects, buttons, and surfaces - Unified tool definitions — define once in `packages/core/src/tools/`, automatically available in AI chat, CLI, and MCP - Harden FigmaAPI — hide internals via Symbols, freeze arrays, fix `layoutSizing`, 30+ new properties and methods - Split tools into domain files (read, create, modify, structure, variables, vector, analyze) — easier to navigate and extend - Replace inline type definitions with named types (`Color`, `Vector`, `SceneNode`) across the codebase - Split 3200-line `renderer.ts` into `packages/core/src/renderer/` with 10 focused files (scene, overlays, fills, strokes, shapes, effects, rulers, labels) - Centralize all color utilities in `packages/core/src/color.ts` — `colorToHex8`, `colorToCSSCompact`, `normalizeColor`, `colorDistance`; remove 5 duplicate implementations across the codebase - Add `geometry.ts` with shared rotation math (`degToRad`, `radToDeg`, `rotatePoint`, `rotatedCorners`, `rotatedBBox`) - Extract `isArrayMixed()` helper for multi-selection property panels ### Fixes - Fix drawer animation jump on close — single spring transition instead of two-phase - Fix `ALL_TOOLS` registry missing newer tools (`analyzeColors`, `diffCreate`, `exportImage`, `arrangeNodes`) - Fix `renderJSX` typo in tool definitions (`renderJsx` → `renderJSX`) - Fix all oxlint warnings and tsgo errors — replace `!` non-null assertions in `use-collab.ts` with local const captures - Fix broken test imports — stale `../../src/engine/` paths updated to `@open-pencil/core` - Fix flaky E2E tests: layers panel navigates to `/demo`, zoom-to-fit test zooms in first, snapshot rendering stabilized with `workers: 1` and `colorScheme: dark` - Fix bogus .fig import mappings for `expanded` and `strokeMiterLimit` fields - Fix PWA manifest error in dev mode, handle invalid font data gracefully - Fix eval response unwrapping and `export_jsx` page selection in RPC bridge - Fix automation commands not recomputing layouts after mutations - Fix workspace dependency not resolved when installing from npm (switch CI to pnpm publish) ### Internal - Add `motion-v` for declarative animations — used in mobile drawer (spring-animated height with pan gestures) and toolbar (layout-animated category switching with directional slide transitions) - Mobile drawer: replace `useSwipe` + manual rAF animation with `motion.div` `:animate` + `@pan`/`@panEnd`; always-on tab state (no more null `activeRibbonTab`); content stays rendered when closed - Mobile toolbar: replace manual `scrollWidth` measuring + inline CSS transitions with `motion.div layout` + `AnimatePresence` directional slide variants - Mobile UI cleanup: extract shared `colorToCSS` util to core, `initials` to `src/utils/text`, `toolIcons` to `src/utils/tools`; replace hand-rolled dropdowns with reka-ui Popover/DropdownMenu; narrow `mobileDrawerSnap` type to string union; move magic numbers to constants; disable PWA service worker in dev mode - 83 new E2E tests (57 → 140): design panel, code panel, components, copy/paste, multi-page, text editing, keyboard shortcuts, context menu - 150 new unit tests (588 → 738): color, undo, snap, vector, style-runs, text-editor - 48 new E2E tests (9 spec files) + 26 mutation unit tests + store/canvas test helpers - Add `data-test-id` attributes to AppearanceSection, LayoutSection, TypographySection, VariablesDialog, EditorView ## 0.7.0 — 2026-03-05 ### Features - SVG export — export selections as SVG from the export panel, context menu, CLI (`bun open-pencil export --format svg`), or MCP/AI tools (`export_svg`). Supports rectangles, ellipses, lines, stars, polygons, vectors, text with style runs, gradients, image fills, effects, blend modes, clip paths, and nested groups (#46) - Copy/Paste as submenu in context menu — Copy as text, Copy as SVG, Copy as PNG (⇧⌘C), Copy as JSX - Stroke align (Inside/Center/Outside) with clip-based rendering matching Figma behavior - Individual stroke weights per side (Top/Right/Bottom/Left) with side selector dropdown - Google Fonts fallback — automatically loads fonts from Google Fonts API when not available locally - Auto-save toggle in File menu — disable to prevent automatic writes to the opened .fig file - Renderer profiler with in-canvas HUD overlay, GPU timing, and phase instrumentation ### Improvements - Replace custom color picker with Reka UI Color components (ColorArea, ColorSlider, ColorField) — adds keyboard navigation and accessibility to the color area, hue, and alpha controls ### Fixes - CJK text rendering — load a system CJK font (PingFang SC, Microsoft YaHei, Noto Sans CJK) as fallback; falls back to Noto Sans SC from Google Fonts when no system font is available (#48) - Font registration errors no longer cache invalid font data — `loadFont` only caches after successful CanvasKit registration - Fix `render` tool failing on Windows + Bun with "Cannot find module" error (#43) - Fix hover highlighting nodes from internal component pages — scope hit-test to current page - Fix hit-testing on transparent frames and groups — empty containers without fills or strokes are now click-through, clipping parents reject hits outside their bounds, matching Figma behavior - Fix instance overrides on .fig import and clipboard paste — resolve guidPaths by overrideKey, handle component swaps (`overriddenSymbolID`), propagate through nested clone chains. Import and paste now share a single override engine. - Apply Figma component property assignments on import — boolean visibility toggles and instance swaps via `componentPropRefs`/`componentPropAssignments` - Apply `derivedSymbolData` sizes on import — containers now shrink correctly when component properties hide children - Fix override resolution for nested instance targets — check the current node before searching descendants - Fix component property assignments for nested instances — resolve scoped `componentPropAssignments` inside `symbolOverrides` via guidPath, handle `guidValue` for instance swaps, reorder phases so transitive sync doesn't clobber visibility - Pixel-perfect vector rendering using pre-computed `fillGeometry`/`strokeGeometry` blobs from .fig files — eliminates white gaps between adjacent stroked shapes - Stroke outlines on clipboard paste — convert vectorNetwork paths to filled outlines via CanvasKit when geometry blobs are unavailable - Apply `derivedSymbolData` transforms and geometry during import — instance children render at correct scale and position - Fix internal pages becoming visible after .fig round-trip — preserve `internalOnly` flag on export - Scope layout recomputation to current page for paste/undo/font-load (major speedup on large multi-page files) - Show loading overlay until all document fonts are loaded (no more partially rendered text) - Load fonts when switching pages (previously only loaded for the first page) - Always show visibility toggle on fill, stroke, and effect rows (matches Figma) - Fix renderer crash on double destroy when closing files quickly - Fix .fig page ordering — use deterministic byte comparison for fractional index positions - Fix text truncation using `textTruncation` field instead of `textAutoResize` - Fix horizontal scrollbar on design and pages panels - Style scrollbars for Tauri (thin dark overlay instead of default OS chrome) - Enable file watcher in Tauri — `watch` feature was missing from `tauri-plugin-fs` ## 0.6.0 — 2026-03-04 ### Features - Multi-selection properties panel — edit position, size, appearance, fill, stroke, and effects across multiple selected nodes - Shared values display normally, differing values show "Mixed" - W/H inputs in multi-selection mode - Flip horizontal/vertical using scale transform instead of rotation - Single-node alignment aligns to parent frame bounds - ACP agent package — Agent Communication Protocol server for AI coding tools, reusing core ToolDefs ### Build - Apple code signing and notarization for macOS builds - Git LFS storage moved from GitHub to Cloudflare R2 ### Fixes - Fix Figma clipboard paste: extract shared kiwi→SceneNode conversion, fixing broken auto-layout, missing gradient/image fills, effects, style runs, and text properties - Fix vector rendering on paste — scale path coordinates from Figma's normalizedSize to actual node bounds - Fix pasted instances having no children — populate from component via symbolData when both are in clipboard - Detect component sets on import — promote FRAME nodes with VARIANT componentPropDefs to COMPONENT_SET - Skip internal canvas on paste — components on Figma's hidden internal page populate instances but are not pasted as visible nodes - Apply instance overrides on paste — text content, fills, visibility, layoutGrow, and textAutoResize from symbolOverrides - Fix auto-layout child ordering — sort by geometric position instead of z-order position strings - Load fonts on paste and .fig import — collect font families from text nodes and load into CanvasKit - Text measurement in auto-layout — use CanvasKit paragraph metrics for WIDTH_AND_HEIGHT text nodes - Recompute layouts after font loading completes - Fix PERCENT line height conversion — was stored as raw value instead of pixels - Fix InvalidCharacterError when copying nodes with non-ASCII text - Load all font weight/style variants needed by pasted text nodes - Fix font loading not registering in core cache - Fix halfLeading applied to text measurement — enable only for rendering - Clear hover on zoom/pinch to keep scene picture cache valid - Fix flip buttons using rotation math instead of actual mirroring - Fix flip transform encoding — scale first matrix column only (was incorrectly producing 180° rotation) - Decode flip state from .fig transform matrix on import ## 0.5.1 — 2026-03-03 ### Fixes - Fix File → Save crash when document has layer blur effects ## 0.5.0 — 2026-03-03 ### Features - Effects rendering: drop shadow, inner shadow, shadow spread, layer blur, background blur, foreground blur - Text shadows render on glyphs instead of bounding box - Multi-file tabs — open multiple documents in tabs within a single window - Tab bar with close buttons, middle-click to close, and new tab (+) button - Keyboard shortcuts: ⌘N/⌘T new tab, ⌘W close tab, ⌘O opens in new tab - Native Tauri menu: File → New and File → Close Tab wired to tab actions - Render text from SkPicture cache when fonts are missing — pixel-perfect display without the font installed - Missing font indicator (⚠) next to font picker in the sidebar - Right-click context menu on layers panel — same actions as the canvas context menu - 40+ new AI/MCP tools ported from figma-use: - Granular set tools: `set_rotation`, `set_opacity`, `set_radius`, `set_minmax`, `set_text`, `set_font`, `set_font_range`, `set_text_resize`, `set_visible`, `set_blend`, `set_locked`, `set_stroke_align` - Node operations: `node_bounds`, `node_move`, `node_resize`, `node_ancestors`, `node_children`, `node_tree`, `node_bindings`, `node_replace_with` - Variable CRUD: `get_variable`, `find_variables`, `create_variable`, `set_variable`, `delete_variable`, `bind_variable` - Collection CRUD: `get_collection`, `create_collection`, `delete_collection` - Boolean operations: `boolean_union`, `boolean_subtract`, `boolean_intersect`, `boolean_exclude` - Vector path tools: `path_get`, `path_set`, `path_scale`, `path_flip`, `path_move` - Create tools: `create_page`, `create_vector`, `create_slice` - Viewport: `viewport_get`, `viewport_set`, `viewport_zoom_to_fit`, `page_bounds` - Misc: `flatten_nodes`, `list_fonts` - `set_text_properties` tool: alignment, auto-resize, decoration - `set_layout_child` tool: sizing, grow, align_self, positioning - 13 MCP server integration tests via `InMemoryTransport` ### UI - Resizable pages/layers split in left panel with reka-ui Splitter - Layers tree auto-expands and scrolls to reveal selected node - Loading overlay on canvas while opening .fig files - Hide internal-only pages (e.g. "Internal Only Canvas" in design systems) - Render page dividers — pages named with only dashes/asterisks/spaces show as horizontal lines - Only show component labels for COMPONENT and COMPONENT_SET, not instances - Replace all native `