* 🐛 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>
* ✨ 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>
* ✨ 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>
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>
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>
* ✨ Add copy and paste for grid layout rows and columns via context menu
* 🔧 Use grid-id instead of grid in context menu deps
---------
Co-authored-by: bittoby <bittoby@users.noreply.github.com>
* ✨ Use update-when for update dashboard state
This make updates more consistent and reduces possible eventual
consistency issues in out of order events execution.
* 🐛 Detect stale JS modules at boot and force reload
When the browser serves cached JS files from a previous deployment
alongside a fresh index.html, code-split modules reference keyword
constants that do not exist in the stale shared.js, causing TypeError
crashes.
This adds a compile-time version tag (via goog-define / closure-defines)
that is baked into the JS bundle. At boot, it is compared against the
runtime version tag from index.html (which is always fresh due to
no-cache headers). If they differ, the app forces a hard page reload
before initializing, ensuring all JS modules come from the same build.
* 📎 Ensure consistent version across builds on github e2e test workflow
---------
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ✨ Make the MCP plugin switching between tabs work correctly
* 🎉 Show notification when the plugin is loaded in another tab
* 📎 PR changes
* ✨ Add events
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>