- Skip CJK and Arabic text until fallback font families are ready
- Re-render after async fallback font loading completes
- Add Playwright coverage for delayed CJK fallback loading
- Register .fig and .pen files with desktop bundles
- Open associated files on startup and via single-instance handoff
- Reuse the existing Tauri file open flow without broad filesystem scopes
Replace hand-rolled regex heuristic with proper acorn JS parsing.
The AST correctly identifies ExpressionStatement as the last node
regardless of trailing semicolons, comments, or complex syntax.
Fixes edge cases like '42;' which the regex wrongly treated as a
statement.
Co-authored-by: stiff <v.stiff@gmail.com>
Co-authored-by: Jais Pedersen <jais@pedersens.net>
Stdio handshake:
Server now sends { type: register, token } to WebSocket clients on
connection, so the stdio bridge receives the token immediately instead
of deadlocking while both sides wait to receive.
Eval return values (REPL-style):
Add wrapEvalCode() that detects when the last non-empty line is a value
expression and promotes it to a return statement. Bare expressions like
JSON.stringify(result) now return the value instead of undefined.
Single implementation in packages/core/src/tools/analyze/eval-wrap.ts
used by both the tool and the automation bridge.
Co-authored-by: stiff <v.stiff@gmail.com>
Co-authored-by: Jais Pedersen <jais@pedersens.net>
Show variant property dropdowns in the design panel when an instance
of a component set is selected. Switching a dropdown value finds the
matching variant component and updates the instance's componentId.
- VariantSection.vue with property dropdowns using AppSelect
- Bridge variant actions to the editor return object
- i18n for en, ru, zh-CN
Ref #239
Data model:
- Add ComponentPropertyDefinition and ComponentPropertyType to SceneNode
- Add componentPropertyValues for per-variant property values
- Populate from .fig import via componentPropDefs kiwi field
- Parse variant name=value syntax from component names on import
Core actions (packages/core/src/editor/components/variants.ts):
- Add/remove/rename variant property definitions with undo
- Collect variant options from component set children
- Find variant by property values
- Switch instance variant by changing a property value
- Parse and build variant name strings
Combine as variants:
- Parse slash-naming (Button/Primary/Large) into variant properties
- Auto-detect property columns from consistent slash counts
- Populate componentPropertyValues on each variant component
Ref #239
Wire VariableScrubInput for minWidth, maxWidth, minHeight, maxHeight
in the size controls panel. Uses the after-variable slot to preserve
the existing select dropdown suffix alongside the variable binding UI.
Paste now targets the selected frame/group/component/section instead of
always placing nodes on the page root, matching Figma's behavior:
- Single container selected: paste as child
- Non-container child selected: paste as sibling in its parent
- Entered container (double-click): paste inside it
- Nothing or multiple nodes selected: paste on the page
Also applies to image paste via placeImageFiles.
Fixes#79
- Add shared DOM event helpers to Vue SDK (inputValue, inputNumberValue,
blurTarget, selectTarget) in packages/vue/src/shared/dom-events.ts
- Export from @open-pencil/vue for app consumption
- Replace all template `as HTMLInputElement` casts with helper calls:
ColorPickerPanel fields, PickerSlider, GradientEditor, ColorInput,
CollabPanel, AppMenu, ScrubInputRoot
- Fix useInlineRename to use instanceof guards instead of casts
- Fix document name rename to accept Event directly
- Add lineHeight, letterSpacing, fontWeight, paragraphSpacing,
paragraphIndent to NumberBindingPath
- Wire lineHeight and letterSpacing inputs in TypographySection
to VariableScrubInput with variable binding support
- Reuse useInlineRename composable for collection and mode renaming
- Accept Event in useInlineRename.commit to eliminate template casts
- Remove HTMLInputElement casts from PagesPanel and LayerTree blur handlers
- Remove hand-rolled rename state from dialog composable
Core:
- Add addMode, removeMode, renameMode, setDefaultMode to SceneGraph
- Add editor actions with full undo: addMode, removeMode, renameMode,
setDefaultMode, duplicateMode, setActiveMode
Vue SDK:
- Wire all mode and collection actions through SDK helpers
- Add mode rename state to dialog composable
- Remove HTMLInputElement casts from commit handlers
UI:
- Mode management lives in the table column headers (Figma-style):
double-click to rename, right-click for context menu (rename,
duplicate, set as default, delete), + button after last mode column
- Collection ⋯ dropdown menu with rename and delete
- Use shared menu UI styles throughout
- i18n for en, ru, zh-CN
Replace fragile ../../assets relative path with createRequire-based
package.json resolution. The old path broke if the file moved or after
bundler output restructuring. The new approach resolves the package root
via require.resolve('@open-pencil/core/package.json') which works from
source, dist, and node_modules.
- Add optional nodeEditState, cursorCanvas, and pen resume fields to core EditorState
- Add parent field to LintNode instead of repeated intersection casts
- Use typed LintPathNode for node path traversal
- Replace autosave useDebounceFn cancel hack with watchDebounced
- Simplify AppEditorState to extend EditorState without Omit+intersection
- Remove PenState intersection type alias
- Use in-operator check for optional setViewportSize
- Update CHANGELOG.md with event bus, test restructuring, and lint hardening
- Document EditorEvents table and conventions in AGENTS.md
- Wire collab graph sync to editor event bus instead of SceneGraph.onNodeEvents
- Wire collab awareness zoom to viewport:changed instead of Vue watcher
- Wire collab selection broadcasting to selection:changed instead of Vue watcher
Wire nanoevents-based event bus to the editor core:
- EditorEvents type with render, graph, selection, tool, page, viewport events
- All node mutations (create/update/delete/reparent/reorder) forwarded from SceneGraph
- Selection changes routed through setSelectedIds() with diff detection
- Tool changes routed through setActiveTool() with change detection
- Page switches emit page:changed
- Viewport pan/zoom/fit/zoomTo emit viewport:changed
- Graph replacement emits graph:replaced
- Typed onEditorEvent() subscription on the Editor return object
- useEditorEvent() Vue composable with automatic scope cleanup
- Replace all direct state.selectedIds/state.activeTool mutations in core, app, and SDK
- Move newly split tests under explicit domain subfolders
- Update nested helper imports after the moves
- Add a lint guard against repeating existing sibling domains as filename prefixes
- Extract color picker fill comparison helpers
- Guard derived text assertions without optional chains
- Split resize math into smaller helper functions
- Remove the remaining complexity test exceptions
- Move auto-layout tests into focused files by layout behavior
- Preserve sizing, wrapping, text measurement, and positioning coverage
- Remove the last max-lines test exception
- Move scene graph behavior tests into focused files
- Share basic scene graph helper builders from basic/helpers.ts
- Remove scene graph basic from max-lines exceptions
- Move FigmaAPI tests into focused files by feature area
- Share API construction from figma/api/helpers.ts
- Remove FigmaAPI coverage from max-lines exceptions
- Move Kiwi serialization fix tests into one file per regression area
- Share codec setup and Kiwi helpers from serialize-fixes/helpers.ts
- Remove serialize fixes from max-lines exceptions
- Replace remaining empty mock callbacks with explicit undefined returns
- Remove the last no-empty-function test exceptions
- Validate check and affected renderer/text tests
- Replace simple no-op test callbacks with explicit undefined returns
- Remove no-empty-function exceptions for autosave, stroke picker, font settings, pen, tauri, AI adapter, and undo helper tests
- Validate check plus affected engine and E2E tests
- Expose window.openPencil.getStore() instead of a direct store property
- Update E2E helpers and specs to use the bridge getter
- Add lint coverage preventing direct window.openPencil.store access
- Remove remaining test non-null assertions and delete the broad test override
- Add explicit guards for canvas, design panel, auto-layout, SVG, Kiwi, hit-test, scene graph, text editor, and clipboard tests
- Validate check plus affected engine and E2E coverage
- Replace non-null assertions in canvas text rendering and mutation undo tests
- Use expectDefined for CanvasKit handles and getNodeOrThrow for graph nodes
- Validate check and affected engine tests
- Replace non-null assertions in modify tool tests
- Use expectDefined and getNodeOrThrow for graph, proxy, and style-run lookups
- Validate check and affected modify tool tests
- Move test-only write-count, mock-handle, and saved-open globals under window.openPencil.test
- Keep external Tauri and Vite injected globals separate from app-owned browser bridge hooks
- Validate check plus autosave and chat E2E coverage
- Rename window-api to browser-bridge to describe the app-to-browser test hook boundary
- Rename the chat transport hook to exposeChatTransportOverride and window.openPencil.setChatTransport
- Route browser globals through src/app/window-api.ts instead of private __OPEN_PENCIL fields
- Move E2E tests to the public window.openPencil test API
- Add an oxlint rule banning direct window.__OPEN_PENCIL* access
- Replace non-null assertions in keyboard, text editing, and legacy fig import coverage
- Add explicit store guards and expectDefined for optional test data
- Validate check, legacy fig import, and keyboard E2E coverage