Commit graph

476 commits

Author SHA1 Message Date
Danila Poyarkov ea4676d991 Finish i18n pass for menus and tool labels
Localize remaining menu state labels like Hide/Show and Lock/Unlock,
wire toolbar tool/action labels through the i18n stores, and localize
the app menu UI toggle tooltip.

Replace template-string JSON imports with an explicit locale loader map
so published package builds have stable locale asset references.
2026-03-25 17:39:47 +03:00
Danila Poyarkov eb2891233a Wire i18n into commands, menus, and add locale files
Commands and menu models now read labels from nanostores i18n
messages instead of hardcoded English strings. App menu rebuilt
to use i18n directly instead of matching on localized labels.

Added locale files: de, es, fr, it, pl (ru was added earlier).
All 6 translations plus English base cover menus, commands, tools,
panels, pages, and dialogs.
2026-03-25 17:05:11 +03:00
Danila Poyarkov 2b3219e106 Add i18n to Vue SDK with nanostores
nanostores + @nanostores/i18n + @nanostores/vue (~6 KB total).

English base translations defined inline in messages.ts, other
locales loaded on demand from JSON. Grouped by domain: menu,
commands, tools, panels, pages, dialogs.

useI18n() composable returns reactive translation objects plus
locale controls. Russian locale included as first translation.

Browser language auto-detected, user preference persisted in
localStorage.
2026-03-25 16:17:03 +03:00
TKman c52f88e34c feat: add spacebar hold to temporarily activate Hand (pan) tool
Hold Spacebar to temporarily switch to the Hand tool for panning,
release to return to the previously active tool. This matches the
standard Figma UX pattern.

- Uses separate keydown/keyup event listeners for reliable handling
- Guards against text editing mode and key repeat
- References state.activeTool for correct tool state

Fixes #147
2026-03-24 23:47:56 +03:00
Danila Poyarkov 2956507783 Fix switch_page not persisting across MCP tool calls
Each RPC tool call created a fresh FigmaAPI instance, so switch_page
set currentPageId on a throwaway object. Sync the page change back
to the editor store after tool execution.

Fixes #112
2026-03-24 23:47:55 +03:00
Danila Poyarkov bceddd759f Guard keyboard shortcuts during canvas text editing
Backspace/Delete and tool shortcuts (V, R, F, T, etc.) fired during
canvas text editing because the guard only checked for DOM input/textarea
focus, not the canvas TextEditor state. Add editingTextId check to both
the onEventFired handler and the plain() shortcut helper.
2026-03-24 23:47:55 +03:00
Danila Poyarkov 80a2855bb3 Fix review issues in undo and Vue SDK 2026-03-24 18:15:40 +03:00
Danila Poyarkov 1da5483fec Format remaining SDK refactor files 2026-03-24 17:02:01 +03:00
Danila Poyarkov 07af72ab24 Fix imported fig rendering and file open regressions 2026-03-24 16:52:45 +03:00
Danila Poyarkov 0a770248a2 Reorganize Vue SDK modules by domain 2026-03-24 09:50:31 +03:00
Danila Poyarkov 74bb119579 Extract Vue SDK editor controls and pickers 2026-03-24 09:50:28 +03:00
Danila Poyarkov 329dd301bd Align app UI primitives with shared ui overrides 2026-03-24 09:50:20 +03:00
Danila Poyarkov 0c30d044b2 Extract Vue SDK commands and menu model 2026-03-24 09:50:17 +03:00
Danila Poyarkov 29b63842fa Integrate SDK composables into all app components
FillPicker: useFillPicker() replaces inline category/switch logic
PagesPanel: usePageList() replaces inline pages computed
VariablesDialog: useVariables() replaces 11 inline CRUD functions
CanvasMenu: inline editor.copySelectionAs* instead of wrapper fns
Toolbar: ToolbarRoot + ToolbarItem wrapping tool buttons

28/42 app components now use SDK. Remaining 14 are legitimately
app-specific (chat, collab, mobile layout) or generic UI widgets
(select, toast, tip — no editor logic needed).
2026-03-17 20:39:45 +03:00
Danila Poyarkov fb2bfb5827 SDK rewrite: delete 15 useless wrappers, replace with composables
Deleted 15 fake headless component folders (slot-prop wrappers with
no provide/inject, no DOM, no ARIA). Replaced with 9 composables:
- usePosition, useLayout, useAppearance, useTypography
- useExport, useVariables, useFillPicker, useGradientStops, usePageList

Kept 6 real headless component families with provide/inject + DOM:
- Canvas (WebGL surface lifecycle)
- ScrubInput (pointer capture + input + display)
- Toolbar (wraps Reka UI Toolbar)
- LayerTree (wraps Reka UI Tree + Atlaskit DnD)
- PropertyList (array CRUD via inject)
- GradientEditor (pointer capture for stop drag)

App components updated: use composables directly instead of wrapping
in Root components. -1047 net lines, cleaner separation.
2026-03-17 20:22:41 +03:00
Danila Poyarkov c36456d820 Fix loader not removed — add onReady callback to dismiss #loader
The loader div from index.html was never dismissed after the SDK
migration because use-canvas.ts moved to the SDK (which correctly
doesn't touch app-specific DOM). Added onReady callback in
EditorCanvas.vue to fade out and remove the loader.
2026-03-17 19:25:06 +03:00
Danila Poyarkov 43694aaeb3 Fix runtime errors: TooltipProvider scope, provideEditor in EditorView
Browser validation found 2 runtime issues:

1. TooltipProvider only wrapped EditorView, but <Tip> components in
   AppToast rendered outside it → moved TooltipProvider to App.vue
   to wrap everything including toasts.

2. SDK components call useEditor() but the app never provided the
   editor via the SDK's injection system → added provideEditor(store)
   in EditorView.vue so SDK primitives can access the editor.

Validated: zero console errors, canvas renders, demo shapes visible.
2026-03-17 17:33:04 +03:00
Danila Poyarkov 92c49f80f4 Self-review fixes: CanvasContext types, toast out of SDK, last graph access
- CanvasContext hit test return types: unknown → SceneNode | null
- Move toast.ts from SDK to app (src/utils/toast.ts) — singleton
  with unguarded window access doesn't belong in headless SDK
- ExportSection: store.graph.getNode → store.getNode
- ImageFillPicker: store.graph.images.get → store.getImage (new method)
- Add getImage(hash) to core editor create.ts

Zero store.graph access from components. Zero toast in SDK.
2026-03-17 17:13:33 +03:00
Danila Poyarkov d3aa511056 Self-review fixes: VueUse adoption, tailwind-variants recipes
VueUse:
- useClipboard({ copiedDuring }) in CodePanel, CollabPanel \u2014 replaces
  manual copied ref + setTimeout pattern
- useDebounceFn in stores/editor.ts \u2014 replaces manual clearTimeout +
  setTimeout for autosave
- onScopeDispose in ExportSection \u2014 replaces onUnmounted for URL cleanup

Tailwind-variants:
- New iconButton() recipe (sm/md) \u2014 replaces 15+ repeated button class strings
- New sectionWrapper() + sectionLabel() \u2014 replaces 12 repeated section classes
- Applied across all 10 property section components
2026-03-17 16:48:12 +03:00
Danila Poyarkov ced083439e Complete SDK plan: all 21 component families built
New SDK components (7):
- ColorPickerRoot — color state, swatch CSS, update handler
- ColorInputRoot — hex string, updateFromHex handler
- FontPickerRoot — font families, search/filter, selection
- ImageFillControlsRoot — image preview, file picker, scale mode
- GradientEditorBar — pointer capture drag for stop bar
- GradientEditorStop — per-stop data + update handlers
- PageControlsRoot — page background color
- VariablesIndicatorRoot — collection/variable counts
- VariablesEditorRoot — collections, variables, CRUD, search/filter

App components rewritten to compose from SDK:
- ColorPicker, ColorInput, FontPicker, ImageFillPicker
- GradientEditor (script: ~100 → imports only)
- PageSection, VariablesSection

Full SDK inventory: 21 component families, 35 Vue files, 48 TS files.
Every item from SDK-PLAN.md is now implemented.
2026-03-17 16:21:26 +03:00
Danila Poyarkov 2a69ef956f SDK: AppearanceControls, TypographyControls, EffectsSection via PropertyListRoot
New SDK components:
- AppearanceControlsRoot — corner radius, visibility, opacity, blend mode
- TypographyControlsRoot — font family/weight/size, alignment, formatting
  toggles, missing font detection, font loading

EffectsSection rewritten to compose from PropertyListRoot \u2014 effect CRUD
via SDK, scrub tracking stays in app component.

All property sections now use SDK headless primitives:
- PositionSection → PositionControlsRoot
- LayoutSection → LayoutControlsRoot
- AppearanceSection → AppearanceControlsRoot
- TypographySection → TypographyControlsRoot
- FillSection → PropertyListRoot
- StrokeSection → PropertyListRoot
- EffectsSection → PropertyListRoot
- ExportSection → ExportControlsRoot
2026-03-17 15:59:43 +03:00
Danila Poyarkov 2757e9e6ec Fix PR review: critical bugs, package config, remaining title attrs
Critical fixes:
- useSceneComputed: watchEffect on state.sceneVersion as fallback
  when replaceGraph() swaps the emitter (bridge stayed on old graph)
- useLayerDrag: capture monitorForElements() cleanup, onScopeDispose
  (was leaking global listeners on tab switch)

Package fixes:
- packages/vue: sideEffects: false for tree-shaking
- Build: --noEmit → --emitDeclarationOnly (was skipping .d.ts)
- canvaskit-wasm peerDep: optional via peerDependenciesMeta
- use-canvas.ts: onUnmounted → onScopeDispose

Remaining title= attrs migrated to <Tip>:
- AppMenu toggle UI, TypographySection missing font, ProviderSettings
2026-03-17 15:30:28 +03:00
Danila Poyarkov 6ffe201bec StrokeSection: compose from PropertyListRoot (388→180 lines)
Stroke CRUD (add/remove/toggle/update color+weight) now uses
PropertyListRoot from SDK. Stroke-specific logic (sides, align,
border weights) stays in app component as functions taking activeNode.
2026-03-17 15:08:34 +03:00
Danila Poyarkov 8ba8c48a4c Migrate all title= attributes to Reka UI Tooltip via <Tip>
Replace 30 title= attributes across 15 component files with <Tip>
wrappers for proper Reka UI Tooltip behavior (positioning, delay,
animation, accessibility).

Remaining 2: TypographySection missing-font icon (non-interactive),
AppMenu toggle-UI button (browser menu context).
2026-03-17 12:22:25 +03:00
Danila Poyarkov 92857aed45 Add LayoutControlsRoot SDK primitive, LayoutSection 556→119 lines
New SDK component LayoutControls/LayoutControlsRoot.vue (189 lines):
- Layout sizing logic (width/height sizing, flex vs auto-layout vs grid)
- Alignment grid computation (3x3 for flex)
- Grid track CRUD (add/remove/update columns and rows)
- Padding management (uniform + per-side toggle)
- Sizing options derived from layout context

App LayoutSection.vue is now template-only (119 lines) — composes
LayoutControlsRoot with Tailwind styling + Lucide icons.
2026-03-17 12:10:27 +03:00
Danila Poyarkov a8e8e63a55 Replace void sceneVersion hacks with useSceneComputed composable
Add useSceneComputed() to SDK — wraps a function in a computed()
that depends on sceneVersion internally. Eliminates the anti-pattern
of 'void editor.state.sceneVersion' at the top of computed blocks.

Updated 8 files: EditorMenuRoot, ExportControlsRoot, PageListRoot,
useSelectionState, PagesPanel, CodePanel, VariablesDialog,
VariablesSection.

Zero void-sceneVersion hacks remaining in components.
2026-03-17 11:48:03 +03:00
Danila Poyarkov 513b16983f Clean remaining component leaks: requestRender, graph access, variable queries
Editor API additions:
- bindVariable/unbindVariable in nodes.ts (with undo + requestRender)
- setPageColor in pages.ts (state + requestRender)
- getVariablesByType, getVariable, getCollection, resolveColorVariable,
  getVariablesForCollection, getCollections, getCollectionCount,
  getVariableCount in variables.ts (read-through to graph)
- getNode, getChildren, getPages in create.ts (read-through to graph)

Removed:
- 5 redundant requestRender() after updateNodeWithUndo
- All store.graph.* access from components (was in 7 files, now 0)

Remaining requestRender (1 file): EffectsSection uses updateNode
(without undo) which correctly needs manual render trigger.
2026-03-17 11:38:32 +03:00
Danila Poyarkov 0fbaf1c24e Adopt Reka UI Tooltip/ToggleGroup, VueUse useClipboard
Reka UI:
- Tip.vue — reusable tooltip wrapping Reka UI Tooltip primitives
- TooltipProvider in EditorView.vue with 400ms delay
- PositionSection: 9 title= attrs → <Tip> wrappers
- TypographySection: alignment → ToggleGroup (single),
  formatting (bold/italic/underline/strikethrough) → ToggleGroup
  (multiple). Keyboard nav + ARIA for free.

VueUse:
- useClipboard replaces navigator.clipboard.writeText in
  CodePanel, CollabPanel, MobileHud, CanvasMenu
2026-03-17 11:30:25 +03:00
Danila Poyarkov fd44d7b853 Deduplicate: 47 → 28 clones, 1.37% → 0.61% duplication
Extracted shared helpers to eliminate copy-pasted code:

Core:
- computeAbsoluteBounds() in geometry.ts — replaces 4 bounding box loops
- makeDocumentNodeChange/makeCanvasNodeChange in kiwi-serialize.ts
- parseFigBuffer/parseFigKiwiContainer in kiwi/fig-parse-core.ts
  (fig-parse-worker.ts: 103 → 11 lines)
- createDefaultEditorState() — shared between core + app store
- calcClusterConfidence() in tools/analyze.ts
- retryWithNormalizedFamily() in fonts.ts
- getVectorNode() in tools/vector.ts
- renderFills() helper in renderer/scene.ts
- withNodeBounds() helper in renderer/overlays.ts
- drawNodeHighlightRect() in renderer/highlight-rect.ts
- isInViewport() in renderer/label-cache.ts

App:
- decodeTauriStderr() in utils/tauri.ts
- printNodeResults() in cli format.ts

Remaining 28 clones are: text-editor cursor movement (inherent),
xpath DOM traversal (inherent), small 5-line patterns too
context-specific to extract.
2026-03-17 11:10:11 +03:00
Danila Poyarkov 267f251f8a Phase 7: Migrate 10 components from useEditorStore to useEditor
Components now using useEditor() from SDK instead of app's useEditorStore():
- CodePanel, DesignPanel, ImageFillPicker, LayerTree, PagesPanel,
  PropertiesPanel, VariablesDialog, FillSection, PageSection,
  VariablesSection

7 components remain on useEditorStore for app-specific methods:
- AppMenu, Toolbar, MobileHud (saveFigFile, exportSelection, mobile*)
- CanvasMenu (renderExportImage)
- EditorCanvas (cursorCanvasX/Y app state)
- MobileDrawer (mobileDrawerSnap app state)
- ExportSection (exportSelection, renderExportImage)
2026-03-17 10:38:40 +03:00
Danila Poyarkov 6dd2f96790 Phase 7: App migration — Toolbar, LayerTree, CanvasMenu, ExportSection
App components now compose from SDK primitives:

Toolbar.vue — imports EDITOR_TOOLS, Tool, EditorToolDef from SDK
  instead of @/stores/editor. Tool data comes from SDK, actions
  still wire to store methods.

LayerTree.vue — rewritten to compose LayerTreeRoot + LayerTreeItem
  from SDK. Tree building, expand/collapse, selection sync,
  scroll-into-view all in SDK. App adds icons, Tailwind styling,
  context menu, DnD indicators. Deleted LayerRow.vue.

CanvasMenu.vue — composes EditorMenuRoot from SDK. Selection state,
  copy-as-* functions from SDK slot props. App adds Reka UI
  ContextMenu composition + Tailwind styling.

ExportSection.vue — composes ExportControlsRoot from SDK. Settings
  management in SDK. Export execution + preview rendering stay
  app-specific (need store.exportSelection/renderExportImage).

FillSection.vue — already composed from PropertyListRoot (prior commit).
2026-03-17 10:29:33 +03:00
Danila Poyarkov 72876f8a3e Phase 4-5: LayerTree and EditorMenu headless primitives
LayerTree SDK components:
- LayerTreeRoot — tree data from editor graph, expand/collapse with
  auto-expand on selection, scroll-into-view, scope sync
- LayerTreeItem — per-node slot with isSelected, padLeft, select,
  toggleExpand, toggleVisibility, toggleLock, rename

App LayerTree.vue now composes LayerTreeRoot + LayerTreeItem + TreeRoot/
TreeItem from Reka UI. Deleted LayerRow.vue — its logic is in the SDK.

EditorMenu SDK component:
- EditorMenuRoot — selection-aware state (hasSelection, isInstance,
  isComponent, isGroup, canCreateComponentSet, otherPages, visibility,
  lock) + copyAsText/SVG/JSX via slots

App CanvasMenu.vue now composes EditorMenuRoot — pure template with
Reka UI ContextMenu + Tailwind styling, zero business logic.

Removed useSelectionState composable — EditorMenuRoot replaces it.
2026-03-17 10:06:24 +03:00
Danila Poyarkov 638db38589 Phase 3: Property primitives — PropertyList, PositionControls
New SDK components:
- PropertyListRoot / PropertyListItem — headless array property editor
  for fills, strokes, effects. Provides add/remove/update/patch/
  toggleVisibility via slots. Handles multi-selection, mixed state.
- PositionControlsRoot — headless position/alignment/flip/rotate.
  Provides x/y/w/h/rotation + updateProp/commitProp/align/flip/rotate.

App FillSection.vue rewritten to compose from PropertyListRoot —
no more useNodeProps() destructuring, CRUD logic in SDK.

SkiaRenderer.renderFromEditorState() — single-call render that
takes EditorState directly (was 30 lines of manual property sync).
2026-03-17 09:43:26 +03:00
Danila Poyarkov d4c102c6f4 Phase 1: establish SDK component pattern with 3 exemplars
New Reka UI-style headless components following the pattern:
  ComponentName/context.ts — InjectionKey, provide/inject
  ComponentName/Root.vue — state management, slot props
  ComponentName/Item.vue — per-item logic, slot props
  ComponentName/index.ts — barrel export

ScrubInput (new headless):
  ScrubInputRoot — scrub state, pointer capture, edit/commit logic
  ScrubInputField — input element for direct editing
  ScrubInputDisplay — read-only display with slot for value/suffix
  App ScrubInput.vue composes these with Tailwind styling

Toolbar (new headless):
  ToolbarRoot — active tool, tool list, flyout state
  ToolbarItem — per-tool active state and select handler

PageList (refactored):
  PageListRoot — pages, currentPageId, CRUD actions via slots

Old components kept as backward-compatible aliases.
2026-03-17 09:12:27 +03:00
Danila Poyarkov aa805edf4d Rename NodeContextMenuContent → CanvasMenu, extract logic
- Rename to CanvasMenu.vue (was NodeContextMenuContent — terrible name)
- Move copy-as-text/SVG/JSX to core editor clipboard module
- Add useSelectionState() composable to SDK — selection-derived computeds
  (hasSelection, selectedNode, isInstance, isComponent, etc.)
- Eliminate 7x 'void store.state.sceneVersion' reactivity hacks
- Component is now mostly declarative template with menu items
2026-03-17 08:52:32 +03:00
Danila Poyarkov 492d9dfa75 Refactor LayerTree: Atlaskit DnD, split LayerRow, shared icons
Replace manual pointer-capture drag with @atlaskit/pragmatic-drag-and-drop:
- Tree-aware hitbox (reorder-above, reorder-below, make-child)
- Native drag preview via Atlaskit adapter
- No more manual DOM measurement for drop zones

Split into focused files:
- LayerTree.vue (164 lines) — tree data, expand, selection
- LayerRow.vue (175 lines) — per-row: icon, rename, lock/visibility, DnD setup
- utils/layer-icons.ts (43 lines) — icon maps shared across components
- use-layer-drag.ts (127 lines) — Atlaskit DnD composable in SDK

Add toggleNodeVisibility/toggleNodeLock per-node methods to editor.
2026-03-17 08:46:23 +03:00
Danila Poyarkov f084212ce8 Inline context menu into EditorCanvas, delete CanvasContextMenu.vue
The wrapper component existed only to do hit-test + select on right-click,
which is now editor.selectAtPoint(). The 5-line Reka UI ContextMenu
composition belongs directly in EditorCanvas where the canvas ref lives.
2026-03-17 08:31:12 +03:00
Danila Poyarkov b2ed748608 Move hit-test logic out of CanvasContextMenu into editor
Add hitTestAtPoint() and selectAtPoint() to core editor/selection.ts.
CanvasContextMenu is now a pure Reka UI wrapper — receives onContextMenu
callback from EditorCanvas which owns the canvas ref and coordinates.
2026-03-17 08:28:39 +03:00
Danila Poyarkov 44f6f55b70 Split FillPicker into focused components
FillPicker.vue (504 → 121 lines): dumb tab switcher, no gradient
internals. Delegates entirely to sub-components.

New GradientEditor.vue (227 lines): owns gradient stops, stop bar
with pointer drag, subtype select, and HsvColorArea for active stop.
Uses AppSelect instead of hand-rolled Reka UI Select.

New ImageFillPicker.vue (79 lines): image preview, file picker,
scale mode select via AppSelect.

Each component owns its full concern — no data leaking between them.
Replaced inline SVGs with Lucide icons, use twMerge for tab classes.
2026-03-17 08:22:54 +03:00
Danila Poyarkov 51bbf67f1f Remove re-export shims, import directly from @open-pencil/vue
Delete 9 shim files from src/composables/ that just re-exported
SDK composables. All 18 consumers now import from '@open-pencil/vue'.

Merge dual useNodeProps()/useMultiProps() calls into single
useNodeProps() in 5 property section components.

src/composables/ now has only 4 app-specific files:
use-chat, use-collab, use-keyboard, use-menu.
2026-03-17 08:10:05 +03:00
Danila Poyarkov 6d19a13526 Add computeBounds/requireNode helpers, scope CodePanel style, dedupe snap/zip
- geometry.ts: add computeBounds() utility for bounding box accumulation
- tools/schema.ts: add requireNode() helper + NodeNotFoundError
- CodePanel.vue: scope <style> to prevent global CSS leak
- snap.ts: replace 44-line getEdges() with rotatedBBox() from geometry
- fig-compress.ts: extract shared zip assembly from fig-export + worker
- Remove unused computeBounds import from vector.ts
2026-03-16 23:01:51 +03:00
Danila Poyarkov 30f8a3fda6 Wire editor actions, move kiwi-serialize, clean dead code
- VariablesDialog: replace 7 manual undo.push + graph mutation + requestRender
  patterns with editor.renameCollection/addVariable/removeVariable etc.
- PositionSection: replace inline alignment/flip/rotate geometry with
  editor.alignNodes/flipNodes/rotateNodes
- Move kiwi-serialize.ts into kiwi/ where it belongs
- Remove dead createPropertyChange export from undo.ts
- Un-export internal queryFonts/FontInfo from fonts.ts
- Deduplicate font weight maps: single FONT_WEIGHT_NAMES in fonts.ts,
  figma-api-proxy.ts imports from there
2026-03-16 22:30:56 +03:00
Danila Poyarkov 4b49d69601 Fix all 135 vue-tsc errors, promote to check gate
vue-tsc now runs as part of 'bun run check' — zero errors.

Fixes by category:
- TS18047 (73): null guards in LayoutSection, TypographySection
- TS18048 (17): collab injection guards in CollabPanel, MobileHud
- TS2339 (12): Uint8Array.fromBase64/toBase64 declarations (ES2024)
- TS2741 (10): AppMenu discriminated union for separators
- TS6133 (7): remove unused declarations
- TS2322 (6): FontPicker AcceptableValue, Toolbar motion-v variants
- TS2345 (3): FillSection updateArrayItem signature, ScrubInput guard
- TS2314 (2): ChatMessage UIMessagePart generic args
- Misc (5): PropertiesPanel dead comparison, VariablesDialog onSubmit,
  NodeContextMenuContent document access, LayerTree arg count
2026-03-16 22:21:21 +03:00
Danila Poyarkov 4a2c5ac0ce Fix type errors found by tsgo and vue-tsc
- Add interaction constants to core barrel export (were in constants.ts
  but not reachable via '@open-pencil/core')
- Fix variable shadowing in use-text-edit.ts: rename inner 'editor'
  (textEditor) to 'textEd' to avoid shadowing the Editor parameter
- Fix unused params: prefix with _ in scene.ts, describe-issues.ts, xpath.ts
- Remove unused getCoords param from setupPanZoom
- Move cursorCanvasX/Y assignment to app callback (not on EditorState)
- Simplify EditorStore return: spread editor instead of listing 80+ methods
  individually — new Editor methods auto-forward, no more manual sync
- Add tsgo to check script, add check:vue script for vue-tsc
- Add vue-tsc as dev dependency
- Reduces vue-tsc errors from 172 (pre-existing) to 135 (37 fixed)
2026-03-16 21:54:08 +03:00
Danila Poyarkov f44f06f721 Refactor: extract business logic from components, clean composables
Tier 1 — Core API fixes:
- updateNodeWithUndo now calls requestRender internally, eliminating
  15+ redundant requestRender() calls across 6 property panel components
- New editor/variables.ts: 7 undo-able variable CRUD operations
  (renameCollection, addCollection, removeCollection, addVariable,
  removeVariable, renameVariable, updateVariableValue)
- New editor/alignment.ts: alignNodes, flipNodes, rotateNodes —
  moves bounding box geometry out of PositionSection.vue

Tier 2 — Composable cleanup:
- Merged use-node-props + use-multi-props into single useNodeProps()
  with MIXED sentinel, eliminating duplicate computed refs
- Added toolCursor() utility — replaces if/else chain in EditorCanvas
- Renamed use-toast.ts → toast.ts (singleton module, not a composable)

Tier 3 — use-canvas-input.ts split (1497 → 817 lines):
- input/types.ts (88) — DragState variants, HandlePosition, TOOL_TO_NODE
- input/geometry.ts (170) — hit testing, handle positions, rotation cursor
- input/pan-zoom.ts (266) — wheel, touch pinch, Safari gestures
- input/resize.ts (121) — constrainToAspectRatio, applyResize
- input/auto-layout.ts (103) — insert indicator computation

All 955 tests pass, 0 lint errors, 1.48% duplication.
2026-03-16 20:35:17 +03:00
Danila Poyarkov 608be0ea27 Move composables to @open-pencil/vue SDK, unify Editor type
Phase 2 of SDK refactor:

Composables moved to packages/vue/src/composables/:
- use-canvas-input (1497 lines) — mouse/touch/gesture interaction
- use-text-edit (340 lines) — inline text editing
- use-canvas-drop (68 lines) — image drag-and-drop
- use-node-props (55 lines) — single/multi node property editing
- use-multi-props (106 lines) — multi-selection property merging
- use-inline-rename (58 lines) — inline rename input
- use-font-status (24 lines) — font loading status
- use-toast (39 lines) — toast notifications

All retyped from EditorStore to Editor (core type). App composables
are now 1-2 line re-export shims from @open-pencil/vue.

Also moved to core Editor:
- placeImageFiles / placeImageNode / decodeImageDimensions
- Interaction constants (PEN_CLOSE_THRESHOLD, ROTATION_SNAP_DEGREES, etc.)

Removed dead exports: HANDLE_SIZE, DRAG_DEAD_ZONE, ACP_DESIGN_CONTEXT.
2026-03-16 17:41:07 +03:00
Danila Poyarkov 8cdb4db7ee Add @open-pencil/vue headless SDK package
Renderless components (Reka UI-style) exposing editor logic via slots:
- OpenPencilProvider — injects editor via Vue provide/inject
- OpenPencilCanvas — CanvasKit/Skia surface with render loop
- PageList, LayerTree, ToolSelector, NodeProperties — slot-based

Composables:
- useEditor() — access editor from any descendant
- useCanvas() — low-level surface lifecycle (moved from app)

The app's use-canvas.ts is now a 2-line re-export from the SDK.
Includes example app in packages/vue/example/.
2026-03-16 17:24:45 +03:00
Danila Poyarkov 99d2703583 Extract createEditor() to packages/core/src/editor/ folder
Split the 2505-line god store into 13 domain modules sharing an
EditorContext interface:

  editor/types.ts       — EditorState, EditorOptions, Tool, EditorToolDef
  editor/create.ts      — createEditor() assembler
  editor/viewport.ts    — screenToCanvas, applyZoom, pan, zoom*
  editor/selection.ts   — select, clearSelection, marquee, snap, hover
  editor/pages.ts       — switchPage, addPage, deletePage, renamePage
  editor/shapes.ts      — createShape, pen tool, adoptNodesIntoSection
  editor/structure.ts   — group, ungroup, reorder, reparent, z-order
  editor/components.ts  — component/instance/detach/componentSet
  editor/clipboard.ts   — duplicate, copy, paste, delete
  editor/undo.ts        — commitMove/Resize/Rotation, snapshot
  editor/text.ts        — startTextEditing, commitTextEdit
  editor/nodes.ts       — updateNode, setLayoutMode

Move design constants (fills, strokes, zoom) from app to core.
Largest file is structure.ts at 394 lines (was 2505).
App store is now a thin Vue wrapper (747 lines) delegating to
the core Editor — ready for @open-pencil/vue SDK consumption.

All 955 tests pass, 0 lint errors, 1.41% duplication.
2026-03-16 15:14:50 +03:00
Danila Poyarkov f14f0a4259 Fix review issues: undoable lock/visibility toggles, use degToRad 2026-03-16 14:29:26 +03:00
Anton A S edd9a8e398 Entered container border, rotation support, INSTANCE/COMPONENT enterable, tests 2026-03-16 14:26:52 +03:00