Reuse safeColor() (which defaults a ?? 1) in the variable export
path. The Kiwi schema requires all Color struct fields — variables
created without explicit alpha caused 'Missing required field a'.
Fixes#128
New module packages/core/src/random.ts with randomHex, randomInt,
randomIndex — centralizes all crypto-based randomness.
Replaces:
- Date.now() IDs in VariablesDialog (not unique, not random)
- Hand-rolled hex token in automation/server.ts
- Inline crypto.getRandomValues in clipboard.ts, use-collab.ts
Also adds --color-component Tailwind theme color (#9747ff) and
replaces all 6 hardcoded occurrences across DesignPanel, LayerTree,
NodeContextMenuContent, and menu.ts.
- Add IS_BROWSER constant to core/constants.ts, rewrite IS_TAURI to use it
- Replace 9 raw 'typeof window' checks across canvaskit, fig-export,
figma-api, fig-file, fonts, SafariBanner, use-chat
- Promote no-typeof-window-check lint rule from warn to error
- Extract array mutation helpers (updateArrayItem, removeArrayItem,
toggleArrayVisibility) into useMultiProps — DRY up FillSection and
StrokeSection (removes 20+ duplicated for-loops)
- Merge loadLocalFont + tryLoadLocalFont into findLocalFont with
optional style param — eliminates duplicated queryLocalFonts logic
and adds consistent variable font filtering for CJK fallback
- Replace nested ternary font matching with a simple loop over
family name candidates
- Extract fetchBundledFont helper for browser/Node font resolution
- Ship Inter-Regular.ttf in packages/core/assets so headless CLI
can resolve the bundled fallback via import.meta.url instead of
fetch('/') which fails in Node/Bun (fixes#121 scenario)
- Use IS_BROWSER constant instead of repeating typeof window checks
MobileHud now uses useCollabInjected() directly — same pattern as
CollabPanel — and handles share/join/disconnect internally.
Removes broken event handlers that were never defined in EditorView.
* fix(set_layout): default to HUG sizing when enabling auto-layout
When `set_layout` transitions a frame from `layoutMode: 'NONE'` to
auto-layout, it now sets `primaryAxisSizingMode` and
`counterAxisSizingMode` to `'AUTO'` (which maps to HUG internally).
Previously these remained at `'FIXED'`, causing the frame to keep its
original dimensions instead of shrinking/growing to fit children. This
made `h="hug"`, `justify="end"`, and `grow={1}` appear broken when
containers were created via the `set_layout` MCP tool (as opposed to
the JSX `render` path, which already defaulted to HUG).
The fix only applies when `direction` is provided and the frame was
previously in `layoutMode: 'NONE'` — updating spacing or alignment on
an existing auto-layout frame does not reset sizing modes.
* docs: add changelog entry for set_layout HUG sizing fix
Replace ssrLoadModule hack with simple child_process.spawn.
Same approach as production (Tauri sidecar) — just run the MCP
server entry point directly. No re-evaluation on HMR, no
WebSocket reconnect storms, no globalThis guards.
Remove headless SceneGraph mode — the MCP server now always proxies
tool calls to the browser via WebSocket. One server, one architecture:
HTTP :7600 — /health, /rpc (CLI), /mcp (MCP Streamable HTTP)
WS :7601 — browser connects, executes tool calls against live editor
- Delete http.ts (merged into server.ts)
- Delete headless SceneGraph, open_file, save_file, new_document
- Delete canvaskit-wasm dependency (no headless rendering)
- Add ws dependency for WebSocket server
- Rewrite tests with mock browser over real HTTP+WebSocket
- Simplify bridge.ts to 6 lines (just imports and starts the server)
- Replace ssrLoadModule hacks with direct imports from @open-pencil/core
- Replace isBunRuntime() hack with @hono/node-server (works everywhere)
- Remove ViteServer type parameter from startAutomationBridge
- Upgrade @open-pencil/mcp zod dependency from v3 to v4
- Add @open-pencil/mcp as root devDependency for bridge imports
The shim must export everything vue-stream-markdown destructures
from import('shiki'): bundledThemesInfo, bundledLanguagesInfo,
createHighlighter. A throwing shim causes the import to succeed
but all named exports to be undefined.
The cancel request was sent to the agent but the stream stayed open
until prompt() resolved, leaving the UI in streaming state. Now the
stream closes immediately with a finish chunk, guarded against
double-close from the concurrent prompt resolution.
Replace npm vue-stream-markdown with open-pencil fork that guards
bundledThemesInfo/bundledLanguagesInfo with ?. to prevent TypeError
when shiki is not installed. Revert shiki shim and cdn-options hacks.
The throwing shim caused hasShikiModule() to return false, but
import('shiki') still resolved in some code paths where the module
was imported directly without the has-check, causing undefined
destructuring and the 'e.find' TypeError.
Without context, the agent uses its own file/terminal tools instead
of the MCP design tools. Prepend instructions on the first message
telling it to use open-pencil MCP tools to draw on the canvas.
ACP agents now get an MCP server at http://127.0.0.1:7600/mcp that
proxies tool calls through the automation WebSocket to the browser,
where they execute against the live editor store. No separate
subprocess, no stale npm package — tools operate on the open canvas.
- Add /mcp endpoint to automation bridge (Streamable HTTP transport)
- Each MCP tool call → sendToBrowser({command:'tool'}) → browser executes
- Strip mcp__open-pencil__ prefix from tool names in chat UI
- Export paramToZod from MCP package for bridge reuse
Pass openpencil-mcp as a stdio MCP server to the ACP session so
agents get all design tools (create shapes, render JSX, export
images, etc.) instead of just plain conversation.
- Add makeFigma option to createServer for external FigmaAPI injection
- Skip file lifecycle tools (open/save/new) when using external FigmaAPI
- Pass MCP server config in ACP newSession mcpServers
- Add bun to Tauri shell scope for MCP server subprocess
- Add scoped command definitions for claude-code-acp, codex-acp, gemini
in Tauri shell plugin capabilities (required for Command.create)
- Show error banner in ChatPanel when transport initialization fails
instead of silently swallowing the error