- Replace one-off class props with typed ui objects
- Group renderless slot actions and remove DOM ref callback plumbing
- Make local font access and typography font loading explicit capability objects
- 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
- ScrubInput: skip scrub drag when pointerdown target is inside a button
- Size fields: always show chevron icon instead of hover-swap to prevent layout jump
- Variable picker: add right padding when no chevron follows, none when chevron present
The outer ScrubInput container captured all pointerdown events and
started a scrub drag, preventing embedded button clicks (like the
variable picker diamond icon) from ever reaching their handlers.
Now checks if the click target is inside a <button> before starting
the scrub.
- Extract centerline algorithm to packages/core/src/vector/centerline.ts
to stay under max-lines (index.ts 441 lines, centerline.ts 289 lines)
- Fix PathEffect.MakeDash leak: store reference and .delete() after use
- Add aria-label to dash toggle button with i18n (strokeDash)
- Read dash/gap values directly from dashPattern array in template
instead of calling dashState() 4 times
- Add unit tests for fitCircleArc and isClosedThinCrescent
(collinear, non-circular, annular wedge, rectangle, hexagon, open path)
Vector nodes with stroke.dashPattern were rendering as solid lines, and
even when the dash was applied through brute outline-stroke conversion,
closed crescent shapes (e.g. annular wedges) showed two parallel dashed
arcs instead of a single arc along the centerline.
Root cause:
- drawVectorPathStrokes converted stroke to a fill outline before drawing,
leaving no place for PathEffect.MakeDash to apply.
- A closed crescent is a single closed path traced around both the outer
and inner arcs, so dashing the path produces two visible arcs.
Fix:
- packages/core/src/canvas/scene.ts: in drawVectorPathStrokes, when the
stroke has a dashPattern, skip the outline conversion and draw with
strokePaint + PathEffect.MakeDash directly. In renderShapeUncached,
dashed VECTOR nodes with a vectorNetwork now route through a new
centerline path instead of the precomputed strokeGeometry.
- packages/core/src/vector/index.ts: new vectorNetworkToCenterlinePath.
For closed thin crescents (cycle of even length where pairwise opposite
vertex distance is small relative to perimeter and consistent), detect
the two cap segments via vertex direction-change angles, split the
cycle into outer/inner subchains, pair vertices by angle, and emit a
smooth Path.addArc when the midpoints fit a circle (fitCircleArc with
3-point construction + radius tolerance), falling back to a midpoint
polyline otherwise. Open paths use the existing chain walk.
JSX prop wiring so users can author dashed Vectors:
- packages/core/src/design-jsx/tree.ts: add strokeDash?: number[] | boolean.
- packages/core/src/design-jsx/renderer.ts: applyStrokeOverrides reads
strokeDash and writes Stroke.dashPattern; true expands to [w*2, w*2].
- packages/core/src/io/formats/jsx/export.ts: solidStroke serializes
dashPattern back to strokeDash on JSX export.
Editor UI:
- src/components/properties/StrokeSection.vue: dash toggle button plus
ScrubInputs for dash and gap length, writing strokes[0].dashPattern.
bun run check passes. Pre-existing test failures (drop shadow / auto
layout) are unrelated to this change and reproduce on a clean
upstream/master.
- 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>
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
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
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>
* 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>
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>
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>
- 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
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>
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.
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.
- 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
Long localized labels like 'По содержимому' overflow the input.
Add sizingHugShort/sizingFillShort i18n keys with abbreviated
translations for all locales. Dropdown items keep full labels.
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
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.
* 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