Commit graph

890 commits

Author SHA1 Message Date
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
Anton A S 6fcb29686d Save ACP debug log to Desktop, keep last 5 minutes 2026-03-15 17:47:32 +03:00
Anton A S 83f7c53fee Add ACP debug log with copy button in chat toolbar 2026-03-15 17:45:14 +03:00
Anton A S 206cb3742f Debug: log ACP session updates 2026-03-15 17:42:50 +03:00
Anton A S aaae074223 Fix ACP settings: show stock keys for all providers, always show Clear button 2026-03-15 17:42:01 +03:00
Anton A S a76d20b27b Fix ACP agent commands: claude-agent-acp, remove stale ts-expect-error 2026-03-15 17:06:57 +03:00
Anton A S 583b03eb3b Harden ACP transport, MCP server, and add permission dialog
- Extract mapUpdate to testable module, dynamic import for @tauri-apps/plugin-shell
- Add warnings for unhandled ACP content types and empty tool titles
- MCP server: session limit (max 10), fix null WS comparison, guard JSX preprocessing
- MCP server: read version from package.json instead of hardcoded 0.0.0
- MCP tests: use port 0 (OS-assigned) to prevent collision
- Connection error handling with user-friendly messages, stale session recovery
- Agent crash detection via close handler, destroying flag, buildCrashChunks
- Port collision: detect EADDRINUSE in vite-plugin stderr and log clear error
- Production Tauri: spawn openpencil-mcp via shell plugin, orphan reuse via health check
- Permission confirmation dialog (reka-ui AlertDialog) with queue, 60s auto-reject timeout
- Health check in ProviderSelect hides ACP agents when MCP server unavailable
- Move DESIGN_CONTEXT to app constants, add ACP_PERMISSION_TIMEOUT_MS
- 36 tests across 3 files (acp-transport, acp-permission, mcp-server)
- Update CHANGELOG, README, CONTRIBUTING, AGENTS.md
2026-03-15 16:49:43 +03:00