github open-pencil/open-pencil v0.16.0

7 hours ago

OpenPencil 0.16 adds comments, shared rooms with voice calls and agents you can follow, slots, component properties, and components that behave like real controls in Preview, plus shader fills, a Lint tab, variable modes on layers, a pencil tool, and Vue and React code in the Code tab. .fig files open and save much closer to Figma, saving only what an edit changed, and selection, pasting, auto layout, text, and trackpad navigation follow Figma. The desktop app needs macOS 13 or later, and openpencil-mcp must be updated together with the app.

Breaking changes

  • The desktop app requires macOS 13 or later; the web app supports Chrome 111, Edge 111, Firefox 128, and Safari 16.4 or later.
  • Shared rooms record the layer tree in a new format, so people on this version and on earlier versions can no longer join each other's rooms. A room this browser saved with an earlier version converts when you open it again.
  • Update openpencil-mcp together with the app: the app renders render JSX itself and no longer accepts the pre-rendered tree earlier versions send. AI and MCP tools reject arguments they do not declare: defineTool from @open-pencil/core/tools takes a v.strictObject input. close_file fails on unsaved changes unless unsaved is "save" or "discard", and diff_create and diff_show patches list JSX attributes such as -rounded={8}, with diff_show taking attributes instead of a JSON props object, so patches in the old format no longer apply. openpencil documents is now a command group: list open documents with openpencil documents list.
  • design_to_tokens writes CSS custom properties named from each variable's code syntax or Tailwind namespace: its tailwind format is a Tailwind v4 @theme, the json format is removed, aliases stay var() references, and non-default modes go under their condition ([data-theme="dark"] by default).
  • A paint's gradientTransform follows Figma's convention everywhere, so gradients made in OpenPencil before this release draw differently. Build gradients with linearGradientTransform and ellipticalGradientTransform from the new @open-pencil/scene-graph/gradient, where isGradientFill and GradientFillType moved from @open-pencil/core/geometry; linearGradientEndpoints is removed.
  • .fig files store colour-variable bindings in Figma's colorVar. A paint bound through the old colorVariableBinding field, which only OpenPencil wrote, reopens unbound. VariableBinding, colorVariableBinding, encodeNodeChangeWithVariables, encodePaintWithVariableBinding, and encodeVarint are removed from @open-pencil/core and @open-pencil/core/kiwi.
  • computeImageHash from @open-pencil/core and @open-pencil/scene-graph returns the hex SHA-1 of the bytes, the name Figma gives an image, instead of an OpenPencil-only hash of the same length. New images are stored under that name, so .fig files and the Figma clipboard carry it as it is; images in documents saved before keep their names.
  • In scripts, a text node's fontSize, fontName, fontWeight, fills, letterSpacing, lineHeight, textDecoration, textCase, textStyleId, and fillStyleId read figma.mixed while its characters differ, as in Figma, and setting one restyles every character. fills is typed readonly Fill[] | symbol, so scripts that index it narrow it first.
  • Design JSX moved from @open-pencil/core to the new @open-pencil/design-jsx package. Import elements, helpers, variables, JSX_REFERENCE, buildComponent, sceneNodeToJSX, and selectionToJSX from there; @open-pencil/core and @open-pencil/core/design-jsx keep only renderJSX and renderTree, renderTreeNode is removed in favor of renderTree, and the @open-pencil/core/io/formats/jsx subpath is gone.
  • JSX export writes only OpenPencil JSX: JSXFormat and JSXExportOptions are removed, and Tailwind JSX comes from sceneNodesToTailwindJSX(graph, nodeIds) in @open-pencil/dom-css or @open-pencil/dom-css/export. @open-pencil/dom-css no longer requires @open-pencil/core, and exportHTMLBundle takes a font resolver in fonts instead of 'assets'; build one on exportWebFontFaceAssets from @open-pencil/core/text/web-font/assets to keep font files in standalone exports.
  • Shared primitives moved to @open-pencil/scene-graph: color conversion and management to /color, text and layout direction to /text-direction, randomHex, randomInt, and randomIndex to /random, fractionalPosition to /order-keys, and @open-pencil/scene-graph/layout-guides is now /layout. @open-pencil/core/bytes is removed in favor of js-base64; the @open-pencil/core root exports are unchanged.
  • SceneNode has new required keys textParagraphs, listSpacing, paragraphSpacing, paragraphIndent, hangingList, targetAspectRatio, isExposedInstance, strokeWeight, strokeAlign, and booleanOperation (which may be undefined), so code that builds SceneNode objects itself must include them. Stroke extends Fill, so every stroke states a paint type, usually 'SOLID'.
  • SceneNode.primaryAxisSizing and counterAxisSizing are AxisSizingMode ('FIXED' | 'HUG'): fill is stored on the child, as layoutGrow and layoutAlignSelf: 'STRETCH', as Figma stores it. Read and change per-axis sizing with layoutSizing, layoutSizingOptions, and layoutSizingUpdates from @open-pencil/scene-graph.
  • .fig reading moved to one reader. importNodeChanges and importClipboardNodes are replaced by parseFigFile and parseFigmaClipboard, populateLazyFigImportRoots and populateAllLazyFigImportRoots by populateFigPage and populateAllFigPages from @open-pencil/core/io/formats/fig, and populateAndApplyOverrides by interpretInstance with materializeInstance; assertFigPackageReady() is gone and FIG_PACKAGE_STATUS reads document-reader. .fig export takes text shaping from a runtime: buildDerivedTextDataV4 is removed, sceneNodeToKiwi needs the runtime that withFigExportRuntime(graph, canvasKit, write) passes to its callback, and FigNodeChangeExportRuntime takes shapeText(node) instead of getGlyphOutlineMetrics.
  • OpenPencil's plugin-data keys are defined once in OPEN_PENCIL_PLUGIN_DATA from @open-pencil/scene-graph and read or replaced with readPluginData and withPluginData. @open-pencil/fig/node-change drops OPEN_PENCIL_PLUGIN_ID (now in @open-pencil/scene-graph), the *_PLUGIN_KEY constants, upsertPluginData, removePluginData, and getOpenPencilPluginValue in favor of readNodeChangePluginData; SourceLibraryPublication moved to @open-pencil/scene-graph and SOURCE_LIBRARY_PUBLICATION_PLUGIN_KEY is gone.
  • Every process mints layer, variable, and collection IDs in a random session, so generateId() no longer starts at 0:1 in the CLI, MCP, or scripts. Code that needs fixed IDs calls setIdSession, which now returns the previous session, or passes the SceneGraph its own generator (#770).
  • Editor state gains required canvasVersion, which the canvas redraws on (call requestRender() instead of bumping sceneVersion), shapeHandleHover, and selectionTheme, which replaces RulerTheme.label. remoteCursors is now presenceCursors (PresenceCursor[], each with a kind of 'person' or 'agent'), and the Tool type, EDITOR_TOOLS, and TOOL_SHORTCUTS include 'COMMENT' on C.
  • Canvas and constants APIs changed: SceneGraph.hitTestFrame is now hitTestDropTarget, adoptNodesIntoSection is now adoptCoveredLayers, and SkiaRenderer.hitTestFrameTitle no longer takes selected IDs. SkiaRenderer.buildParagraph returns a TextLayout that draws with draw(canvas, x, y). AUTO_LAYOUT_BREAK_THRESHOLD, CORNER_ROTATE_ZONE, getDefaultCanvasBgColor, and CANVAS_BG_COLOR_DARK are removed: the selection's resize and rotation zones are TRANSFORM_ZONE and transformZoneAt in @open-pencil/core/geometry, which also owns the HandlePosition and CornerPosition types, SECTION_DEFAULT_FILL is replaced by SECTION_DEFAULT_FILLS, and new pages use PAGE_DEFAULT_BACKGROUNDS, both keyed by interface theme. Icons come from an IconProvider: fetchIcon, fetchIcons, searchIcons, searchIconsBatch, and clearIconCache are replaced by the default iconify provider, createIconifyProvider(), and placeIcon, and VectorizedPath from @open-pencil/core/vector replaces clipNetworks with clips.
  • @open-pencil/vue composables act on the whole selection: useStrokeControls and useEffectsControls no longer take a node, useTypography edits every selected text layer and its weights (also the TypographyControlsRoot slot prop) is a computed list of the families' weights, read with .value, and usePosition reports x, y, and rotation as Figma's panel does. useCanvasInput returns autoLayoutValueEdit with updateAutoLayoutValueEdit, commitAutoLayoutValueEdit, and cancelAutoLayoutValueEdit in place of the padding-only autoLayoutPaddingEdit and its functions; the edit names its field (a padding or itemSpacing), and autoLayoutValueAnchor places its editor. useVariables().collections returns copies and addVariable returns the new ID; useVariablesEditor, useVariablesTable, useVariablesDialogState, and the @tanstack/vue-table dependency are removed. useFlatReorderDrag takes operations and onCombine, and the updates messages replace the system update dialog strings with the Software Update window's.

Added

  • Comment on designs, as in Figma: the Comment tool (C) pins threads to layers, with replies, resolving, rich text, and a searchable, filterable list in the sidebar. Comments are saved in the .fig file, so they travel through storage and shared rooms, and scripts, the AI chat, and MCP clients read, add, reply to, and resolve them.
  • Be in several shared rooms at once, each in its own tab and syncing in the background, and talk with the people in a room in a peer-to-peer voice call from the headphones beside the avatars. Join from Home with Join room… or a pasted link, open a browser room in the desktop app with Open in desktop app, and join right away under a generated name such as Teal Fox. Everyone in a room sees and edits the document's variables and the overrides of its instances.
  • Follow collaborators and AI agents: avatars count each person's agents and show what they are doing, clicking follows them, and your own agents are followed while they work. Agents from the AI chat, ACP and Pi harness chats, and MCP clients show labeled cursors at the layers they touch and leave the canvas when their reply ends, and the Pages panel and command palette show who is on each page. The command palette also lists recent pages and Go to page….
  • Create and fill slots, as in Figma, from the canvas, the properties panel, scripts, and AI and MCP tools: slots have names, limits, and preferred components, instances show each slot as Default or Modified, and Reset slot brings back the component's content.
  • Create and edit component properties and variants from the properties panel, as in Figma (#847): link text, visibility, and swapped components to text, boolean, and instance swap properties, expose nested instances, reorder properties, and turn a component into a set with Add variant.
  • Make components behave like real controls: the Behaviour section turns a component or set into a Button, Switch, Checkbox, Slider, Tabs, Text field, or another of 15 Reka UI-style controls, and View → Preview (⌥⌘↩) lets you use them live without changing the document, drawn as the canvas draws them, in its variable modes. Scripts, AI and MCP tools, and design JSX build them too.
  • Fill layers with shaders from the open-source shaders library: the fill picker's Shader tab stacks effects with generated controls and takes presets copied from shaders.com. The canvas shows the first frame and preview plays it where WebGPU is available; .fig files and the Figma clipboard keep the frame as an image and the shader's preset, so Figma still opens them.
  • Apply a variable mode to layers and pages from Appearance, or the Page section with nothing selected, as in Figma: choose Auto or a mode for each collection the layers use. Layers set to a mode show it after their name in Layers.
  • Draw freehand with the pencil tool (⇧P): strokes become smoothed, editable vector paths, with Shift for straight lines, as Figma's pencil does.
  • Bulleted and numbered lists in text, as in Figma, with nesting, list and paragraph spacing, indents, and hanging markers, set from the Typography section or with ⇧⌘8 and ⇧⌘7. Lists round-trip through .fig files, the Figma clipboard, HTML, design JSX, and scripts.
  • Style selected characters while editing text, as in Figma: the Fill and Typography fields show and change the selection, and any range of characters keeps its own case, link, text and fill styles, and variable bindings. The canvas draws each range in its own case, .fig files and the Figma clipboard keep them, apart from font weight bindings, and HTML, Tailwind, Vue, and React exports keep character styles, links as <a href>, cases as text-transform, and lists; HTML import reads them back. Scripts style ranges with getStyledTextSegments and the getRange/setRange methods, including getRangeTextCase, getRangeHyperlink, getRangeTextStyleId, getRangeFillStyleId, and getRangeBoundVariable with their setters, and the text's hyperlink. Design JSX writes <Span> and <Paragraph> with decoration style, colour, thickness, offset, skip ink, textCase, and href.
  • Edit shapes on the canvas, as in Figma: gradient end points and stops while a gradient's picker is open, corner radius handles on rectangles, polygons, and stars, a polygon's or star's point count and inner ratio, also in the properties panel, and auto layout padding and gaps, which open for editing on a double-click.
  • Switch a frame, group, or section to another of them from the layer type menu in the properties panel header, as in Figma. A frame becomes a group that keeps its layers, effects, and opacity and fits them; a group becomes a frame of its bounds that clips. A frame or group on the page or in a section becomes a section that keeps its fill, strokes, corners, and layers where they are, and a section becomes a frame.
  • Lock a layer's aspect ratio beside its width and height, as in Figma; the lock survives .fig files and the clipboard and is available in the plugin API.
  • Show the pixel grid when zoomed in, toggled with View → Pixel Grid or ⇧', and snap moves, resizes, and drawing to whole pixels with Snap to Pixel Grid (⇧⌘').
  • Figma's navigation keys move through layers and the document: Enter selects children, or opens a selected vector's path for editing, Shift+Enter or \ selects the parent, Tab and Shift+Tab select siblings, N and Shift+N zoom to the next or previous frame, Page Down and Page Up switch pages, and Shift+0 zooms to 100%.
  • Rearrange auto layout as in Figma: drag several layers along a row or column as one block, move a layer one slot with the arrow keys, take it out with Ignore auto layout, and see the layout's children outlined on hover.
  • Edit several layers at once: a multi-selection gets Layout, Typography, corner radius, and stroke sections, plus Selection colors, which recolours a colour everywhere it is used. The layout guides of several frames list and edit together while they match, and Add gives each frame one new guide.
  • Check designs in the new Lint tab: issues are grouped by rule, marked on the canvas and in the Layers panel, and many have one-click fixes such as binding colours to matching variables. openpencil lint --fix and the lint and lint_fix tools apply the same fixes outside the app.
  • Edit variables in a redesigned variables dialog: collections and groups in a sidebar, search across names and values, inline editing, drag to reorder, multi-select, CSS names and expressions, per-mode conditions written in plain words, and a live stylesheet preview. Collections copied from a library are marked, with a warning on variables Figma won't show.
  • Import and export variables, text styles, and effect styles as W3C design tokens, in the shape Figma imports, from the variables dialog, by dropping .tokens, .tokens.json, or .resolver.json files on the editor, with openpencil tokens --format dtcg, and with the import_design_tokens tool. Copy variables as CSS or a Tailwind v4 theme, or print them with openpencil tokens.
  • Insert icons from Iconify's open-source sets with an icon picker in the toolbar, Object menu, and command palette. Placed icons keep their name, so the Icon section can swap or recolour them, and exports write them as icons.
  • See the selection as a Vue or React component in the Code tab, styled with CSS or Tailwind, and see which layer the cursor's element draws, with design issues underlined in the code.
  • Export components to Storybook with openpencil export -f storybook: one story file per component for React, Vue, or HTML, with variant controls, and with --framework vue or --framework react, working Reka UI or Radix UI components (#727). Export HTML and Tailwind JSX from the app and the CLI (-f tailwind-jsx), with vector shapes as inline SVG, and choose PPTX in the Export panel.
  • Open a .fig, .pen, or other supported document by dragging it onto the window, paste SVG markup copied as text, such as Figma's Copy as SVG, and open HTML files in CLI commands such as openpencil tree and openpencil convert.
  • Save the open document to cloud storage with File → Save to storage…; the tab stays bound to the stored copy, so Save and auto-save write to the bucket. A new Cloud Storage guide covers setup, and Settings → Cloud storage offers the OpenPencil Cloud waitlist.
  • Set up AI in a few steps with guided setup: on first launch, pick a coding agent on this computer, an API account, or a local server. The desktop app installs or updates agents, adapters, the MCP server, and the Harness companion with one click, through Bun when Bun installed them, tests each connection, and proposes models; where npm's global folder needs administrator rights, setup says so and offers the sudo command, Bun, or a prompt for your agent. The MCP server and Harness companion print their version with --version; sign in to OpenRouter instead of pasting a key.
  • Choose how much an AI model thinks for each message, from Off to Extra high, attach images by dropping them on the chat, and watch designs appear on the canvas while direct providers stream JSX.
  • Review and undo AI edits from the chat: each tool call shows a summary, its input and output, and a before-and-after image with a JSX diff. Revert a reply's edits, restore them, regenerate the last reply, retry a failed message, or edit and resend it; undoing an edit tells the AI on the next message. The conversation menu saves a chat's trace as a JSON file, with every message, reasoning step, tool call, and request.
  • Control the running app from the CLI and MCP: openpencil documents, undo, redo, and settings, the matching MCP tools, openpencil tool list, describe, and call to run any MCP tool from the shell, and openpencil diff with the diff_visual and diff_apply tools to compare and replay changes. Share only the selected layers with MCP clients from Settings → MCP → Local server or OPENPENCIL_MCP_SCOPE=selection.
  • Diagnose failures from Settings → Diagnostics: errors, failed AI chats, and broken tool calls are recorded with scrubbed messages and stacks, filterable, copyable with the app version, and saved as a JSON file.
  • Choose an accent color in Settings → General → Appearance, and customize the toolbar in Settings → Toolbar or from its right-click menu: hide, reorder, and regroup tools on desktop and mobile.
  • Official builds send one anonymous ping a day, with the app version, operating system, architecture, desktop or web, and whether usage stats are shared, and no ID; turn it off in Settings → Privacy or with OPENPENCIL_TELEMETRY=0 or DO_NOT_TRACK=1 in the desktop app's environment. Usage stats (launches, file formats opened and exported, and which AI provider answered) are sent only after you agree in a one-time prompt after the welcome screen. The privacy page lists every field.
  • Use more of the Figma plugin API: instance.swapComponent(), detachInstance(), figma.getNodeByIdAsync(), getMainComponentAsync(), and an instance's overrides with resetOverrides(). OpenPencil's own openpencil script global adds comments and controls.
  • Write design JSX with more of the document: visible, locked, constraints, italic, full stroke props, and component properties set by name on <Instance> (#750). JSX export writes them back with every fill, effect, mask, and variable binding, and TSX can use @open-pencil/design-jsx as its JSX import source.
  • New SDK entry points: pass an ID generator to new SceneGraph(), place pasted content with resolvePasteTarget from @open-pencil/core/editor, compute Flatten and Outline stroke with flattenNodesToVectorProps and outlineStrokeNodesToVectorProps from @open-pencil/core/canvas, import designDocumentToSceneGraph from @open-pencil/dom-css/scene-graph in browser builds, and tell FontManager.setBundledFontLocator() where a bundler put the fonts from the new @open-pencil/core/assets export.
  • New npm packages: @open-pencil/design-jsx for writing and exporting design JSX, and @open-pencil/emit, which it and the exporters use to print source code.
  • openpencil.dev opens on live editor demos of design, AI, code export, the CLI, the SDK, and collaboration, in every docs language, with a short roadmap, and a download page offers the installer for the visitor's system and lists every installer with Homebrew and the web app.

Changed

  • Selecting, dragging, drawing, duplicating, and pasting follow Figma: clicks select a top-level frame's direct children and double-clicks go deeper, a click up to 3.5 screen pixels outside a shape selects it, a click on a section's empty area selects the section, dragged layers land in the frame under the cursor, groups and locked frames never take a drop, and Space, Shift, and Control change how a drag reparents. A layer leaves auto layout once its edge is about 5 px past the frame instead of after the cursor travels 96 px, and a duplicate is stacked directly above its original.
  • A selected layer resizes and rotates where Figma's does: every edge resizes along its whole length within 7.5 px outside it and up to a tenth of the side inside it, corners within 7.5 px, and the area outside a corner within 16 px rotates; a frame's name takes a press before rotation but not before resizing. A selection draws Figma's four corner handles, without the edge midpoints, and layers under 10 px on both sides show no handles or size label.
  • Pasted layers keep how they were turned and flipped on the canvas, as in Figma, and land where Figma puts them: layers copied out of a frame or section keep their offset from its box, move into the frame only when they start past its far edge or miss it, and paste back where they were when nothing is selected, or in the middle of the view when that place is out of view.
  • Trackpads and mouse wheels pan and zoom the canvas as in Figma: a two-finger pan moves the canvas twice as far, up to 120 px per scroll event, a pinch zooms as far in the desktop app and Safari as in other browsers, also over Preview's controls and comment pins, and on Windows each mouse wheel notch pans 16 px, speeding up to 120 px when notches come less than 50 ms apart.
  • Layers move and turn as in Figma, on the canvas and in scripts: the properties panel shows X and Y of a turned layer's box and a counterclockwise rotation, children of groups report positions in their container, and groups and booleans refit to their contents.
  • New layers, pages, and sections look as in Figma: white frames that clip, #D9D9D9 shapes, black 1 px lines, theme-aware page backgrounds and sections, numbered names such as "Rectangle 2", and Figma's results for Group, Frame selection, Create component, booleans, and Combine as variants.
  • Tool shortcuts and cursors follow Figma: Section moves to Shift+S and Frame also answers to A, and the pen, comment, and hand tools show their own cursors, with the hand closing while panning.
  • The font weight list offers only the weights the text's family has, as in Figma. Switching family keeps the weight and italic where the new family has them, otherwise the nearest weight with the same slant.
  • The Layout guide section follows Figma: each guide is a row with a summary such as "5 columns" or "Grid 10px" that switches between grid, columns, and rows, and its settings open from the row's icon with Count, Type, Width or Height, Margin or Offset, Gutter, and Color fields. The section now sits after Selection colors.
  • A layer keeps its stroke weight and alignment without strokes, as in Figma, and strokes take the same paints fills have, including gradients and images (#797).
  • Layers inside components and instances are outlined in purple, and bound variables show as a neutral chip instead of purple text.
  • Import SVG as editable layers, as pasting into Figma does: groups, one vector per shape, text layers, and clip path groups instead of one merged vector (#734).
  • The plugin API follows Figma inside instances: it refuses to move, rotate, constrain, or mask a layer an instance takes from its component, or to change a vector's path there, with Figma's errors, while slot content stays editable, and setting a layer to the value it already has makes no override. setProperties switches an instance's variant, alongside its other properties, and fails without changing anything when no variant has the values; resize places a frame's layers by their constraints while resizeWithoutConstraints leaves them; a new frame or component hugs along its main axis once auto layout is on, and resize fixes a hugging auto layout frame's size.
  • Popovers, menus, selects, and pickers share one look and motion, selects open over their field, every section adds and removes items with + and −, and variables, styles, and swap components come from one searchable list. Menus taller than the window scroll.
  • Settings… (⌘,) is in the OpenPencil menu on macOS and at the end of the File menu elsewhere, instead of under View → Preferences.
  • The demo at /demo is a tour of one player card in eight chapters stacked on one canvas, each showing one thing with a numbered card that says what to try and draws the piece of the app to click: preview, the artwork slot, the controls' behaviours, the theme's modes, shader fills, the Code tab, Lint on a card with planted issues, and sharing and opening your own files. It opens instantly, built ahead of time, on the first chapter, and follows the app's light or dark theme. The welcome dialog on first launch offers it next to AI setup, and File → Open the Tour opens it any time.
  • The desktop app shows updates in a Software Update window with formatted release notes and cancellable download progress (#743). On macOS and Linux you choose when to restart, and restarting asks about unsaved documents first.
  • Design lint reports far fewer false positives, checks touch targets against WCAG 2.2 AA in the Recommended preset, asks 3:1 contrast of large text as WCAG AA does, and reports unbound colours and off-scale spacing as suggestions.
  • HTML and Tailwind export write variable-bound values as their tokens, such as var(--color-primary) or bg-primary, and Tailwind JSX uses the same class mapping as Tailwind HTML.
  • The AI chat starts with comment, variable, lint, page, and image export tools on, keeps working on the page where it started, and recommends the latest models, with Claude Sonnet 5.5 and GPT-6.1 Sol as the new defaults. Saved profiles keep their model.
  • Settings → MCP shows the local server in the desktop app and WebMCP in the browser, where each works, and Tool access lists the local server's tools while it is stopped.
  • Invalid tool arguments from the AI chat, CLI, or WebMCP name the tool and list every problem with its location.
  • Run Pi with the providers and default model you signed in to in the Pi CLI, and see what to fix when its Harness companion or MCP server is missing or out of date. Codex install instructions point at @agentclientprotocol/codex-acp, which replaces @zed-industries/codex-acp.
  • get_selection returns the selected layers with their direct children by default and takes a depth, and openpencil eval prints the value of a script's last expression.
  • Opening a share link opens the room in its own tab, so an open document is never changed, and leaving a room keeps its file as a local copy.
  • Text in both themes meets the WCAG AA contrast ratio of 4.5:1.
  • The Linux AppImage no longer bundles xdg-open and relies on the system's xdg-utils.

Fixed

  • Instances follow their component as in Figma: style overrides from .fig files survive component edits, every field an instance does not override follows the component, restructured component layers move their instance copies with overrides, and properties set on nested instances show and stay set when pages load on their own (#849). Resizing an instance no longer turns its layers' sizes into overrides, copies that size themselves to their content keep their size through component edits and reopen at their saved size, and a path edited in a component reaches its instances, scaled with each copy.
  • Save .fig files that open correctly in Figma: overrides beyond text and fill colour, exposed nested instances, slot content, blend modes, bound variable values, variable metadata, fill-sizing, ordering keys, and stable layer IDs (#890). An opened .fig stays unchanged until edited, saves before its first edit, and saves variable-only edits.
  • Saving an opened .fig file writes only what an edit changed in a layer or variable and keeps the rest as Figma saved it, including data OpenPencil does not model, an instance's library link and saved overrides, and text's letter spacing in percent and font names. A variable's new value reaches every layer that uses it, through an alias too, variables that alias library variables still point at the library, and a collection's modes keep their order in Figma.
  • Shared and library styles survive .fig files: the properties panel shows the styles layers use in opened files, styles made in OpenPencil save as styles and reopen, layers that use a library style keep it in Figma, and a style an instance has cleared stays cleared.
  • Figma shows images in .fig files OpenPencil writes, stored under the SHA-1 of their bytes, the only name Figma reads. Instances pasted into Figma keep their names and no longer carry their component's library identity, and fill and stroke bindings to variables made in OpenPencil survive the paste as remote variables.
  • Importing design tokens adds them to the file's own collections, never to a collection copied from a library, where Figma did not show them.
  • .fig files whose text content is bound to a number or boolean variable open instead of failing with "Unsupported variable expression STRINGIFY", and saving keeps the binding (#1110). Rounded points in imported vectors keep their own corner radius and join, layout guides save, show as columns or rows, and draw an Auto count as Figma does, and a file's thumbnail shows its page's own background.
  • Showing or hiding a layer of an instance that a boolean component property drives sets the property, as in Figma, so every layer it drives follows.
  • Text saved to .fig or copied to Figma keeps its wrapping, alignment, line height, ligatures, and decorations there (#914), and shows there when drawn in a fallback, missing, or variable font, truncated, or with an empty line. Auto line height stays auto when .fig files are opened and saved, and a pixel line height shorter than the font opens as set.
  • Lines of text that mix font sizes or line heights are spaced as in Figma: each line is as tall as the tallest line height on it, with its largest font centred in it, instead of stacking the tallest parts of every style on it.
  • Auto layout behaves as in Figma: Fill children in Hug frames keep their size, frames from .fig files reflow when edited, absolute children follow constraints, Hug frames grow while typing, a row that hugs its height around a stretched layer keeps it in Figma, and undo puts back everything an auto layout change moved.
  • Draw like Figma: gradients from Figma files and pastes no longer run backwards, blurs and shadows use Figma's falloff, masks apply their opacity and blur, layers masked by a group show, polygons and stars keep their rounded corners, point count, and inner ratio, also when copied to Figma, and the corners of a stroked path take its join, with caps only at its ends.
  • Underlines and strikethroughs follow the font's metrics and Figma's styles, outlined or flattened text keeps its glyph positions, text on a path turns each character along the stretch it covers, and fonts load reliably: styles of variable fonts and macOS font collections, CJK and Arabic fallbacks, and a clear notice for unsupported outlines such as PingFang (#746, #752).
  • Undo covers more: hiding, locking, renaming, and moving layers to another page, and layers MCP clients, the CLI, and AI chat create, delete, or move, including on other pages and in variables. Hiding or locking several layers is one step, undo and redo work while a colour picker is open, and undoing back to the last save shows the document as saved.
  • The pen tool redoes a path with its points, fill, and stroke, Escape keeps the path drawn so far, and the distance for closing a path no longer changes with zoom. Path editing closes when its vector is deleted or another document opens, and editing a vector's points no longer leaks memory.
  • Dragging a layer down the layers panel drops it where the line shows, and moving a layer into a frame or group from the panel keeps it where it's drawn on the canvas.
  • Opening a page also loads the pages holding the component sets its instances use, so the variant picker can switch to any variant of a set kept on a page not opened yet.
  • Import HTML and CSS laid out as a browser lays it out, with correct shadows, borders, and box-sizing, in openpencil import, the app, and the Code tab's preview (#788).
  • Import SVG files with an XML declaration or a byte-order mark, fill-opacity, stroke-opacity, open filled subpaths, and stroke caps and joins that survive saving. SVG export, and the drawings built on it, draw a vector's open strokes beside its filled regions.
  • Design JSX lays out justifyContent, alignItems, grid repeat() tracks, fill sizes in grids, and box-shadow lists as written, applies props it used to ignore, reports bad gradient arguments, warns about ignored paint and effect options (#736), and keeps line breaks when exporting multi-line text.
  • HTML export keeps grid layouts, rotation, every shadow and blur, flex grow, right-to-left direction, sections, and the positions of layers outside auto layout, declares box-sizing: border-box, and names a JPEG, GIF, or WebP image fill by its own type.
  • The plugin API reads and writes lineHeight, letterSpacing, effects, paints, page backgrounds, layoutGrids, and componentPropertyReferences in Figma's shapes, updates geometry and text size immediately, and refuses invalid sizing and component cycles as Figma does (#786). openpencil eval -o saves a script's edits laid out, and CLI eval and tools measure text in the document they edit; @open-pencil/core/figma-api exports flushPendingLayout for hosts that save scripts' results.
  • AI, MCP, and CLI tools reject arguments they do not take (#977), keep variable bindings through stdio render (#830), size groups and booleans to their contents (#738), work on pages other than the one on screen, run XPath queries under Node (#787), judge text contrast by its WCAG ratio (#735), and report failed saves as errors.
  • AI chat replies stop cleanly, survive switching pages, accept eval results of any shape, use a newly saved API key right away, and keep your message when the chat cannot start. In the desktop app, replies stream and keep going past 30 seconds, a request that cannot reach the provider is retried, and scrolling back to the bottom resumes following. Pi chats start with OpenPencil's MCP tools on Node 22.15 or later, resume cleanly, and stop a reply without losing the session.
  • In the desktop app, Select All, Undo, and Redo typed in a text field such as the chat input act on the field instead of the canvas, and other canvas shortcuts no longer reach the canvas from a text field.
  • Shared rooms keep one consistent layer tree when people move, reorder, or add layers at the same time (#888, #889).
  • Storage keeps working: browser storage saves after Safari suspends a tab, in Private Browsing, and for documents over 127 MiB; crash recovery and autosave run after edits instead of on redraws, which froze Safari; S3 storage works with servers that answer range reads with 200, such as rclone serve s3; and when saved credentials are paused, the home screen offers Retry access.
  • Memory stays flat during long AI and MCP sessions (#587), image-heavy .fig files open without running out of memory (#924), and closing a tab releases its document.
  • Show a clear message instead of a blank window when the browser or WebView is too old, and start on macOS 13 with older WebKit (#744).
  • Picking an OkHCL colour no longer overwrites a layer's other plugin data, including other plugins' data and its export settings.
  • Warnings and errors show in the theme's colors: library update and diagnostics warnings, errors on the home screen and when renaming layers, publishing a library or authoring variants, and chat errors, copy confirmations, and error toasts in the light theme.
  • Smaller fixes: layers keep their place when moved into or out of a rotated or flipped parent (#737), the Vue SDK's CanvasSurface starts, .pen text sizes from its font, whole-step number field drags, ⌘Z after nudges, ⇧-resizing, Paste here in transformed frames, zooming to a fixed level, the font picker's starting position and search order, section and frame titles, tab and menubar overflow, the mobile file menu's labels, tooltips, full hex and opacity values, component set borders at every zoom, the caret in empty text, imported page backgrounds, .fig thumbnails in recent files, the canvas context menu closing on a quick second right-click, model pickers in Settings → Models closing while the editor slides in, and screen reader labels.
  • Ship the MIT license text in every npm package, with READMEs for @open-pencil/core, @open-pencil/cli, and @open-pencil/mcp.

Performance

  • Open large .fig files with a fraction of the memory: the Nuxt UI design kit takes 0.7 GB instead of 2.2 GB, and pages share unchanged component data and JavaScriptCore object layouts.
  • Save an edited .fig by rewriting only what changed, off the main thread in the browser: the 109 MB Preline UI kit saves in about 3 seconds instead of over two minutes, with about 1.5 GB less memory on large kits.
  • Open multi-page .fig documents, lay out pages with many text layers and instances, and edit text selections and variables without long freezes or full redraws.
  • Joining a shared room is faster: the document a person receives on joining is compressed, about a twelfth of its size before (480 KB instead of 5.7 MB for the demo tour), and edits travel in Yjs's more compact encoding.

Security

  • Limit what the desktop app may write and run: no writes to startup locations, shell profiles, global package folders, or MCP discovery files, and only the exact command lines of supported agents, the MCP server, and the Harness companion.
  • Update to Tauri 2.12, which binds large IPC responses to the requesting webview (GHSA-w28w-mhc8-qvjv), and install desktop updates only when the signature names the announced version.
  • Evaluate calc through jsep and an arithmetic allowlist instead of expr-eval, which had an unpatched code-execution advisory (GHSA-q9v2-7m5w-4693); calc now supports ** and nothing it does not document.
  • Escape layer names and text in JSX and Tailwind JSX export, so document text cannot inject attributes or expressions into render and replace.
  • Validate collaborator cursors and names, clipboard data, component library revisions, and MCP and automation messages before using them, and update @xmldom/xmldom to 0.9.12, which fixes quadratic parsing of crafted SVG and XML.

Don't miss a new open-pencil release

NewReleases is sending notifications on new releases.