Commit graph

396 commits

Author SHA1 Message Date
Danila Poyarkov 9616094766 refactor(text): group derived text helpers 2026-05-16 11:56:35 +03:00
Danila Poyarkov 358ac9ebf7 fix(clipboard): orient text glyph outlines 2026-05-16 11:52:22 +03:00
Danila Poyarkov 91f516ecaa fix(clipboard): include text glyph blobs 2026-05-16 11:46:14 +03:00
Danila Poyarkov 992be9b713 fix(clipboard): include glyph advances 2026-05-16 11:05:15 +03:00
Danila Poyarkov e47455d91b fix(clipboard): export Figma font style names 2026-05-16 10:52:51 +03:00
Danila Poyarkov 22741a3dc4 perf(canvas): gate draw call instrumentation 2026-05-15 01:41:34 +03:00
Danila Poyarkov a091ac7284 perf(canvas): cache paragraph font families 2026-05-14 23:32:26 +03:00
Danila Poyarkov c3fd32a516 feat(editor): add auto-layout hover controls 2026-05-14 18:15:53 +03:00
Danila Poyarkov c00356bc29 fix(demo): make auto-layout text responsive 2026-05-13 06:23:08 +03:00
Danila Poyarkov 9ab6bfa3bf fix(app): restore left panel scrolling 2026-05-13 05:47:32 +03:00
Danila Poyarkov 8cf51ffefd perf(editor): use repaint previews while resizing 2026-05-13 05:38:14 +03:00
Danila Poyarkov 1242fa7086 fix(editor): keep drag previews in sync 2026-05-13 05:34:04 +03:00
Danila Poyarkov 4e64644385 perf(editor): use repaint previews while dragging 2026-05-13 05:23:06 +03:00
Danila Poyarkov 419c77d8de fix(canvas): wait for exact font styles 2026-05-13 04:47:32 +03:00
Danila Poyarkov 5fd0e60a99 fix(editor): hit test entered containers in world space 2026-05-13 04:11:19 +03:00
Danila Poyarkov 2f39ecaa4d fix(canvas): cull nested nodes in world space 2026-05-13 03:56:04 +03:00
Danila Poyarkov 66f562b52f fix(fonts): bundle Inter bold 2026-05-13 03:50:35 +03:00
Danila Poyarkov f25e5cfe0a fix(scene-graph): avoid id collisions after import 2026-05-13 03:36:28 +03:00
Danila Poyarkov 866cabd623 feat(kiwi): preserve component library metadata
- Import Figma component keys, library keys, publish/version IDs, descriptions, links, and variant specs
- Export verified component metadata back into NodeChange fields
- Use variantPropSpecs to derive component property values after component set imports
- Cover large fixture metadata import as heavy tests
2026-05-12 21:48:42 +03:00
Danila Poyarkov 4bf161b983 fix(components): sync instances when switching variants
- Swap instance contents when changing variants instead of only updating componentId
- Recreate instance redo state without stale child IDs
- Insert assets into entered containers using local coordinates
- Fall back to Windows paths when URL parsing fails
2026-05-12 18:43:11 +03:00
Danila Poyarkov 24d3ea748f fix(components): respect default variants in assets
- Insert component set assets using explicit default variant values
- Report duplicate variant value combinations in the assets panel
- Cover default insertion and conflict warnings in Playwright
2026-05-12 18:16:51 +03:00
Danila Poyarkov 1ceabf7938 feat(components): add local assets panel
- Add a left-sidebar Assets tab for local components and component sets
- Insert component set assets as instances of the default variant
- Showcase local component set insertion in the demo
- Cover search, empty state, standalone insert, and variant switching with Playwright
2026-05-12 17:30:35 +03:00
Danila Poyarkov 23039e0b99 fix(canvas): wait for CJK fallback fonts
- 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
2026-05-12 16:54:55 +03:00
Tem-man 3fb2cfc7b1
update id and name of deepseek as deepseek-v4-flash and deepseek-v4-pro (#253)
Update DeepSeek model IDs to the new V4 Flash/Pro names, set V4 Flash as the default, and bump @ai-sdk/deepseek.
2026-05-12 15:23:53 +03:00
Danila Poyarkov 5787e45117 refactor(tools): move eval modules into domain folder 2026-05-11 12:19:46 +03:00
Danila Poyarkov a1339e845b refactor(tools): use acorn AST parser for eval wrapping
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>
2026-05-11 12:15:38 +03:00
Danila Poyarkov 9e013e60e7 fix(mcp): fix stdio handshake and eval return values
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>
2026-05-11 12:05:43 +03:00
Danila Poyarkov c79c9106fb feat(components): add variant inspector UI
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
2026-05-07 19:48:59 +03:00
Danila Poyarkov fee44ebdb7 feat(components): add variant property data model and core actions
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
2026-05-07 16:12:46 +03:00
Danila Poyarkov 72614fac4c fix(editor): paste into selected container
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
2026-05-07 13:56:30 +03:00
Danila Poyarkov 107e9f3d51 feat(variables): add mode management and collection deletion
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
2026-05-06 21:36:04 +03:00
Danila Poyarkov 2147aeb327 fix(core): resolve bundled fonts from package root
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.
2026-05-06 17:07:31 +03:00
Danila Poyarkov 738d199e8a refactor: remove ugly type intersection casts
- 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
2026-05-06 16:08:36 +03:00
Danila Poyarkov 1835903e56 feat(editor): add event bus for editor lifecycle events
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
2026-05-06 15:25:27 +03:00
Danila Poyarkov dfa9505805 chore(lint): enforce domain prefix folders project-wide
- Generalize sibling domain prefix linting beyond tests
- Move create icon tools under the existing create domain folder
- Keep create.ts exporting the icon tools through the domain folder
2026-05-06 14:33:17 +03:00
Danila Poyarkov c3f5189f8f style: tighten import grouping
- Configure oxfmt custom import groups for workspace, app, package, and test aliases
- Keep type imports grouped with their matching source category instead of one global tail group
- Expand the format script to cover formatter config, Vite files, and scripts
2026-05-06 02:22:08 +03:00
Danila Poyarkov ddb1db8175 style: format imports 2026-05-06 02:16:37 +03:00
Danila Poyarkov 51eae14e4b chore(tests): remove targeted non-null assertions
- Add shared test helpers for required nodes and child ids
- Clean non-null assertions from noisy render, nudge, text undo, and fig roundtrip tests
- Enforce the non-null assertion ban for the cleaned test files
- Remove stale eslint disables around empty functions and broad mock transport typing
2026-05-06 01:14:50 +03:00
Danila Poyarkov 4d3e16c0a2 chore(lint): ban broad function types
- Add structural lint rules for broad Function usage and globalThis deletion outside tests
- Type design JSX component factories with an explicit callable signature
- Replace remaining broad Kiwi serialization test records with unknown values
2026-05-06 00:48:57 +03:00
Danila Poyarkov fae190a673 chore(lint): remove unsafe test casts
- Enforce explicit-any and TypeScript suppression bans in tests
- Remove remaining broad any casts from engine and e2e coverage
- Split markdown ambient declarations from typed browser globals
2026-05-06 00:42:45 +03:00
Danila Poyarkov aa07e833cb chore(lint): ban broad double casts project-wide
- Extend the as-unknown-as structural rule beyond app and Vue code
- Remove existing broad double casts from core and tests
- Replace the vector base64 helper and XPath facade casts with precise code paths
2026-05-06 00:29:23 +03:00
Danila Poyarkov 7f3eb532d8 fix(core): render gradient text fills
- Clip gradient paints through the shaped paragraph mask instead of reading only the paint color
- Cover the paragraph mask path and a headless red-to-blue text render

Fixes #246
2026-05-06 00:22:17 +03:00
Danila Poyarkov 768b527155 feat(core): add font fallback manifest 2026-05-05 21:35:12 +03:00
Danila Poyarkov 25ea361fd7
feat(app): cache downloaded fonts
- Add a downloaded font cache hook to FontManager
- Store Tauri font downloads under AppLocalData with manifest validation
- Prefer cached downloads before system font lookup on desktop
- Skip opt-in heavy .fig parsing tests in PR CI
2026-05-05 21:14:38 +03:00
Danila Poyarkov 9c11c5b075
refactor(core): centralize font loading
- Replace loose font service functions with a FontManager instance
- Route renderer provider lifecycle and font caches through the manager
- Request browser local font access from the font picker interaction
2026-05-05 18:59:54 +03:00
Tem-man 1e2df31636
fix(core): improve CJK font fallback loading
- Allow CJK fallback loading to use local variable system fonts
- Add Windows CJK fallback families
- Harden CanvasKit WASM asset resolution and serving
2026-05-05 18:39:39 +03:00
Joseph Cumines c69e3884cf
fix(core): critical codec and canvas fixes
- Deduplicate plugin data and relaunch data during .fig parsing/export
- Unify shared plugin data into the pluginData storage path
- Refactor shadow rendering and harden CanvasKit font provider teardown
- Expand engine and renderer coverage
2026-05-05 14:58:02 +03:00
Danila Poyarkov 93160b1bfb feat(ai): add DeepSeek as first-class provider
Use @ai-sdk/deepseek instead of openai-compatible, which properly
handles reasoning_content in conversation history. DeepSeek V4
requires reasoning_content on every assistant turn — the dedicated
provider backfills it automatically.

Adds DeepSeek V3 and R1 models to the provider list.
2026-05-04 11:48:45 +03:00
Danila Poyarkov ac15fb143b feat(io): add PDF export
Vector PDF output via jsPDF + svg2pdf.js (lazy-loaded, 124KB gzipped).
Reuses existing SVG export pipeline — text stays selectable, paths
stay sharp at any zoom.

- packages/core/src/io/formats/pdf/ — SVG→PDF conversion module
- export_pdf tool for MCP/AI
- PDF format in IO registry and app export panel
- CLI: bun open-pencil export file.fig --format pdf
2026-05-04 00:18:54 +03:00
Danila Poyarkov a2050a9860 feat(tools): add import_svg tool
Parse raw SVG markup and create vector nodes on the canvas.
Supports path, circle, ellipse, rect, line, polygon, polyline
with fill, stroke, stroke-width, viewBox sizing, and currentColor.
2026-05-03 23:59:21 +03:00