Commit graph

896 commits

Author SHA1 Message Date
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 eaa2adef9d Update REFACTOR.md with final status of all items 2026-03-16 23:19:55 +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 4a25b88438 Document check:vue script in AGENTS.md 2026-03-16 21:55:05 +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 59b66e68ca Update REFACTOR.md with completed items 2026-03-16 20:35:42 +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 7defe46369 Document @open-pencil/vue in AGENTS.md 2026-03-16 17:25:25 +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 6379217692 Update AGENTS.md and REFACTOR.md with editor architecture docs 2026-03-16 15:15:38 +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 0eb40e8fee Add refactoring roadmap from codebase audit 2026-03-16 15:02:49 +03:00
Danila Poyarkov 2a69990b34 Remove stale pnpm-workspace.yaml and ARCHITECTURE-NEXT.md 2026-03-16 14:38:54 +03:00
Danila Poyarkov 71fc74f93f Remove stale tweet draft 2026-03-16 14:37:42 +03:00
Danila Poyarkov 5e9afab441 Merge: Layer lock/visibility toggles + Figma-style selection scope (#116) 2026-03-16 14:29:33 +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 da97f13040 Update group hit-test assertion to match Figma-style behavior 2026-03-16 14:27:46 +03:00
Anton A S 0a50326bb7 Update changelog 2026-03-16 14:27:46 +03:00
Anton A S edd9a8e398 Entered container border, rotation support, INSTANCE/COMPONENT enterable, tests 2026-03-16 14:26:52 +03:00
Anton A S 6d3e2d20cd Scope checklist: marquee in scope, layer panel sync, delete locked guard, undo/redo validation, tests 2026-03-16 14:26:52 +03:00
Anton A S 1b743bec94 Add selection scope: enter containers with dblclick, exit with Escape or click-outside 2026-03-16 14:26:52 +03:00
Anton A S 19d6e01960 Fix double-click enter: offset coords to group-local space 2026-03-16 14:26:52 +03:00
Anton A S 01235c0d84 Fix group selection: single click selects group, double click enters, locked groups block enter 2026-03-16 14:26:52 +03:00
Anton A S 116acb5cfa Make lock/visibility icons more visible 2026-03-16 14:26:52 +03:00
Anton A S 6596702389 Fix lock: block move/resize/rotate for locked nodes, skip locked in marquee, improve toggle icon visibility 2026-03-16 14:26:52 +03:00
Anton A S bc85d73cc8 Add lock and visibility toggles to layers panel 2026-03-16 14:26:52 +03:00
Danila Poyarkov 7572d3a54e Add roundtrip tests for COLOR variable alpha handling
Co-authored-by: Shai Rubinstein <rubinsh@gmail.com>
2026-03-16 14:23:19 +03:00
Danila Poyarkov 1b6a75bd67 Update CHANGELOG with all changes since 0.10.0 2026-03-16 14:19:32 +03:00
Danila Poyarkov 9391ac0112 Fix save crash when COLOR variable is missing alpha
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
2026-03-16 14:17:28 +03:00
Danila Poyarkov 8d7dd0a36b Add random.ts helpers, replace all raw crypto.getRandomValues calls
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.
2026-03-16 14:13:36 +03:00
Danila Poyarkov 921d2d35c5 Export IS_BROWSER from core, replace all raw typeof window checks
- 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)
2026-03-16 14:05:24 +03:00
Danila Poyarkov c06ed32990 Harden linting: add eqeqeq, prefer-const, radix, no-console, no-typeof-window-check
New built-in rules:
- eqeqeq (null-safe) — prevent loose equality coercions
- prefer-const — catch unused let declarations
- radix — require parseInt radix parameter
- no-console — warn on console.log in app code (CLI/MCP exempted)

New custom rule:
- no-typeof-window-check — flag raw typeof window checks, prefer
  IS_BROWSER/IS_TAURI constants (warn-level, constants.ts + fonts.ts
  exempted)

Fix 4 violations caught by new rules.
2026-03-16 13:54:07 +03:00
Danila Poyarkov 283b836d50 Add unit tests for font helpers: isVariableFont, normalizeFontFamily, styleToVariant, fetchBundledFont
21 new tests covering:
- normalizeFontFamily: Variable suffix stripping, edge cases
- styleToVariant: all weight→Google variant mappings
- isVariableFont: fvar table detection with synthetic TTF buffers
- fetchBundledFont: headless asset resolution, TTF magic validation
2026-03-16 13:40:16 +03:00
Danila Poyarkov 15f80d6d62 Refactor font loading: DRY up local font lookup, fix headless fallback
- 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
2026-03-16 13:38:16 +03:00
Danila Poyarkov 2cb83c5582 Simplify set_layout HUG default condition
When wasNone is true, args.direction is guaranteed truthy (we early-return
otherwise), so the extra args.direction check is redundant.
2026-03-16 13:28:17 +03:00
Danila Poyarkov b3d3c0a00b Use collab injection in MobileHud instead of prop drilling
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.
2026-03-16 13:27:17 +03:00
Danila Poyarkov 6a40e4e3e3 Disable automation WebSocket in production web app
Only connect to the local automation bridge in Tauri desktop or
local dev, matching the existing spawnMCPIfNeeded guard.

Fixes #126
2026-03-16 13:27:17 +03:00
rubinsh a8fc8f1ea8
fix(set_layout): default to HUG sizing when enabling auto-layout (#125)
* 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
2026-03-16 13:27:02 +03:00
Danila Poyarkov 63e204fc71 Fix CI: use npm vue-stream-markdown instead of local link 2026-03-15 19:37:31 +03:00
Danila Poyarkov f4c68f7f41 Release v0.10.0 2026-03-15 19:27:37 +03:00
Danila Poyarkov 21996096e8 Merge acp-research: ACP agent support, MCP server refactor 2026-03-15 18:46:46 +03:00
Danila Poyarkov 12d3b60e69 Clean up ACP branch: gate debug log, fix capitalizations, harden types
- Gate acpDebugLog behind import.meta.env.DEV (no logging in production)
- Remove saveAcpDebugLog (Desktop hardcode) — clipboard copy is sufficient
- Hide ACP log button unless in DEV mode with entries
- Fix textFromContent: proper type guards instead of unsafe casts
- Fix trailing empty line in server.ts handleBrowserMessage
- Rename Acp → ACP, Mcp → MCP in all identifiers:
  AcpPermissionDialog → ACPPermissionDialog (file + component)
  AcpDebugEntry → ACPDebugEntry
  spawnMcpIfNeeded → spawnMCPIfNeeded
  checkMcpHealth → checkMCPHealth
  McpContent/McpResult/McpTransport → MCPContent/MCPResult/MCPTransport
  createMcpSession → createMCPSession
2026-03-15 18:24:37 +03:00
Anton A S 63f3ef3755 Show agent name in model selector for ACP, fix model dropdown, update docs 2026-03-15 18:13:29 +03:00
Anton A S 55ba13b43b Use full system prompt for ACP agents instead of short context 2026-03-15 18:06:15 +03:00