* 🐛 Fix nil path content crash by exposing safe public API
Move nil-safety for path segment helpers to the public API layer
(app.common.types.path) rather than the low-level segment namespace.
Add nil-safe wrappers for get-handlers, opposite-index, get-handler-point,
get-handler, handler->node, point-indices, handler-indices, next-node,
append-segment, points->content, closest-point, make-corner-point,
make-curve-point, split-segments, remove-nodes, merge-nodes, join-nodes,
and separate-nodes. Update all frontend callers to use path/ instead of
path.segment/ for these functions, removing the path.segment require
from helpers, drawing, edition, tools, curve, editor and debug.
Replace ad-hoc nil checks with impl/path-data coercion in all public
wrapper functions in app.common.types.path. The path-data helper
already handles nil by returning an empty PathData instance, which
provides uniform nil-safety across all content-accepting functions.
Update the path-get-points-nil-safe test to expect empty collection
instead of nil, matching the new coercion behavior.
* ♻️ Clean up path segment dead code and add missing tests
Remove dead code from segment.cljc: opposite-handler (duplicate of
calculate-opposite-handler) and path-closest-point-accuracy (unused
constant). Make update-handler and calculate-extremities private as
they are only used internally within segment.cljc.
Add missing tests for path/handler-indices, path/closest-point,
path/make-curve-point and path/merge-nodes. Update extremities tests
to use the local reference implementation instead of the now-private
calculate-extremities. Remove tests for deleted/privatized functions.
Add empty-content guard in path/closest-point wrapper to prevent
ArityException when reducing over zero segments.
The get-frame-ids function could enter infinite recursion when:
1. There's a circular reference in the frame hierarchy
2. A shape's frame-id points to itself (corrupt data)
The fix uses the cached version (get-frame-ids-cached) in recursive calls
and adds a guard to prevent self-referencing.
The stale-asset-error? predicate only matched keyword-constant
cross-build mismatches ($cljs$cst$). Protocol dispatch failures
($cljs$core$I prefix, e.g. IFn/ISeq) and V8's 'Cannot read
properties of undefined' phrasing were not covered, so the handler
fell through to a generic toast instead of triggering a hard reload.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Zone.js (injected by browser extensions such as Angular DevTools) patches
addEventListener by wrapping it and assigning a custom .toString to the
wrapper via Object.defineProperty with writable:false. When the same
element is processed a second time, the plain assignment in strict mode
(libs.js is built with a "use strict" banner) throws a native TypeError:
"Cannot assign to read only property 'toString' of function '...'".
This error escapes the React tree through the window error/unhandledrejection
events and was surfacing the exception page to users even though Penpot itself
is unaffected.
The fix:
- Extract the private ignorable-exception? helpers from the letfn block into
top-level defn/defn- forms so the predicate can be reused elsewhere.
- Add the Zone.js toString TypeError to the ignorable-exception? predicate so
the global uncaught-error handler silently suppresses it.
- The React error boundary is intentionally left unchanged: anything that
reaches it has executed inside React's reconciler and must not be ignored.
Cache in-progress frame traversals before following parent frame links so thumbnail updates stop recursing forever on cyclic or transiently inconsistent shape graphs.
Add a regression test that covers cyclic frame-id chains and keeps the expected frame/component extraction behavior intact.
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ✨ Expand interaction helper test coverage
Add coverage for interaction destination and flow helpers,
including nil handling and removal helpers. Document the
intent of the new assertions so future interaction changes
keep the helper contract explicit.
* ✨ Cover interaction validation edge cases
Exercise the remaining interaction guards and overlay
positioning edge cases, including invalid state
transitions and nested manual offsets. Keep the test
comments focused on why each branch matters for editor
behavior.
Clamp the frame index to the valid range in zoom-to-fit and
zoom-to-fill events before accessing the frames vector. When the
URL query parameter :index exceeds the number of frames on the
page (e.g. index=1 with a single frame), nth would throw
"No item 1 in vector of length 1". Also adds unit tests covering
the boundary condition.
Extend the telemetry payload with a sorted list of unique email domains
extracted from all registered profile email addresses. The new
:email-domains field is populated via a single SQL query using
split_part and DISTINCT, and is included in the stats sent when
telemetry is enabled.
Also update the tasks-telemetry-test to assert the new field is present
and contains the expected domain values.
Return nil from get-prev-sibling when the shape is no longer present in
the parent ordering so delete undo generation falls back to index-based
restore instead of crashing on invalid vector access.
* 🐛 Handle plugin errors gracefully without crashing the UI
Plugin errors (like 'Set is not a constructor') were propagating to the
global error handler and showing the exception page. This fix:
- Uses a WeakMap to track plugin errors (works in SES hardened environment)
- Wraps setTimeout/setInterval handlers to mark errors and re-throw them
- Frontend global handler checks isPluginError and logs to console
Plugin errors are now logged to console with 'Plugin Error' prefix but
don't crash the main application or show the exception page.
Signed-off-by: AI Agent <agent@penpot.app>
* ✨ Improved handling of plugin errors on initialization
* ✨ Fix test and linter
---------
Signed-off-by: AI Agent <agent@penpot.app>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
The options stored in options-ref is a delay (lazy value). In
on-token-key-down, it was passed raw to next-focus-index without being
dereferenced first, causing count to be called on a JS object that does
not implement ICounted.
Fix: dereference the delay in on-token-key-down (matching the existing
pattern in on-key-down), and make next-focus-index itself also handle
delays defensively. Add unit tests covering the delay case.
This is a temporary workaround for penpot/penpot-mcp#27.
It adds a wait time before exports via the export_shape tool to account
for asynchronous updates in Penpot, increasing the likelihood of exporting
the fully updated state.
The root lock file not being present causes issues, because
the sub-project dependencies are managed by it.
The lack of inclusion of pnpm-lock.yaml was the root cause of #8829.
A dependency was updated incompatibly, breaking the release.
Since npm pack has a hard exclusion rule for pnpm-lock.yaml,
we include it under a different name and restore it at runtime.
If the MCP version (as given in mcp/package.json) does not match
the Penpot version (as given by penpot.version), display a warning
message in the plugin UI.
This is important for users running the local MCP server, as it
is a common failure mode to combine the MCP server with an
incompatible Penpot version.