2026-02-28 22:03:07 +00:00
|
|
|
{
|
|
|
|
|
"name": "@open-pencil/core",
|
2026-03-09 14:43:11 +00:00
|
|
|
"version": "0.9.0",
|
2026-03-02 05:31:54 +00:00
|
|
|
"license": "MIT",
|
2026-02-28 22:03:07 +00:00
|
|
|
"type": "module",
|
Add subpath exports to @open-pencil/core
12 domain-specific subpath exports for targeted imports:
scene-graph, kiwi, tools, renderer, render, rpc, figma-api,
canvaskit, layout, color, render-image, profiler.
- Create kiwi/index.ts barrel (codec, fig-file, fig-import, protocol)
- Route main index.ts kiwi re-exports through the new barrel
- Add exports map + publishConfig mirror for npm consumers
- Mark package as sideEffects: false for tree-shaking
- Document subpath exports in AGENTS.md
2026-03-12 16:58:32 +00:00
|
|
|
"sideEffects": false,
|
2026-02-28 22:03:07 +00:00
|
|
|
"exports": {
|
2026-02-28 22:24:54 +00:00
|
|
|
".": {
|
2026-03-02 12:02:43 +00:00
|
|
|
"types": "./src/index.ts",
|
2026-03-04 11:51:44 +00:00
|
|
|
"bun": "./src/index.ts",
|
2026-03-02 12:03:40 +00:00
|
|
|
"default": "./src/index.ts"
|
Add subpath exports to @open-pencil/core
12 domain-specific subpath exports for targeted imports:
scene-graph, kiwi, tools, renderer, render, rpc, figma-api,
canvaskit, layout, color, render-image, profiler.
- Create kiwi/index.ts barrel (codec, fig-file, fig-import, protocol)
- Route main index.ts kiwi re-exports through the new barrel
- Add exports map + publishConfig mirror for npm consumers
- Mark package as sideEffects: false for tree-shaking
- Document subpath exports in AGENTS.md
2026-03-12 16:58:32 +00:00
|
|
|
},
|
|
|
|
|
"./scene-graph": {
|
|
|
|
|
"types": "./src/scene-graph.ts",
|
|
|
|
|
"bun": "./src/scene-graph.ts",
|
|
|
|
|
"default": "./src/scene-graph.ts"
|
|
|
|
|
},
|
|
|
|
|
"./kiwi": {
|
|
|
|
|
"types": "./src/kiwi/index.ts",
|
|
|
|
|
"bun": "./src/kiwi/index.ts",
|
|
|
|
|
"default": "./src/kiwi/index.ts"
|
|
|
|
|
},
|
|
|
|
|
"./tools": {
|
|
|
|
|
"types": "./src/tools/index.ts",
|
|
|
|
|
"bun": "./src/tools/index.ts",
|
|
|
|
|
"default": "./src/tools/index.ts"
|
|
|
|
|
},
|
|
|
|
|
"./renderer": {
|
|
|
|
|
"types": "./src/renderer/index.ts",
|
|
|
|
|
"bun": "./src/renderer/index.ts",
|
|
|
|
|
"default": "./src/renderer/index.ts"
|
|
|
|
|
},
|
|
|
|
|
"./render": {
|
|
|
|
|
"types": "./src/render/index.ts",
|
|
|
|
|
"bun": "./src/render/index.ts",
|
|
|
|
|
"default": "./src/render/index.ts"
|
|
|
|
|
},
|
|
|
|
|
"./rpc": {
|
|
|
|
|
"types": "./src/rpc/index.ts",
|
|
|
|
|
"bun": "./src/rpc/index.ts",
|
|
|
|
|
"default": "./src/rpc/index.ts"
|
|
|
|
|
},
|
|
|
|
|
"./figma-api": {
|
|
|
|
|
"types": "./src/figma-api.ts",
|
|
|
|
|
"bun": "./src/figma-api.ts",
|
|
|
|
|
"default": "./src/figma-api.ts"
|
|
|
|
|
},
|
|
|
|
|
"./canvaskit": {
|
|
|
|
|
"types": "./src/canvaskit.ts",
|
|
|
|
|
"bun": "./src/canvaskit.ts",
|
|
|
|
|
"default": "./src/canvaskit.ts"
|
|
|
|
|
},
|
|
|
|
|
"./layout": {
|
|
|
|
|
"types": "./src/layout.ts",
|
|
|
|
|
"bun": "./src/layout.ts",
|
|
|
|
|
"default": "./src/layout.ts"
|
|
|
|
|
},
|
|
|
|
|
"./color": {
|
|
|
|
|
"types": "./src/color.ts",
|
|
|
|
|
"bun": "./src/color.ts",
|
|
|
|
|
"default": "./src/color.ts"
|
|
|
|
|
},
|
|
|
|
|
"./render-image": {
|
|
|
|
|
"types": "./src/render-image.ts",
|
|
|
|
|
"bun": "./src/render-image.ts",
|
|
|
|
|
"default": "./src/render-image.ts"
|
|
|
|
|
},
|
|
|
|
|
"./profiler": {
|
|
|
|
|
"types": "./src/profiler/index.ts",
|
|
|
|
|
"bun": "./src/profiler/index.ts",
|
|
|
|
|
"default": "./src/profiler/index.ts"
|
2026-02-28 22:24:54 +00:00
|
|
|
}
|
|
|
|
|
},
|
2026-03-02 12:02:43 +00:00
|
|
|
"main": "./src/index.ts",
|
|
|
|
|
"types": "./src/index.ts",
|
2026-02-28 22:24:54 +00:00
|
|
|
"files": ["src", "dist"],
|
|
|
|
|
"scripts": {
|
2026-03-02 11:59:59 +00:00
|
|
|
"build": "bunx tsgo && bunx fix-esm-import-path dist",
|
|
|
|
|
"prepublishOnly": "bun run build"
|
2026-02-28 22:24:54 +00:00
|
|
|
},
|
2026-03-01 13:23:30 +00:00
|
|
|
"repository": {
|
|
|
|
|
"type": "git",
|
|
|
|
|
"url": "git+https://github.com/open-pencil/open-pencil.git",
|
|
|
|
|
"directory": "packages/core"
|
|
|
|
|
},
|
2026-02-28 22:24:54 +00:00
|
|
|
"publishConfig": {
|
2026-03-01 13:23:30 +00:00
|
|
|
"access": "public",
|
2026-03-07 17:16:52 +00:00
|
|
|
"provenance": true,
|
|
|
|
|
"exports": {
|
|
|
|
|
".": {
|
|
|
|
|
"types": "./dist/index.d.ts",
|
Add subpath exports to @open-pencil/core
12 domain-specific subpath exports for targeted imports:
scene-graph, kiwi, tools, renderer, render, rpc, figma-api,
canvaskit, layout, color, render-image, profiler.
- Create kiwi/index.ts barrel (codec, fig-file, fig-import, protocol)
- Route main index.ts kiwi re-exports through the new barrel
- Add exports map + publishConfig mirror for npm consumers
- Mark package as sideEffects: false for tree-shaking
- Document subpath exports in AGENTS.md
2026-03-12 16:58:32 +00:00
|
|
|
"import": "./dist/index.js",
|
2026-03-07 17:16:52 +00:00
|
|
|
"default": "./dist/index.js"
|
Add subpath exports to @open-pencil/core
12 domain-specific subpath exports for targeted imports:
scene-graph, kiwi, tools, renderer, render, rpc, figma-api,
canvaskit, layout, color, render-image, profiler.
- Create kiwi/index.ts barrel (codec, fig-file, fig-import, protocol)
- Route main index.ts kiwi re-exports through the new barrel
- Add exports map + publishConfig mirror for npm consumers
- Mark package as sideEffects: false for tree-shaking
- Document subpath exports in AGENTS.md
2026-03-12 16:58:32 +00:00
|
|
|
},
|
|
|
|
|
"./scene-graph": {
|
|
|
|
|
"types": "./dist/scene-graph.d.ts",
|
|
|
|
|
"import": "./dist/scene-graph.js",
|
|
|
|
|
"default": "./dist/scene-graph.js"
|
|
|
|
|
},
|
|
|
|
|
"./kiwi": {
|
|
|
|
|
"types": "./dist/kiwi/index.d.ts",
|
|
|
|
|
"import": "./dist/kiwi/index.js",
|
|
|
|
|
"default": "./dist/kiwi/index.js"
|
|
|
|
|
},
|
|
|
|
|
"./tools": {
|
|
|
|
|
"types": "./dist/tools/index.d.ts",
|
|
|
|
|
"import": "./dist/tools/index.js",
|
|
|
|
|
"default": "./dist/tools/index.js"
|
|
|
|
|
},
|
|
|
|
|
"./renderer": {
|
|
|
|
|
"types": "./dist/renderer/index.d.ts",
|
|
|
|
|
"import": "./dist/renderer/index.js",
|
|
|
|
|
"default": "./dist/renderer/index.js"
|
|
|
|
|
},
|
|
|
|
|
"./render": {
|
|
|
|
|
"types": "./dist/render/index.d.ts",
|
|
|
|
|
"import": "./dist/render/index.js",
|
|
|
|
|
"default": "./dist/render/index.js"
|
|
|
|
|
},
|
|
|
|
|
"./rpc": {
|
|
|
|
|
"types": "./dist/rpc/index.d.ts",
|
|
|
|
|
"import": "./dist/rpc/index.js",
|
|
|
|
|
"default": "./dist/rpc/index.js"
|
|
|
|
|
},
|
|
|
|
|
"./figma-api": {
|
|
|
|
|
"types": "./dist/figma-api.d.ts",
|
|
|
|
|
"import": "./dist/figma-api.js",
|
|
|
|
|
"default": "./dist/figma-api.js"
|
|
|
|
|
},
|
|
|
|
|
"./canvaskit": {
|
|
|
|
|
"types": "./dist/canvaskit.d.ts",
|
|
|
|
|
"import": "./dist/canvaskit.js",
|
|
|
|
|
"default": "./dist/canvaskit.js"
|
|
|
|
|
},
|
|
|
|
|
"./layout": {
|
|
|
|
|
"types": "./dist/layout.d.ts",
|
|
|
|
|
"import": "./dist/layout.js",
|
|
|
|
|
"default": "./dist/layout.js"
|
|
|
|
|
},
|
|
|
|
|
"./color": {
|
|
|
|
|
"types": "./dist/color.d.ts",
|
|
|
|
|
"import": "./dist/color.js",
|
|
|
|
|
"default": "./dist/color.js"
|
|
|
|
|
},
|
|
|
|
|
"./render-image": {
|
|
|
|
|
"types": "./dist/render-image.d.ts",
|
|
|
|
|
"import": "./dist/render-image.js",
|
|
|
|
|
"default": "./dist/render-image.js"
|
|
|
|
|
},
|
|
|
|
|
"./profiler": {
|
|
|
|
|
"types": "./dist/profiler/index.d.ts",
|
|
|
|
|
"import": "./dist/profiler/index.js",
|
|
|
|
|
"default": "./dist/profiler/index.js"
|
2026-03-07 17:16:52 +00:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"main": "./dist/index.js",
|
|
|
|
|
"types": "./dist/index.d.ts"
|
2026-02-28 22:03:07 +00:00
|
|
|
},
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"canvaskit-wasm": "^0.40.0",
|
|
|
|
|
"culori": "^4.0.2",
|
Port analyze/diff tools from figma-use, split tools into domain files
Analyze: colors, typography, spacing, clusters
Diff: diff_create (tree diff), diff_show (preview changes)
Utility: get_components, get_current_page, arrange, node_to_component
Split schema.ts (2600 lines) into read, create, modify, structure,
variables, vector, analyze, registry — each under 600 lines.
Clean up inline types (use Color, Vector, SceneNode from existing defs).
Update AGENTS.md and CONTRIBUTING.md with code quality guidelines.
2026-03-05 16:00:40 +00:00
|
|
|
"diff": "^8.0.3",
|
2026-02-28 22:03:07 +00:00
|
|
|
"fflate": "^0.8.2",
|
XPath query for node selection (#82)
* Add XPath query for node selection
- packages/core/src/xpath.ts: XPath engine using fontoxpath with custom
DOM facade over SceneGraph nodes
- RPC command: 'query' with selector, page, limit params
- CLI command: 'open-pencil query <file> <xpath>' with --json, --page, --limit
- AI/MCP tool: 'query_nodes' with XPath examples in description
- Queryable attributes: name, width, height, x, y, visible, opacity,
cornerRadius, fontSize, fontFamily, fontWeight, layoutMode, itemSpacing,
padding*, strokeWeight, rotation, locked, blendMode, text, lineHeight,
letterSpacing
* Add tests and docs for XPath query
- 9 unit tests for queryByXPath/matchByXPath core functions
- 6 tool tests for query_nodes AI/MCP tool
- README: query examples in CLI section
- VitePress docs: patterns, queryable attributes, example output
- CHANGELOG: feature entry
* Lazy-load fontoxpath to avoid bundling in desktop app
- Dynamic import() instead of static import for fontoxpath
- queryByXPath/matchByXPath now async (they were sync before)
- RpcCommand.execute allows R | Promise<R> for async commands
- Fix 2 lint errors (unnecessary optional chain, always-truthy)
- Update all tests and CLI to await results
2026-03-09 12:32:27 +00:00
|
|
|
"fontoxpath": "^3.34.0",
|
2026-02-28 22:03:07 +00:00
|
|
|
"fzstd": "^0.1.1",
|
2026-03-09 13:02:54 +00:00
|
|
|
"nanoevents": "^9.1.0",
|
Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72)
* Add multi-provider AI support (Anthropic, OpenAI, Google AI, OpenAI-compatible)
- Add AI_PROVIDERS registry with per-provider model lists, key placeholders, and URLs
- Refactor use-chat composable: provider factory creates the right AI SDK model
- Per-provider API key storage in localStorage with automatic legacy migration
- New ProviderSetup.vue replaces APIKeySetup.vue with provider selector
- New ProviderSettings.vue popover accessible from gear icon in chat input
- OpenAI-compatible provider with custom base URL and model ID fields
- Install @ai-sdk/anthropic and @ai-sdk/google dependencies
- Update E2E tests for new provider setup flow
* Fix reactivity, key masking, missing model ID field, and chat reset on model change
- Use ref instead of computed for apiKey — computed getter over localStorage
wasn't triggering reactivity when the key was set
- Load stored key for new provider in providerId watcher
- Reset chat on modelId and customModelId change, not just provider switch
- Add custom model ID field to ProviderSetup.vue for OpenAI-compatible
- Simplify ProviderSettings key field — empty input with contextual placeholder
instead of fragile dot-masking that broke on partial edits
- Use data-test-id locator for model selector in E2E test
* Use useLocalStorage from vueuse instead of manual ref + watch + localStorage
Replaces 5 hand-rolled ref/watch/localStorage sync pairs with useLocalStorage.
API key uses a computed storage key that rebinds when the provider changes.
* Use useLocalStorage for collab name persistence
* Review fixes: changelog placement, remove dead class, cursor-pointer
* Extract ProviderSelect component and uiInput helper
- ProviderSelect.vue: shared select dropdown for AI providers
- ui/input.ts: shared input styling (sm/md sizes)
- Remove 7 duplicated input class strings across chat components
* Use uppercase acronyms: keyURL, customBaseURL, supportsCustomBaseURL
* Match select dropdown width to trigger via --reka-select-trigger-width
Move min-w-[var(--reka-select-trigger-width)] into selectContent base
style so all selects get it by default. Remove per-component overrides.
* Uppercase acronyms in variable names, improve setup form layout
- providerID, modelID, customModelID, setAPIKey, AIProviderID
- Keep apiKey lowercase at start (standard JS convention)
- Compact setup form: single column, full-width Connect button
- Fix grammar: 'a OpenRouter' → 'an OpenRouter'
- Shorter promo text
* Allow text selection in chat panel
* Fix chat E2E test: Save → Connect button text
* Disable mermaid in chat markdown renderer
Alias mermaid and beautiful-mermaid to empty shims in Vite config,
preventing vue-stream-markdown from attempting to load them.
* Interleave text and tool calls in chat messages
Render message parts in order instead of grouping all tool calls
first then all text. Use SDK's isToolUIPart/isTextUIPart/getToolName
instead of custom type guards. Fix error state: output-error, not error.
* Implement figma.viewport.scrollAndZoomIntoView()
Figma Plugin API method that centers the viewport on given nodes.
Reuses the same bounding box logic as the viewport_zoom_to_fit tool.
* Catch tool execution errors and return them to the AI
Instead of crashing with an unhandled exception, tool errors are
caught and returned as { error: message } so the AI can retry or
explain the failure. UI detects error outputs and shows them in red
with the error message when expanded.
* Replace esbuild with sucrase for JSX transform
Sucrase is a pure JS transform (201 KB / 46 KB gzip) that works in
both Node/Bun and the browser. Replaces esbuild (13 MB WASM) which
only worked in Node/Bun.
- buildComponent() and renderJSX() are now synchronous
- render tool works in browser AI chat (no more 'esbuild required')
- Handles full JS expressions (map, ternaries, Array.from, etc.)
* Update system prompt to prioritize render tool with JSX
Document available tags, props, layout, text, and sizing options.
Instruct the AI to use full JS expressions in JSX for complex layouts.
* Fix zoom shortcuts to match Figma
Cmd+0: Zoom to 100% (was incorrectly mapped to Zoom to fit)
Cmd+1: Zoom to fit
Cmd+2: Zoom to selection
Shift+1 / Shift+2: same as Cmd+1 / Cmd+2
Add zoomTo100() and zoomToSelection() to editor store.
Refactor zoomToFit() to use shared zoomToBounds() helper.
* Refactor keyboard shortcuts to useMagicKeys
Replace manual keydown handler with VueUse useMagicKeys + whenever
for declarative shortcut registration.
- mod() helper for cross-platform Meta/Control shortcuts
- plain() helper for modifier-free keys
- Proper modifier exclusion (⌘G vs ⌘⇧G no longer conflict)
- Add E2E tests: duplicate, zoom (⌘0/⌘1/⌘2/⇧1/⇧2), auto-layout (⇧A)
- All 26 keyboard shortcut tests pass
* Fix scrollAndZoomIntoView to actually zoom
Match Figma Plugin API behavior (equivalent to Shift-1): compute
zoom level that fits all nodes with padding, capped at 100%.
Previously only set center without adjusting zoom.
2026-03-08 19:05:18 +00:00
|
|
|
"sucrase": "^3.35.1",
|
CSS Grid layout mode (#73)
* Switch to @open-pencil/yoga-layout with CSS Grid support
Use our fork (open-pencil/yoga, grid branch) which cherry-picks the
upstream CSS Grid PRs (#1893–#1898) onto current main. The JS bindings
are ported from the old embind approach to the new wasm_bridge.c API.
npm:@open-pencil/yoga-layout alias keeps all imports as 'yoga-layout'.
Also handle FinalizationRegistry change (upstream #1908) — node.free()
no longer exists, nodes are garbage collected automatically.
* Add CSS Grid layout mode
Scene graph:
- LayoutMode gains 'GRID' option alongside HORIZONTAL/VERTICAL
- GridTrack type (sizing: FIXED/FR/AUTO, value) for track definitions
- GridPosition type (column, row, columnSpan, rowSpan) for children
- New node props: gridTemplateColumns/Rows, gridColumnGap, gridRowGap,
gridPosition
Layout engine:
- buildGridTree() sets Display.Grid and maps GridTrack[] to Yoga's
setGridTemplateColumns/Rows API with FR/Points/Auto track types
- Grid children use gridPosition for column/row placement with span
- Flex layout path unchanged
Store:
- setLayoutMode('GRID') auto-creates NxM track grid based on child
count (sqrt heuristic), defaults to 1fr tracks
UI (LayoutSection.vue):
- Grid button (grid-2x2 icon) added to flow direction row
- Columns/Rows track editors: ScrubInput for value + AppSelect for
sizing mode (Fill fr / Fixed px / Auto), add/remove buttons
- Separate column gap and row gap ScrubInputs
- Wrap button hidden when grid is active
- Flex alignment grid hidden when grid is active
Kiwi serialization skips GRID mode for now (no .fig codec support).
* Add grid layout integration tests
12 new test scenarios covering:
- Basic 2x2 grid, fixed columns, mixed fr/fixed, unequal fr weights
- Column gap, row gap, both gaps combined
- Padding offsets
- Explicit gridPosition placement, column span, row span
- Absolute children skipped
- Hidden children collapsed
- Nested grid inside flex parent (computeAllLayouts)
Also fix: grid frames as children of flex parents now correctly use
Display.Grid via configureChildAsGrid() instead of falling through
to configureChildAsAutoLayout() which set FlexDirection.
* Deduplicate grid yoga configuration
Extract configureAsGrid() and createGridChildNode() shared by
buildGridTree (root-level grid) and configureChildAsGrid (grid
nested inside flex parent).
* Add grid layout to changelog
* Add grid support to JSX and Tailwind CSS export
OpenPencil format: grid → columns/rows/columnGap/rowGap props,
child colStart/rowStart/colSpan/rowSpan.
Tailwind format: grid grid-cols-N grid-rows-N gap-x-* gap-y-*,
mixed tracks use arbitrary values (grid-cols-[200px_1fr_auto]),
child col-start-*/row-start-*/col-span-*/row-span-*.
Both formats: padding emitted for grid frames (shared with flex),
flex-only props (justify/items/wrap/gap) scoped to isFlex.
* Remove duplicate gridTrackToTw, use shared formatTrack
Also drop unused GridTrackSizing import from LayoutSection.vue.
* Fix grid icon: use layout-grid instead of grid-2x2
unplugin-icons can't resolve icon names with digits after hyphens
(grid-2x2 → grid2x2 which doesn't exist).
* Polish layout UI and fix auto-layout behavior
- Replace text labels with compact icons: ↔/↕ for gap, ☐ for uniform padding, T/R/B/L for per-side padding
- Direction-aware gap icon: ↕ for vertical, ↔ for horizontal
- Pin +/− padding toggle button right of gap input
- Fix alignment grid axes for vertical layout (transpose primary/counter)
- Fix grid switch: set FIXED sizing, compute frame size from children
- Remove hardcoded white fill from Shift+A wrap
- Auto-detect horizontal vs vertical from selection bounds
* Add flex-to-grid switch integration test
Verify HUG frame expands and children are placed in 2x2 grid
when switching from vertical flex to grid layout.
* Update changelog for grid layout
2026-03-08 15:23:53 +00:00
|
|
|
"yoga-layout": "npm:@open-pencil/yoga-layout@3.3.0-grid.2"
|
2026-02-28 22:03:07 +00:00
|
|
|
},
|
|
|
|
|
"devDependencies": {
|
2026-02-28 22:24:54 +00:00
|
|
|
"@types/culori": "^4.0.1",
|
Port analyze/diff tools from figma-use, split tools into domain files
Analyze: colors, typography, spacing, clusters
Diff: diff_create (tree diff), diff_show (preview changes)
Utility: get_components, get_current_page, arrange, node_to_component
Split schema.ts (2600 lines) into read, create, modify, structure,
variables, vector, analyze, registry — each under 600 lines.
Clean up inline types (use Color, Vector, SceneNode from existing defs).
Update AGENTS.md and CONTRIBUTING.md with code quality guidelines.
2026-03-05 16:00:40 +00:00
|
|
|
"@types/diff": "^8.0.0",
|
2026-02-28 22:24:54 +00:00
|
|
|
"typescript": "~5.8.3"
|
2026-02-28 22:03:07 +00:00
|
|
|
}
|
|
|
|
|
}
|