21567 Commits

Author SHA1 Message Date
Alejandro Alonso
8f6133ddac
Merge pull request #8853 from penpot/alotor-performance-tokens
🐛 Fix problem with token performance
2026-04-08 18:15:26 +02:00
andrés gonzález
6063c1c532
📚Clarify remote MCP availability in production (#8910) 2026-04-08 17:48:05 +02:00
Andrey Antukh
ffac8d2861 📎 Update changelog 2.14.2 2026-04-08 17:34:00 +02:00
Andrey Antukh
f97df3e8ab 🐛 Fix PathData corruption root causes across WASM and CLJS
Replace unsafe std::mem::transmute calls in Rust WASM path code with
validated TryFrom conversions to prevent undefined behavior from invalid
enum discriminant values. This was the most likely root cause of the
"No matching clause: -19772" production crash -- corrupted bytes flowing
through transmute could produce arbitrary invalid enum variants.

Fix byteOffset handling throughout the CLJS PathData serialization
pipeline. DataView instances created via buf/slice carry a non-zero
byteOffset, but from-bytes, transit write handler, -write-to,
buf/clone, and buf/equals? all operated on the full underlying
ArrayBuffer, ignoring offset and length. This could silently produce
PathData with incorrect size or content.

Rust changes (render-wasm):
- RawSegmentData: From<[u8; N]> -> TryFrom<[u8; N]> with discriminant
  validation (must be 0x01-0x04) before transmuting
- RawBoolType: From<u8> -> TryFrom<u8> with explicit match on 0-3
- Add #[wasm_error] to set_shape_path_content, current_to_path,
  convert_stroke_to_path, and set_shape_bool_type so panics are caught
  and routed through the WASM error protocol instead of crashing
- set_shape_path_content: replace .expect() with proper Result/? error
  propagation per segment
- Remove unused From<BytesType> bound from SerializableResult trait

CLJS changes (common):
- from-bytes: use DataView.byteLength instead of ArrayBuffer.byteLength
  for DataView inputs; preserve byteOffset/byteLength when converting
  from Uint8Array, Uint32Array, and Int8Array
- Transit write handler: construct Uint8Array with byteOffset and
  byteLength from the DataView, not the full backing ArrayBuffer
- -write-to: same byteOffset/byteLength fix
- buf/clone: copy only the DataView byte range using Uint8Array with
  proper offset, not Uint32Array over the full ArrayBuffer
- buf/equals?: compare DataView byte ranges using Uint8Array with
  proper offset, not the full backing ArrayBuffers

Frontend changes:
- shape-to-path, stroke-to-path, calculate-bool*: wrap WASM call and
  buffer read in try/catch to ensure mem/free is always called, even
  when an exception occurs between the WASM call and the free call

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-08 17:24:09 +02:00
Andrey Antukh
b63e4a297b 🐛 Handle corrupted PathData segments gracefully instead of crashing
Add nil defaults to all case expressions that match binary segment
type codes so that corrupted/unknown values are skipped instead of
throwing 'No matching clause'. This prevents a React render crash
(triggered via shape-with-open-path? -> get-subpaths -> reduce)
when a PathData buffer contains invalid bytes, e.g. from a WASM
data transfer or deserialization of damaged stored data.

Affected functions: read-segment, impl-walk, impl-reduce,
impl-lookup, to-string-segment*, and the seq/reduce protocol
implementations on both JVM and CLJS PathData types.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-08 17:21:03 +02:00
Andrey Antukh
6a0d131715 🐛 Fix swapped move-to/line-to type codes in PathData binary readers
The impl-walk, impl-reduce, and impl-lookup functions had the binary
type codes for move-to and line-to swapped (1 mapped to :line-to and
2 to :move-to). This is inconsistent with from-plain, read-segment,
to-string-segment*, and the Rust RawSegmentData enum which all use
1=move-to and 2=line-to. The swap caused incorrect command types to
be reported to callers like get-handlers and get-points.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-08 17:20:47 +02:00
Andrey Antukh
92de9ed258 🐛 Fix PathData corruption root causes across WASM and CLJS
Replace unsafe std::mem::transmute calls in Rust WASM path code with
validated TryFrom conversions to prevent undefined behavior from invalid
enum discriminant values. This was the most likely root cause of the
"No matching clause: -19772" production crash -- corrupted bytes flowing
through transmute could produce arbitrary invalid enum variants.

Fix byteOffset handling throughout the CLJS PathData serialization
pipeline. DataView instances created via buf/slice carry a non-zero
byteOffset, but from-bytes, transit write handler, -write-to,
buf/clone, and buf/equals? all operated on the full underlying
ArrayBuffer, ignoring offset and length. This could silently produce
PathData with incorrect size or content.

Rust changes (render-wasm):
- RawSegmentData: From<[u8; N]> -> TryFrom<[u8; N]> with discriminant
  validation (must be 0x01-0x04) before transmuting
- RawBoolType: From<u8> -> TryFrom<u8> with explicit match on 0-3
- Add #[wasm_error] to set_shape_path_content, current_to_path,
  convert_stroke_to_path, and set_shape_bool_type so panics are caught
  and routed through the WASM error protocol instead of crashing
- set_shape_path_content: replace .expect() with proper Result/? error
  propagation per segment
- Remove unused From<BytesType> bound from SerializableResult trait

CLJS changes (common):
- from-bytes: use DataView.byteLength instead of ArrayBuffer.byteLength
  for DataView inputs; preserve byteOffset/byteLength when converting
  from Uint8Array, Uint32Array, and Int8Array
- Transit write handler: construct Uint8Array with byteOffset and
  byteLength from the DataView, not the full backing ArrayBuffer
- -write-to: same byteOffset/byteLength fix
- buf/clone: copy only the DataView byte range using Uint8Array with
  proper offset, not Uint32Array over the full ArrayBuffer
- buf/equals?: compare DataView byte ranges using Uint8Array with
  proper offset, not the full backing ArrayBuffers

Frontend changes:
- shape-to-path, stroke-to-path, calculate-bool*: wrap WASM call and
  buffer read in try/catch to ensure mem/free is always called, even
  when an exception occurs between the WASM call and the free call

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-08 17:14:18 +02:00
Andrey Antukh
2eaa2dc807 🐛 Handle corrupted PathData segments gracefully instead of crashing
Add nil defaults to all case expressions that match binary segment
type codes so that corrupted/unknown values are skipped instead of
throwing 'No matching clause'. This prevents a React render crash
(triggered via shape-with-open-path? -> get-subpaths -> reduce)
when a PathData buffer contains invalid bytes, e.g. from a WASM
data transfer or deserialization of damaged stored data.

Affected functions: read-segment, impl-walk, impl-reduce,
impl-lookup, to-string-segment*, and the seq/reduce protocol
implementations on both JVM and CLJS PathData types.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-08 17:14:18 +02:00
Andrey Antukh
0dfa450cc8 🐛 Fix swapped move-to/line-to type codes in PathData binary readers
The impl-walk, impl-reduce, and impl-lookup functions had the binary
type codes for move-to and line-to swapped (1 mapped to :line-to and
2 to :move-to). This is inconsistent with from-plain, read-segment,
to-string-segment*, and the Rust RawSegmentData enum which all use
1=move-to and 2=line-to. The swap caused incorrect command types to
be reported to callers like get-handlers and get-points.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-08 17:14:18 +02:00
Andrey Antukh
cb33fe417e
🐛 Fix non-integer row/column values in grid cell position inputs (#8869)
* 🐛 Fix non-integer row/column values in grid cell position inputs

The numeric-input component allows Alt+arrow key increments of 0.1x the
step value, which could produce float values (e.g. 4.5, 0.5) when users
adjusted grid cell row/column/row-span/column-span positions. The schema
requires these fields to be integers, causing backend validation errors.

Round the input values to integers in the on-grid-coordinates callback
before passing them to update-grid-cell-position.

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

* 🐛 Enforce integer-only values in grid cell numeric inputs

Add an `integer` prop to the legacy `numeric-input*` component that
rounds parsed values in `parse-value`, ensuring all input paths (typed
text, arrow keys, Alt+arrow, mouse wheel, expressions) produce integers.
Use it for all six row/column inputs in the grid cell options panel.

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

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-08 17:05:55 +02:00
Andrey Antukh
c8675c5b7e
♻️ Normalize newsletter-updates checbox on different register flows (#8839)
*  Add newsletter opt-in checkbox to registration validation form

Add accept-newsletter-updates support through the full registration
token flow. The newsletter checkbox is now available on the
registration validation form, allowing users to opt-in during the
email verification step.

Backend changes:
- Refactor prepare-register to consolidate UTM params and newsletter
  preference into props at token creation time
- Add accept-newsletter-updates to prepare-register-profile and
  register-profile schemas
- Handle newsletter-updates in register-profile by updating token
  claims props on second step

Frontend changes:
- Add newsletter-options component to register-validate-form
- Add accept-newsletter-updates to validation schema
- Fix subscription finalize/error handling in register form

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

* ♻️ Refactor auth register components to modern style

Migrate all components in app.main.ui.auth.register and
app.main.ui.auth.login/demo-warning to use the modern * suffix
convention, removing deprecated ::mf/props :obj metadata and
updating all invocations from [:& name] to [:> name*] syntax.

Components updated:
- terms-and-privacy -> terms-and-privacy*
- register-form -> register-form*
- register-methods -> register-methods*
- register-page -> register-page*
- register-success-page -> register-success-page*
- terms-register -> terms-register*
- register-validate-form -> register-validate-form*
- register-validate-page -> register-validate-page*
- demo-warning -> demo-warning*

Also remove unused old context-notification import in login.cljs.

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

* 🔥 Remove unused onboarding-newsletter component

The newsletter opt-in is now handled directly in the registration
form via the newsletter-options* component, making the standalone
onboarding-newsletter modal obsolete.

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

* 🐛 Fix register test for UTM params to use prepare-register step

UTM params are now extracted and stored in token props during the
prepare-register step, not at register-profile time. Move utm_campaign
and mtm_campaign from the register-profile call to the
prepare-register-profile call in the test.

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

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-08 17:00:52 +02:00
Alonso Torres
6ce2aadfae
Improve message from schema errors in plugins (#8865) 2026-04-08 16:14:51 +02:00
Dream
5502fe8df3
📚 Add changelog entry for undo/redo selection state (#8896) 2026-04-08 16:05:53 +02:00
Xaviju
10cfd99525
🐛 Fix lint invalid CSS props (#8907)
* 🐛 Fix lint invalid CSS props

* 🐛 Fix named colors in favor of modern notation or custom properties

* 🐛 Removed multiple combined selectors

* 🐛 Convert alpha value to numeric
2026-04-08 15:44:09 +02:00
Elena Torró
e8e7900911
Merge pull request #8904 from penpot/ladybenko-wasm-cleanup
 Explicitly call free_gpu_resources on RenderState drop
2026-04-08 12:13:08 +02:00
Belén Albeza
f6b8117fe9 Explicitly call free_gpu_resources on RenderState drop 2026-04-08 12:03:12 +02:00
Elena Torro
6d5b97a7e9 🔧 Fix text bounds 2026-04-08 11:16:09 +02:00
Eva Marco
b8be89f231
🐛 Update onboarding image (#8902) 2026-04-08 11:00:59 +02:00
alonso.torres
0b0e193b70 🐛 Fix problem with text auto grow in layouts 2026-04-08 10:21:32 +02:00
Aitor Moreno
d190655e64
Merge pull request #8841 from penpot/ladybenko-13861-modal-webgl-not-available
🎉 Show modal when WebGL is not available
2026-04-08 10:12:47 +02:00
Belén Albeza
619bc5833d 🔧 Remove VS Code settings 2026-04-08 09:59:44 +02:00
Andrey Antukh
40dfeb169c Merge remote-tracking branch 'origin/staging' into develop 2026-04-07 21:37:21 +02:00
Andrey Antukh
61d319eaac
⬆️ Update dependencies (#8867)
* ⬆️ Update deps

* ⬆️ Update storybook dependencies

* ⬆️ Update dependencies

* 🐛 Fix invalid var() usage on SCSS variable in numeric_input

* ⬆️ Update deps
2026-04-07 21:35:00 +02:00
Andrey Antukh
0cc5f7c63e Merge remote-tracking branch 'origin/staging' into develop 2026-04-07 19:28:23 +02:00
Andrey Antukh
a27ef26279 Merge remote-tracking branch 'origin/main' into staging 2026-04-07 19:23:37 +02:00
Andrey Antukh
f8c04949e1
🐛 Fix nil path content crash by exposing safe public API (#8806)
* 🐛 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.
2026-04-07 18:54:14 +02:00
Andrey Antukh
e10bd6a8d3
🐛 Fix infinite recursion in get-frame-ids for thumbnail extraction (#8807)
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.
2026-04-07 16:34:08 +02:00
Andrey Antukh
52f28a1eee 🐛 Fix stale-asset detector missing protocol-dispatch errors
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>
2026-04-07 16:33:40 +02:00
Andrey Antukh
9a0ae32488 ⬆️ Update opencode dependency on repo root 2026-04-07 16:33:40 +02:00
Dream
0c08dfb13d
Add the ability for save and restore selection state in undo/redo (#8652)
*  Capture selection state before changes are applied

Save current selection IDs in commit-changes so undo entries
can track what was selected before each action.

*  Save and restore selection state in undo/redo

Extend undo entry with selected-before and selected-after fields.
On undo, restore selection to what it was before the action.
On redo, restore selection to what it was after the action.
Handles single entries, stacked entries, accumulated transactions,
and undo groups.

Fixes #6007

* ♻️ Wire selected-before through workspace undo stream

Pass the captured selection state from commit data into
the undo entry so it is stored alongside changes.

* 🐛 Fix unmatched delimiter in changes.cljs

* 🐛 Pass selected-before through commit event to undo entry

selected-before was captured in commit-changes but dropped by the
commit function since it was missing from the destructuring and the
commit map. This caused restore-selection to receive nil on undo.

---------

Signed-off-by: eureka928 <meobius123@gmail.com>
Co-authored-by: Mihai <noreply@github.com>
2026-04-07 16:30:47 +02:00
Andrey Antukh
1e4ff4aa47
🐛 Ignore Zone.js toString TypeError in uncaught error handler (#8804)
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.
2026-04-07 16:25:57 +02:00
Andrey Antukh
b99157a246
🐛 Prevent thumbnail frame recursion overflow (#8763)
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>
2026-04-07 15:09:54 +02:00
Belén Albeza
0558bab092 🎉 Show modal when WebGL is not available 2026-04-07 14:55:57 +02:00
Eva Marco
48e8c0bc65
🐛 Fix show resolved value instead of value (#8883) 2026-04-07 14:27:26 +02:00
Pablo Alba
3c639f41c4
Add option to leave a nitrate organization 2026-04-07 11:26:57 +02:00
Eva Marco
a5055af538
🐛 Fix hidden on multiple selection (#8854) 2026-04-07 10:58:34 +02:00
Luis de Dios
e99b6ec213
🐛 Fix MCP active tab switching (#8856) 2026-04-07 10:58:04 +02:00
Eva Marco
67734c5835
🐛 Fix hover on layers (#8885) 2026-04-07 10:57:27 +02:00
Cheonji Kim
d5855f355f
📎 Fix typo in README.md for MCP server description (#8884)
Edited line 7(perfom -> perform)

Signed-off-by: Cheonji Kim <76100119+CheonjiKim@users.noreply.github.com>
2026-04-07 10:45:20 +02:00
Alejandro Alonso
83833896c9 Merge remote-tracking branch 'origin/staging' into develop 2026-04-07 10:18:35 +02:00
Alonso Torres
11d9c09a2e
🐛 Fix problem with dashboard thumbnails (#8862) 2026-04-07 10:10:08 +02:00
Aitor Moreno
101b2fe9e6 🎉 Add style data from text editor v3 2026-04-06 13:13:53 +02:00
Aitor Moreno
12382cfbb9 🎉 Feat apply styles to selection 2026-04-06 13:13:53 +02:00
Aitor Moreno
0f389fe3ad
Merge pull request #8881 from penpot/azazeln28-fix-text-editor-v2-tests
🐛 Fix text-editor v2 waitForIdle not having a timeout
2026-04-06 13:13:05 +02:00
Aitor Moreno
9aa2abff2e 🐛 Fix text-editor v2 waitForIdle not having a timeout 2026-04-06 12:37:57 +02:00
Aitor Moreno
4205e283ea
Merge pull request #8835 from penpot/superalex-fix-text-editor-mixed-selection-styles
🐛 Fix spurious mixed text styles on multi-span selection
2026-04-06 09:33:29 +02:00
Elena Torro
68760c8e26 🎉 Improve text inner stroke rendering 2026-04-02 12:00:08 +02:00
alonso.torres
cbe3a3f33e 🐛 Fix problem when changing grow-type 2026-04-02 11:44:41 +02:00
Andrey Antukh
2ca7acfca6
Add tests for app.common.geom and descendant namespaces (#8768)
* 🎉 Add tests for app.common.geom.bounds-map

* 🎉 Add tests for app.common.geom and descendant namespaces

* 📎 Fix linting issues

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
2026-04-02 09:50:34 +02:00
Andrey Antukh
d2a3b67053
🎉 Add additional tests for app.common.types.shape.interactions (#8765)
*  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.
2026-04-02 09:50:08 +02:00