Commit Graph

20659 Commits

Author SHA1 Message Date
Penpot Dev
0ad3ae0620 📚 Add explicit commit guideline to builtin agents 2026-03-26 14:42:22 +01:00
Andrey Antukh
3eaf67a385 🐛 Fix fetch abort errors escaping the unhandled exception handler (#8801)
When AbortController.abort(reason) is called with a custom reason (a
ClojureScript ExceptionInfo), modern browsers (Chrome 98+, Firefox 97+)
reject the fetch promise with that reason object directly instead of with
the canonical DOMException{name:'AbortError'}.  The ExceptionInfo has
.name === 'Error', so both the p/catch guard and is-ignorable-exception?
failed to recognise it as an abort, letting it surface to users as an
error toast.

Fix by calling .abort() without a reason so the browser always produces
a native DOMException whose .name is 'AbortError', which is correctly
handled by all existing guards.

Also add a defense-in-depth check in is-ignorable-exception? that
filters errors whose message matches the 'fetch to \'' prefix, guarding
against any future re-introduction of a custom abort reason.

Co-authored-by: Penpot Dev <dev@penpot.app>
2026-03-26 14:13:38 +01:00
Andrey Antukh
1a4ca6d04b 📚 Update frontend/AGENTS.md file 2026-03-26 14:12:11 +01:00
Penpot Dev
945efdb0b4 🔥 Remove .opencode/skills
I think they make ai agent work worse.
2026-03-26 13:09:31 +01:00
Penpot Dev
2ba3605f11 ⬆️ Update root repo deps 2026-03-26 13:09:31 +01:00
Andrey Antukh
5fca9457cf ♻️ Extract use-portal-container hook to reduce duplication (#8798)
The dedicated-container portal pattern was repeated across 6 components.
Extract it into a reusable use-portal-container hook under app.main.ui.hooks.
2026-03-26 12:45:42 +01:00
Alejandro Alonso
85cfb8161a 📎 Rename skills 2026-03-25 23:50:29 +01:00
Alejandro Alonso
a34a668f94 📎 Add opencode skills 2026-03-25 23:48:18 +01:00
andrés gonzález
f01bfb7a26 🐛 Adding missing images to mcp doc (#8782) 2026-03-25 16:30:47 +01:00
Andrey Antukh
737e04fe2c 🐛 Fix nil deref on missing bounds in layout modifier propagation (#8735)
* 🐛 Fix nil deref on missing bounds in layout modifier propagation

When a parent shape has a child ID in its shapes vector that does
not exist in the objects map, the layout modifier code crashes
because it derefs nil from the bounds map.

The root cause is that children from the parent shapes list are
not validated against the objects map before being passed to the
layout modifier pipeline. Children with missing IDs pass through
unchecked and reach apply-modifiers where bounds lookup fails.

Fix by adding nil guards in apply-modifiers to skip children
without bounds, and changing map to keep to filter them out.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

* 📎 Add tests for nil bounds in layout modifier propagation

Tests cover flex and grid layout scenarios where a parent
frame has child IDs in its shapes vector that do not exist
in the objects map, verifying that set-objects-modifiers
handles these gracefully without crashing.

Tests:
- Flex layout with normal children (baseline)
- Flex layout with non-existent child in shapes
- Flex layout with only non-existent children
- Grid layout with non-existent child in shapes
- Flex layout resize propagation with ghost children
- Nested flex layout with non-existent child in outer frame

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-25 15:36:21 +01:00
andrés gonzález
38bf6c3603 📚 Add MCP docs (#8772) 2026-03-25 15:32:24 +01:00
Andrey Antukh
a2672a598c 🐛 Fix TypeError when token error map lacks :error/fn key (#8767)
* 🐛 Fix TypeError when token error map lacks :error/fn key

Guard against missing :error/fn in token form control resolve streams.
When schema validation errors are produced they may not carry an
:error/fn key; calling nil as a function caused a TypeError crash.
Apply an if-let guard at all 7 affected sites across input.cljs,
color_input.cljs and fonts_combobox.cljs, falling back to :message
or returning the error map unchanged.

* ♻️ Extract token error helpers and add unit tests

Extract resolve-error-message and resolve-error-assoc-message helpers
into errors.cljs, replacing the seven duplicated inline lambdas in
input.cljs, color_input.cljs and fonts_combobox.cljs with named
function references.  Add frontend-tests.tokens.token-errors-test
covering both helpers for the normal path (:error/fn present) and the
fallback path (schema-validation errors that lack :error/fn).

Signed-off-by: Penpot Dev <dev@penpot.app>

---------

Signed-off-by: Penpot Dev <dev@penpot.app>
2.14.1-RC1
2026-03-25 12:12:18 +01:00
Andrey Antukh
0dfac801a4 Improve error handling and exception formatting (#8757)
*  Improve error handling and exception formatting

- Enhance exception formatting with visual separators and cause chaining
- Add new handler for :internal error type
- Refine error types: change assertion-related errors to :assertion type
- Improve error messages and hints consistency
- Clean up error handling in zip utilities and HTTP modules

* 🐛 Properly handle AbortError on fetch request unsubscription

When a fetch request in-flight is cancelled due to RxJS unsubscription
(e.g. navigating away from the workspace while thumbnail loads are
pending), the AbortController.abort() call triggers a catch handler
that previously relied solely on a @unsubscribed? flag to suppress the
error.

This was unreliable: nested observables spawned inside rx/mapcat (such
as datauri->blob-uri conversions within get-file-object-thumbnails)
could abort independently, with their own AbortController instances,
meaning the outer unsubscribed? flag was never set and the AbortError
propagated as an unhandled exception.

Add an explicit AbortError name check as a disjunctive condition so
that abort errors originating from any observable in the chain are
suppressed at the source, regardless of subscription state.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-24 19:55:23 +01:00
Andrey Antukh
cc73a768d5 Add comprehensive tests for path and descendant namespaces (#8755)
Add tests for app.common.types.path.subpath, helpers, segment,
bool operations (union/difference/intersection/exclude), top-level
path API, and shape-to-path conversion. Covers previously untested
functions across all path sub-namespaces. Tests pass on both JVM
and JS (ClojureScript/Node) platforms.
2026-03-24 19:53:22 +01:00
Andrey Antukh
3ef100427b 🎉 Add tests for app.common.data namespace (#8750)
*  Add tests for predicates and ordered data structures

Adds tests for boolean-or-nil?, in-range?, ordered-set/map creation
and ordering, oassoc/oassoc-in/oupdate-in/oassoc-before, and the
ordered collection index helpers (adds/inserts/addm/insertm-at-index).

*  Add tests for lazy and sequence helpers

Adds tests for concat-all, mapcat, zip, zip-all, enumerate,
interleave-all, add-at-index, take-until, safe-subvec and domap.

*  Add tests for collection lookup and map manipulation

Adds tests for group-by, seek, index-by, index-of-pred/of,
replace-by-id, getf, vec-without-nils, without-nils,
without-qualified, without-keys, deep-merge, dissoc-in, patch-object,
without-obj, update-vals, update-in-when, update-when, assoc-in-when,
assoc-when, merge, txt-merge, mapm, removev, filterm, removem,
map-perm, distinct-xf and deep-mapm.

*  Add tests for parsing, numeric and utility helpers

Adds tests for nan?, safe+, max, min, parse-integer, parse-double,
parse-uuid, coalesce-str, coalesce, read-string, name, prefix-keyword,
kebab-keys, regexp?, nilf, nilv, any-key?, tap, tap-r, map-diff,
unique-name, toggle-selection, invert-map, obfuscate-string,
unstable-sort, opacity-to-hex, format-precision, format-number
and append-class.

*  Add tests for remaining untested helpers in data ns

Cover percent?, parse-percent, num-string?, num?, not-empty?,
editable-collection?, oreorder-before, oassoc-in-before,
lazy-map and reorder.

Platform-specific assertions use reader conditionals where
CLJS and JVM behaviour differ (js/isFinite string coercion,
js/isNaN empty-string coercion).
2026-03-24 19:52:52 +01:00
Andrey Antukh
7461c5304c Add comprehensive tests for app.common.colors ns (#8758)
Cover all public functions: valid-hex-color?, parse-rgb,
valid-rgb-color?, rgb->str, hex->rgb, rgb->hex, rgb->hsv,
hsv->rgb, rgb->hsl, hsl->rgb, hex->hsl, hex->hsv, hex->rgba,
hex->hsla, hex->lum, hsl->hex, hsl->hsv, hsv->hex, hsv->hsl,
format-hsla, format-rgba, expand-hex, prepend-hash, remove-hash,
color-string?, parse, next-rgb, reduce-range, interpolate-color,
uniform-spread, uniform-spread? and interpolate-gradient.

Tests pass on both JVM and JS (ClojureScript) platforms.
Platform differences (NaN saturation for achromatic colors,
integer vs float return types) are handled with mth/close?.
2026-03-24 19:10:44 +01:00
Andrey Antukh
0f19bc02d7 📎 Add testing engineer agent (opencode) 2026-03-24 18:49:30 +01:00
Andrey Antukh
edfa437ce7 📚 Improve CONTRIBUTING.md file 2026-03-24 18:18:38 +01:00
Andrey Antukh
cc03f3f884 📚 Add minor improvements to ai agents documentation 2026-03-24 18:00:39 +01:00
Andrey Antukh
750e8a9d51 🐛 Fix dissoc error when detaching stroke color from library (#8738)
* 🐛 Fix dissoc error when detaching stroke color from library

The detach-value function in color-row was only passing index to
on-detach, but the stroke's on-color-detach handler expects both
index and color arguments. This caused a protocol error when trying
to dissoc from a number instead of a map.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

* 🐛 Fix crash when detaching color asset from stroke

The color_row detach-value callback calls on-detach with (index, color),
but stroke_row's local on-color-detach wrapper only took a single argument
(fn [color] ...), so it received index as color and passed it to
stroke.cljs which then called (dissoc index :ref-id :ref-file), crashing
with 'No protocol method IMap.-dissoc defined for type number'.

Fix the wrapper to accept (fn [_ color] ...) so it correctly ignores the
index passed by color_row (it already has index in the closure) and
forwards the actual color map to the parent handler.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-24 15:35:32 +01:00
Alejandro Alonso
f88e287357 Merge pull request #8726 from penpot/niwinz-main-bugfix-1
🐛 Fix null text crash on paste in text editor
2026-03-24 15:19:41 +01:00
Andrey Antukh
56f1fcdb53 🐛 Fix crash when pasting image into text editor
When pasting an image (with no text content) into the text editor,
Draft.js calls handlePastedText with null/empty text. The previous fix
guarded splitTextIntoTextBlocks against null, but insertText still
attempted to build a fragment from an empty block array, causing
Modifier.replaceWithFragment to crash with 'Cannot read properties of
undefined (reading getLength)'.

Fix insertText to return the original state unchanged when there are no
text blocks to insert. Also guard handle-pasted-text in the ClojureScript
editor to skip the insert-text call entirely when text is nil or empty.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-24 13:00:28 +00:00
Andrey Antukh
d863c7065f 🐛 Fix null text crash on paste in text editor
The splitTextIntoTextBlocks function in @penpot/draft-js called
.split() on the text parameter without a null check. When pasting
content without text data (e.g., images only), Draft.js passes null
to handlePastedText, causing a TypeError.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-24 13:49:28 +01:00
Andrey Antukh
8729fed724 📎 Add opencode and copilot deps on root package.json 2026-03-24 12:52:56 +01:00
Alejandro Alonso
5d6eb3b3d6 Merge pull request #8739 from penpot/niwinz-main-bugfix-5
🐛 Fix error when get-parent-with-data encounters non-Element nodes
2026-03-24 12:50:48 +01:00
Alejandro Alonso
3abd63c35a Merge pull request #8740 from penpot/niwinz-main-bugfix-7
🐛 Ensure path content is always PathData when saving
2026-03-24 12:44:51 +01:00
Andrey Antukh
13b5c96a42 📎 Update changelog 2.14.0 2026-03-24 09:19:58 +01:00
Andrey Antukh
d051a3ba45 🐛 Ensure path content is always PathData when saving
The save-path-content function only converted content to PathData when
there was a trailing :move-to command. When there was no trailing
:move-to, the content from get-path was stored as-is, which could be
a plain vector if the shape was already a :path type with non-PathData
content. This caused segment/get-points to fail with 'can't access
property "get", cache is undefined' when the with-cache macro tried
to access the cache field on a non-PathData object.

The fix ensures content is always converted to PathData via path/content
before being stored in the state.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-24 08:15:58 +01:00
Andrey Antukh
577f00dd24 🐛 Fix error when get-parent-with-data encounters non-Element nodes
The get-parent-with-data function traverses the DOM using parentElement
to find an ancestor with a specific data-* attribute. When the current
node is a non-Element DOM node (e.g. Document node reached from event
handlers on window), accessing .-dataset returns undefined, causing
obj/in? to throw "right-hand side of 'in' should be an object".

This adds a nodeType check to skip non-Element nodes during traversal
and continue up the parent chain.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-23 19:19:27 +00:00
Andrey Antukh
b484415a9f 🐛 Fix generic error shown on clipboard permission denial (#8666)
When the browser denies clipboard read permission (NotAllowedError),
the unhandled exception handler was showing a generic 'Something wrong
has happened' toast. This change adds proper error handling for
clipboard permission errors in paste operations and shows a
user-friendly warning message instead.

Changes:
- Add error handling in paste-from-clipboard for NotAllowedError
- Improve error handling in paste-selected-props to detect permission errors
- Mark clipboard NotAllowedError as ignorable in the uncaught error handler
  to prevent duplicate generic error toasts
- Add translation key for clipboard permission denied message

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-23 20:03:14 +01:00
Andrey Antukh
2d616cf9c0 📚 Add better organization for AGENTS.md file (#8675) 2026-03-18 14:59:38 +01:00
Andrey Antukh
1a59017e1c 🐛 Ignore posthog exceptions in unhandled exception handler (#8629)
PostHog recorder throws errors like 'Cannot assign to read only property
'assert' of object' which are unrelated to the application and should be
ignored to prevent noise in error reporting.
2.14.0-RC5
2026-03-17 18:41:06 +01:00
Elena Torró
c6f8356847 📚 Improve docs on feature flags (#8641) 2026-03-17 11:49:14 +01:00
Andrey Antukh
e730e9ee64 🐛 Fix subscribe to undefined stream error in use-stream hook (#8633)
Add a nil guard before subscribing to the stream in the use-stream
hook. When a nil/undefined stream is passed (e.g., from a conditional
expression or timing edge case during React rendering), the subscribe
call on undefined causes a TypeError. The guard ensures we only
subscribe when the stream is defined.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-17 10:06:16 +01:00
Andrey Antukh
0779c9ca61 🐛 Fix TypeError in get-points when content is not PathData (#8634)
The with-cache macro in impl.cljc assumed the target was always a
PathData instance (which has a cache field). When content was a plain
vector, (.-cache content) returned undefined in JS, causing:

  TypeError: Cannot read properties of undefined (reading 'get')

Fix:
- path/get-points (app.common.types.path) is now the canonical safe
  entry point: converts non-PathData content via impl/path-data and
  handles nil safely before delegating to segment/get-points
- segment/get-points remains a low-level function that expects a
  PathData instance (no defensive logic at that level)
- streams.cljs: replace direct call to path.segm/get-points with
  path/get-points so the safe conversion path is always used
- with-cache macro: guards against nil/undefined cache, falling back
  to direct evaluation for non-PathData targets

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-17 09:31:10 +01:00
andrés gonzález
efd6d19a12 📚 Remove link to sales form from the Help Center (#8643) 2026-03-16 16:58:08 +01:00
Elena Torró
ac69f28a0a Merge pull request #8646 from penpot/elenatorro-include-wasm-share-on-gitignore
🔧 Ignore render_wasm shared.js autogenerated file
2026-03-16 13:04:25 +01:00
Elena Torro
ff1ba6b953 🔧 Ignore render_wasm shared.js autogenerated file 2026-03-16 12:09:54 +01:00
Andrey Antukh
a528508751 📚 Update AGENTS.md 2026-03-13 12:57:22 +01:00
Andrey Antukh
5d931d1614 Merge remote-tracking branch 'origin/main' into staging 2.14.0-RC4 2026-03-13 09:20:30 +01:00
Andrey Antukh
33c5f82c43 🐛 Fix penpot.openPage() to navigate in same tab by default
- Change the default for the newWindow param from true to false, so
  openPage() navigates in the same tab instead of opening a new one
- Accept a UUID string as the page argument in addition to a Page object,
  avoiding the need to call penpot.getPage(uuid) first
- Add validation error when an invalid page argument is passed

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-13 09:19:50 +01:00
Alejandro Alonso
ebd17974a6 Merge pull request #8580 from penpot/niwinz-staging-fix-paste-issue
🐛 Fix crash when pasting non-map transit clipboard data
2026-03-13 08:33:19 +01:00
Andrey Antukh
eecb51ecc1 🐛 Fix clipboard getType error when no allowed types found (#8609)
When clipboard items have types that don't match the allowed types
list, the filtering results in an empty array. Calling getType with
undefined throws a NotFoundError. This change adds a check for null/undefined
types and filters them from the result.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-13 08:24:36 +01:00
Andrey Antukh
e7e6303184 🐛 Make ct/format-inst nil safe (#8612)
Prevent JS TypeError when date is nil in date formatting.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 19:55:51 +01:00
Andrey Antukh
25df9f2f83 🐛 Fix DataCloneError in plugin postMessage communication
Fixes a crash where plugins sending messages via 'penpot.ui.sendMessage()'
could fail if their message payload contained non-serializable values like
functions or closures.

The fix adds validation using 'structuredClone()' to catch these messages
early with a helpful error message, and adds a defensive try/catch in the
modal's message handler as a safety net.

Fixes the error: 'Failed to execute postMessage on Window: ... could not
be cloned.'

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 15:26:00 +01:00
Andrey Antukh
8d5450391e 🐛 Fix crash when pasting non-map transit clipboard data
Guard against transit-decoded clipboard content that is not a map
before calling assoc, which caused a runtime crash ('No protocol
method IAssociative.-assoc defined for type number').

Also route :copied-props paste data to paste-transit-props instead
of incorrectly sending it to paste-transit-shapes.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 15:05:22 +01:00
Alejandro Alonso
be9b1158ed Merge pull request #8588 from penpot/niwinz-staging-abort-signal-fix
🐛 Fix unhandled AbortError in HTTP fetch requests
2026-03-12 13:53:18 +01:00
Eva Marco
8f5c38d476 🐛 Fix scroll on colorpicker (#8595) 2026-03-12 13:36:38 +01:00
Andrey Antukh
80d165ed5b 🐛 Fix unhandled AbortError in HTTP fetch requests
Identify and silence "signal is aborted without reason" errors by:
- Providing an explicit reason to AbortController when subscriptions are disposed.
- Updating the global error handler to ignore AbortError exceptions.
- Ensuring unhandled rejections use the ignorable exception filter.

The root cause was RxJS disposal calling .abort() without a reason, combined
with the on-unhandled-rejection handler missing the ignorable error filter.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-12 13:31:58 +01:00
Alejandro Alonso
4b330e7b50 Merge pull request #8596 from penpot/niwinz-staging-fix-max-recursion
🐛 Fix RangeError (stack overflow) in find-component-main
2026-03-12 13:30:16 +01:00