Commit graph

300 commits

Author SHA1 Message Date
Danila Poyarkov 0d16354c89 feat(app): add light theme setting 2026-04-30 15:52:59 +03:00
Danila Poyarkov 6545f20c53
Refactor architecture boundaries across core, app, and packages (#234)
* refactor(core): decompose editor factory and action modules

Split the monolithic editor factory and large action modules into focused
domain helpers:

- create.ts assembles context through bridge modules (clipboard,
  components, structure, undo) and delegates to graph-reads, graph-events,
  layout-runner, component-sync, and state factory
- structure.ts delegates to group, container-wrap, auto-layout-wrap,
  reorder, and state-toggle helpers
- selection.ts delegates to hit-test, overlays, container navigation,
  and read helpers
- clipboard.ts delegates to subtree-history, images, export, copy,
  fonts, and placement helpers
- shapes.ts delegates to pen actions and section-adopt
- components.ts delegates to focus and instances helpers
- alignment.ts delegates to flip-rotate helper
- text.ts uses explicit TextEditSession for snapshot comparison

New focused modules: nudge, variable-bindings, layout-mode,
page-viewports, tool-registry, color-space

Undo: history/position and history/snapshot helpers, hardened
batch/rollback with nested batch support and configurable limit

* refactor(core): split tool definitions by domain

Split the monolithic tool registry into domain-specific modules:

- read/ — selection, find, pages, fonts, components, nodes, query, jsx
- create/ — basic shapes, components, vector, JSX render
- modify/ — paint, effects, geometry, layout, state, text, update
- structure/ — basic, arrange, batch, hierarchy, replace, tree
- variables/ — bindings, collections, read, values
- vector/ — boolean, path, export, viewport
- analyze/ — colors, typography, spacing, clusters, diff, eval
- describe/ — summaries, tree, roles, layout-issues
- stock-photo/ — providers, requests, apply
- codegen/ — component-map, tokens

Split registry into core/extended tiers; refine schema and AI adapter

* refactor(core): restructure kiwi codec and instance overrides

Reorganize the Kiwi .fig codec into domain subdirectories:

- binary/ — codec, schema, protocol
- fig/ — file, import, parse (core, worker, transfer)
- node-change/ — convert, export-node, serialize, plugin-data
- instance-overrides/ — constraints, dsd, populate, props, resolve,
  symbol-overrides, symbol-props, sync, types

Vendored kiwi-schema/ left isolated

* refactor(core): split profiler, icons, IO, and add subpath exports

Profiler: speedscope-export, capture-session, hud-controller
Icons: api, svg, types, render, create-icons tool
IO: format registry and subpath exports
Canvas/color/text/vector: targeted cleanup

Add deliberate subpath exports: random, xpath, vector, color, canvas,
scene-graph, kiwi, design-jsx, io, tools, editor, layout, canvaskit,
profiler, text, lint, rpc, figma-api, constants

* refactor(vue): decompose canvas input, surface lifecycle, and controls

Canvas surface: gl-surface, kit-loader, render-loop, resize-observer

Canvas input handlers:
- move: drop-target, move-snap, duplicate-drag
- select: select-move, select-hover, select-hit
- resize: resize-rect, resize-vector, resize-start
- transform: rotation, marquee, pan, text-selection
- text-edit: navigation, clipboard, textarea lifecycle
- Shared: click-count, space-key, pan, pan-zoom, draw, raf-scheduler

Editor composition:
- commands split: actions, context, metadata, edit, selection, view
- menu-model split: command-groups, builders, types
- Gradient stop composable reuse in primitive root

Controls: fill, layout, typography, appearance, effects, stroke,
okhcl, prop-scrub, node-props, undo-batch, color-variable-binding
Variables/i18n/document/export helpers

Organize canvas, primitives, controls, editor, and variables into
cohesive module directories with package-local import aliases

Expose MenuActionNode/MenuSeparatorNode from public API

* refactor(app): split document IO, editor session, and automation bridge

Document IO: source-state, naming, writer, reload-source, reload-state,
imported-document, watch-targets, save-targets

Editor session: create, modules, types, accessors, computed, refs
Editor canvas: loader-overlay, collaboration-awareness,
context-selection, menu-actions, menu-model

Automation bridge: eval, tools, exports, files, selection, RPC fallback

AI/ACP: transport, map-update, permission, debug, chat effects/storage
Collab: awareness, graph-bindings, yjs-sync, follow, session, types
Shell keyboard: actions, bindings, clipboard, focus, nudging,
raw-events, registry, reserved, shortcuts, space-tool
Shell menu: app-menu, document-name, entry, files

Demo: colors, effects, helpers, section builders (components,
app-preview, effects, standalone, variables) — document.ts reduced
from 981 to 32 lines as pure orchestrator

Move app modules under src/app/ with organized domain structure:
editor, document, ai, collab, shell, automation, demo, tabs

* refactor(app): decompose UI components with provide/inject context

Split monolithic components using Reka UI-inspired namespace folders
with scoped provide/inject context — no prop drilling:

- CollabPanel/ — context, avatars, share, connected, join
- ColorPickerPanel/ — context, area, format, field groups, sliders
- MobileHud/ — context, action toast, tool badge, file menu, presence
- ProviderSettings/ — context, API key/type, endpoint, tokens, photos
- Toolbar/ — actions, types, desktop, mobile, tool button, flyout
- LayoutSection/ — types, auto-layout, flex, grid, padding, size, clip

Properties helpers: fill-okhcl adapter, fill-label, color-style-row
Menu: entry helpers, document-name rename, stale type removal

* refactor(mcp): split server into focused modules

- browser-rpc — WebSocket client management
- mcp-sessions — session lifecycle
- tool-output — response formatting
- tool-schema — Zod schema generation from ToolDefs
- jsx-preprocess — JSX source transformation
- result — result helpers
- tool-registration — MCP tool wiring
- auth — API key validation
- http-options — CORS/request handling
- stdio-bridge — stdio transport adapter

* refactor(cli): split analyze subcommands and shared helpers

- Analyze subcommands: clusters, colors, spacing, typography
- RPC data loading helper
- Migrate imports to targeted core subpath exports

* refactor(docs): split VitePress config and shared table component

Config helpers: sdk-sidebar, seo, labels, sidebars, locale-theme,
root-theme, locales

Shared SdkDataTable component replaces duplicated table markup in
SdkPropsTable, SdkEventsTable, and SdkSlotsTable

Update contributing and testing docs

* refactor(tauri): decompose desktop entrypoint

Split lib.rs into focused service modules:

- fig_container.rs — .fig archive/compression commands
- fonts.rs — font cache and system font enumeration
- menu.rs — native menu construction
- menu_events.rs — menu event dispatch and devtools toggle
- window.rs — main window show/focus lifecycle

* test: share domain test factories and migrate fixtures

New shared helpers:
- tests/helpers/scene.ts — makeSceneGraph factory
- tests/helpers/vector-network.ts — vertex/segment/network builders
- tests/helpers/fig-traversal.ts — all-node collection, type counts
- tests/helpers/undo.ts — undo test utilities
- tests/helpers/editor-history.ts — editor history test helpers

Migrate render, vector, fig-roundtrip, and undo tests to use shared
factories instead of inline fixture construction

* build: add structural lint rules, split vite config, update docs

Structural lint (oxlint.structure.json + lint/plugin.js):
- 20+ custom rules enforcing package boundaries, lifecycle patterns,
  naming conventions, and import discipline

Vite config split: raw-markdown, canvaskit-assets, pwa, server,
aliases, automation plugins

Remove legacy shims and utils superseded by SDK/core modules
Update AGENTS.md, CONTRIBUTING.md, eval-command docs, tsconfig

* fix(vue): normalize canvas directory casing and remove duplicate export

- Rename Canvas/ to canvas/ in git index to match #vue/canvas/* imports
  (PascalCase was correct for component primitives but canvas/ is a
  non-component domain directory)
- Remove duplicate ./random subpath export in core package.json

* fix: add #vue and #core Vite resolve aliases for dev server

* refactor(core): reduce remaining large modules

Split the remaining large core hotspots into cohesive domain modules while preserving public facades and behavior.

- Extract scene graph types, variables, node defaults, and vector-network helpers
- Decompose canvas renderer orchestration, state, paints, colors, lifecycle, labels, and delegated domain methods into renderer/ and labels/ subfolders
- Split Kiwi node-change, binary variable binding, layout, RPC, vector, JSX export, clipboard, design JSX, and Figma proxy helpers
- Replace collision-driven *Fn import aliases with namespace imports and enforce the pattern in lint

Validation:
- bun run check
- bun --filter @open-pencil/vue build
- bun run test:dupes

* fix(app): forward color input attrs

* fix(app): cover section drawing errors

* fix(editor): undo option-drag duplicates

* docs: document domain subfolder convention

* fix(app): handle undo redo on keydown

* refactor(app): dispatch shortcuts from keydown

* refactor: group prefixed domain modules

* refactor(app): use tinykeys for shortcuts

* refactor(core): group symbol override modules

* refactor(core): group fig kiwi container helper

* refactor(canvas): split overlay rendering modules

* refactor(vue): remove unused internal barrels

* fix(app): lay out demo components before instancing

* fix(app): restore demo badge spacing

* perf(canvas): split scene and overlay rendering

* refactor(vue): wrap wheel gesture lifecycle

* fix(canvas): wait for fonts before hiding loader

* docs: update unreleased changelog
2026-04-30 15:14:19 +03:00
Danila Poyarkov 1d5558ddc3 feat: JSX reference, multi-root JSX, prompts as markdown files
- Move CODEGEN_PROMPT to codegen.md, loaded via raw-md bundler plugin
- Add JSX_REFERENCE as jsx-reference.md with full prop/tag/example docs
- Copy JSX Reference button (book icon) in Code panel header
- Multi-root JSX: try parsing as-is, wrap in fragment on failure
- Component and Instance tag aliases in JSX renderer
- renderJSX returns RenderResult[] to support fragments
- raw-md plugin for both tsdown and Vite

Co-authored-by: sld0Ant <sld0Ant@users.noreply.github.com>
2026-04-22 17:51:41 +03:00
Danila Poyarkov a46d63916c fix(chat): fix provider settings popover not appearing
The Tip (Tooltip) wrapper around PopoverTrigger caused floating-ui
to miscalculate the popover position as translate(0px, -200%),
rendering it off-screen. Replaced with a plain title attribute
for the hover hint.

Fixes #218
2026-04-22 16:52:36 +03:00
Danila Poyarkov 44484050e2 fix(canvas): make canvas focusable so Backspace works after click
The canvas had no tabindex, so clicking on it didn't move focus away
from previously focused input elements. When document.activeElement
was still an input/textarea (e.g. from a panel interaction), the
keyboard shortcut guard treated Backspace as blocked by an active
input — making it impossible to delete selected nodes via the canvas.

- Add tabindex="-1" + outline-none to the canvas element
- Explicitly focus canvas on mousedown (except during text editing)

Fixes #220
2026-04-22 13:45:23 +03:00
Danila Poyarkov 776d8969d4 fix(app): support cmd/ctrl+click for layers panel multi-select
Previously only Shift+click toggled additive selection in the layers
panel. Cmd+click (macOS) and Ctrl+click (Windows/Linux) had no effect
beyond replacing the selection with a single layer.

Treat Shift, Cmd, and Ctrl modifiers equally as additive toggle, matching
common design tool conventions (Figma, Sketch).

Co-authored-by: TKman <102001532+greekr4@users.noreply.github.com>
2026-04-22 13:28:25 +03:00
Anton Soldatov 029c3637a1
Fix tab order and keyboard handling in inspector panel (#191)
* fix(a11y): tab order in inspector panel follows visual layout

- ScrubInput gets tabindex=0 so Tab reaches it; on focus it enters
  edit mode and transfers focus to the inner <input>
- All inspector buttons get explicit tabindex=0 to follow DOM order
- Tab flow: align buttons → X → Y → W → H → rotation → flip/rotate →
  opacity → corner radius → ...

Closes #180

* fix(inspector): apply ScrubInput value live while typing

* fix(keyboard): block shortcuts while ScrubInput is focused

- Detect scrub input focus via closest() in isEditing() guard
- Add isScrubInputFocused() check in plain() so tool shortcuts,
  Backspace, Enter etc. don't fire while typing in inspector fields

* fix(inspector): use reactive state for ScrubInput focus, remove redundant tabindex on buttons

- Surface editing state from ScrubInputRoot via editing-change emit
- Track scrubInputFocused in editor store instead of querying DOM
- Remove no-op tabindex="0" from all <button> elements
- Keep tabindex on ScrubInput wrapper <div> (actual tab order fix)

---------

Co-authored-by: Danila Poyarkov <dev@dannote.net>
2026-04-16 10:14:16 +03:00
Danila Poyarkov 1109c7498a chore: format 2026-04-15 13:45:40 +03:00
Danila Poyarkov 728274f4a7 fix: tooltip competing with popover trigger on Windows
Add disabled prop to Tip — forces tooltip closed when the parent popover
is open. Fixes the settings gear and variable-bind popovers not opening
on Windows WebView2 due to TooltipTrigger intercepting pointer events.

Closes #171
Co-authored-by: Anton Soldatov <sld0Ant@users.noreply.github.com>
2026-04-15 13:45:35 +03:00
Danila Poyarkov 837c09bd78 fix(tauri): ATS exception for localhost WebSocket on macOS
Allow insecure localhost connections via NSAllowsLocalNetworking so the
WebSocket bridge works in release builds on macOS.

Also add debug logging for WebSocket and MCP health check failures.

Closes #190
Co-authored-by: Anton Soldatov <sld0Ant@users.noreply.github.com>
2026-04-15 13:16:29 +03:00
Danila Poyarkov 82c390c2d2 refactor(tauri): clean up external link helper
- Move use-external-link.ts → utils/external-link.ts (plain function, not a composable)
- Remove non-null assertions on providerDef.keyURL (v-if already guards)
- Remove test scaffolding from production global.d.ts
2026-04-15 11:03:31 +03:00
Anton Soldatov 7b5a5bdeef
fix(tauri): use opener plugin for external links instead of shell (#197)
External links (<a target="_blank">) in the AI panel triggered
"Command plugin:shell|open not allowed by ACL" on Tauri desktop.

Root cause: Tauri v2 intercepts <a target="_blank"> via the shell plugin,
but only the opener plugin had ACL permissions. Replace all external links
with programmatic openUrl() from @tauri-apps/plugin-opener, which is already
whitelisted via "opener:default" in capabilities.

- Extract openExternalLink() composable for Tauri/browser switch
- Replace <a target="_blank"> with <button @click> in ProviderSetup/Settings
- Add E2E test verifying window.open is called in browser context

Fixes #193

Co-authored-by: Danila Poyarkov <dev@dannote.net>
2026-04-15 11:02:00 +03:00
Chris Gliddon 2b4b8c0f63
fix(toast): error toasts auto-dismiss + dedupe + cap stack at 5 (#209)
Error toasts had `duration: 0` which in reka-ui means "never auto-
dismiss." Combined with the global `window.error` /
`unhandledrejection` handler in `setupGlobalErrorHandler`, any error-
emitting source — even a transient one called repeatedly per frame
(wheel events, render-loop callbacks) — could pile up over the
canvas with no way to recover except a hard-restart.

Three small changes that compose:

- Errors now use ERROR_TOAST_DURATION (10s) instead of 0. Long
  enough to read; short enough to self-clean.
- Dedupe by (message, variant): repeating the same error increments
  a count badge ("×3") on the existing toast instead of stacking
  duplicates. Prevents the cascade-on-every-frame failure mode.
- Hard cap at 5 stacked toasts; oldest drop off when a 6th would
  arrive. Belt-and-suspenders against any source we missed.

The Toast interface gains a non-optional `count: number` (defaults
to 1). External callers (info/warning/error) are unaffected — they
go through the new internal `push()` which sets it.
2026-04-15 10:27:32 +03:00
Danila Poyarkov 714390b54b feat: copy node ID / XPath from context menu, CLI selection command
Add nodeToXPath() to core — builds a unique XPath selector for any node
by walking up the tree and using name predicates (or positional index
when names collide among same-type siblings). Properly escapes names
containing quotes using XPath concat() or double-quote delimiters.

UI: "Copy node ID" and "Copy XPath" items in the canvas right-click
"Copy/Paste as" submenu. Also i18n the entire submenu — all labels now
use menuMessages with translations for de, es, fr, it, pl, ru, zh-CN.

CLI: `open-pencil selection` command (app-mode only) returns selected
nodes with id, name, type, size, and xpath. Connects to the running
app via the automation WebSocket.

Automation: new `selection` RPC handler that returns selected nodes
with computed XPath selectors.
2026-04-15 10:09:52 +03:00
Danila Poyarkov a20d170956 Release v0.11.3 2026-04-08 16:06:37 +03:00
Danila Poyarkov 862c0f28dc Unify UI styling to slot-based useXxxUI() pattern
- Convert button, input, icon-button, toast, surface from flat-string
  functions to slot-based { base: string } return pattern
- Convert section from bare TV exports to useSectionUI() with
  { wrapper, label } slots
- Remove standalone select helpers (selectTrigger, selectContent,
  selectItem) in favor of useSelectUI()
- Delete use-component-ui.ts — no longer needed
- Update all 20+ call sites across property panels and chat components
2026-04-06 22:58:34 +03:00
Danila Poyarkov e9f8fd352d Use short universal labels for inline sizing suffix
Long localized labels like 'По содержимому' overflow the input.
Add sizingHugShort/sizingFillShort i18n keys with abbreviated
translations for all locales. Dropdown items keep full labels.
2026-04-06 21:25:03 +03:00
Danila Poyarkov 467a2e1dc1 Unify size input with inline sizing dropdown
Replace the separate ScrubInput + AppSelect pair with a single
combined control: the sizing mode (Hug/Fill) shows as an inline
label inside the scrub input, swapping to a chevron on hover.

- ScrubInput: add group class and #suffix slot (stays generic)
- LayoutSection: embed reka-ui Select in the suffix slot with
  group-hover toggle between label and chevron
2026-04-06 19:52:27 +03:00
Danila Poyarkov 2c5c612e0f Show auto-layout panel for components and instances
The layout section was gated on node.type === 'FRAME', hiding it for
COMPONENT, COMPONENT_SET, and INSTANCE nodes that also support
auto-layout. Also use copyStyleRuns instead of structuredClone per
project lint rules.
2026-04-06 16:05:18 +03:00
Danila Poyarkov 0287808b87 Use static icon components in layout direction buttons
Dynamic component :is with template literals aren't resolved by
unplugin-icons, so arrow-right/arrow-down/layout-grid were missing.
2026-04-06 15:50:57 +03:00
Danila Poyarkov cc7f055dfc Format 2026-04-06 14:40:19 +03:00
Danila Poyarkov 1c99e6a9c2
fix(acp): show install errors for missing agent CLIs (#176)
* fix(acp): show install errors for missing agent CLIs (#172)

* Use npm for install commands, toasts for errors, tighten error matching

- Replace bun add -g with npm i -g (universal baseline)
- Replace initError inline banner with toast.show() (consistent with rest of app)
- Remove overly broad 'not found' / 'no such file' from isMissingCommandError

* Refactor toast API to toast.info/warning/error
2026-04-06 14:19:38 +03:00
Danila Poyarkov 943995fb70 Fix MCP auth, collab state, and export bounds 2026-04-01 21:27:04 +03:00
Danila Poyarkov b826202b09 Fix inline rename and rotated hit testing 2026-03-31 16:44:35 +03:00
Danila Poyarkov fd0124b6f8 Add Arabic and RTL support
# Conflicts:
#	packages/core/src/renderer/text.ts
2026-03-29 21:06:29 +03:00
Danila Poyarkov 25e656d68c Add zoom dropdown 2026-03-29 19:46:21 +03:00
Danila Poyarkov 581abf3b93 Fix export panel target handling 2026-03-29 19:16:45 +03:00
Danila Poyarkov d0d6ba9def Move reusable color picker logic into Vue SDK 2026-03-29 18:54:14 +03:00
Danila Poyarkov 1f11392da6 Refactor color picker field formats 2026-03-29 18:46:55 +03:00
Mahmoud Almontasser 9ed102b706 feat: add Arabic and RTL support 2026-03-29 11:42:30 +02:00
Danila Poyarkov 04c4c208e7 Add OkHCL color picker support 2026-03-28 20:33:35 +03:00
Danila Poyarkov 0001802dda Add shared IO format registry 2026-03-28 01:58:05 +03:00
Ilya Nikitin 0ff5a87ccb update pen tool space drag spec
Adds a new scenario to the pen tool specifications defining the behavior for relocating the active vertex with spacebar during a curve drag, including asserting that the cursor remains a crosshair instead of switching to the hand tool.

Made-with: Cursor
2026-03-27 21:27:45 +05:00
Ilya Nikitin 455f04d6f1 Merge branch 'up_master' into feat/vector-editor
# Conflicts:
#	src/composables/use-keyboard.ts
2026-03-27 19:33:07 +05:00
Ilya Nikitin ae2fbccf01 fix(components): update Lucide icon names in properties sections
Ensure proper semantic alignment between icon names and their visual representation
2026-03-27 17:09:34 +03:00
Ilya Nikitin e393ca7a07 fix(properties): update Lucide icon names for alignment and flip controls
Ensure proper semantic alignment between icon names and their visual representation
2026-03-27 17:09:02 +03:00
Danila Poyarkov c1d7fc80b0 Fix property panel visibility toggles, independent corners/stroke sides, extract shared ColorStyleRow
- Fix fill/stroke/effect/appearance visibility eye toggle (stale clone reads)
- Fix independent corner radii toggle not showing 4 inputs
- Fix stroke sides toggle: replace dropdown with direct toggle like corners
- Copy uniform weight to all sides when expanding stroke sides
- Batch multi-selection visibility toggles into single undo entry
- Extract ColorStyleRow shared by FillSection and StrokeSection
- Replace useFillVariableBinding/useStrokeVariableBinding with useColorVariableBinding(kind)
- AppearanceSection uses useAppearance() directly instead of slot wrapper
- Add sceneVersion dependency to useNodeProps and PropertyListRoot computeds
- Add E2E tests for visibility toggles and corner/stroke side toggles
2026-03-27 17:06:33 +03:00
Ilya Nikitin 2b92916881 feat(vector): add anchor point alignment for selected vertices
Align selected anchor points relative to each other in vector edit mode. The standard alignment buttons in the position panel now operate on selected vertices when 2 or more are selected, enabling precise vector path editing workflows.
2026-03-26 11:55:56 +05:00
Danila Poyarkov e03038f4bf Translate collaboration share popover 2026-03-25 21:48:38 +03:00
Danila Poyarkov 4b9cd54983 Translate more editor UI surfaces 2026-03-25 18:47:23 +03:00
Danila Poyarkov 9f296b7357 Translate chat setup and code panel strings 2026-03-25 18:40:17 +03:00
Danila Poyarkov 2f646ffcaf Translate more visible UI strings 2026-03-25 18:26:03 +03:00
Danila Poyarkov 77032ab092 Translate visible panel chrome for Russian locale 2026-03-25 18:12:09 +03:00
Danila Poyarkov 9220ef399b Handle checkbox submenu items in app menu 2026-03-25 18:07:24 +03:00
Danila Poyarkov 6bfc5c2189 Rename toggleUI i18n key 2026-03-25 17:40:26 +03:00
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 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