12646 Commits

Author SHA1 Message Date
Aitor Moreno
1477758656 🐛 Fix pointer selection 2026-04-15 16:44:24 +02:00
Elena Torró
b442ca2209
Merge pull request #8988 from penpot/alotor-improve-token-set-change
🐛 Improve change token set performance
2026-04-15 16:23:55 +02:00
Aitor Moreno
424b689dca 🐛 Fix mixed fills issues 2026-04-15 14:32:32 +02:00
Aitor Moreno
77b4d07d1f 🐛 Fix v3 text styles not being applied when inc/dec value 2026-04-15 14:32:32 +02:00
Aitor Moreno
6fd264051a 🐛 Fix v2/v3 wrong styling 2026-04-15 14:32:32 +02:00
Andrey Antukh
8f30a95ca0 🐛 Guard against nil variant-data in typography-item
When d/seek finds no matching font variant (e.g. the variant-id stored
on the typography no longer exists in the font's variants list),
variant-data is nil and (:name nil) produces nil, resulting in a
malformed label like "14px | ". Fall back to "--" in that case.
2026-04-15 12:27:18 +02:00
Andrey Antukh
e8547ab6dd 🐛 Pass on-finish-drag to harmony-selector in colorpicker
Without this, dragging the value/opacity sliders in the harmony tab
never called on-finish-drag, so the undo transaction started by
on-start-drag was never committed.
2026-04-15 12:27:18 +02:00
Andrey Antukh
628ce604c5 ♻️ Convert colorpicker and its sub-components to modern rumext * format
slider-selector (slider_selector.cljs):
- Rename to slider-selector*
- Rename prop vertical? to is-vertical
- Remove prop reverse? entirely: it was never passed by any callsite,
  so the related reversal logic in calculate-pos and handler positioning
  is also removed as dead code

value-saturation-selector (ramp.cljs):
- Rename to value-saturation-selector*
- Update internal call site to [:> value-saturation-selector* ...]
- Update slider-selector call sites to [:> slider-selector* ...]

harmony-selector (harmony.cljs):
- Rename to harmony-selector*
- Update slider-selector call sites to [:> slider-selector* ...] with
  renamed is-vertical prop
- Remove stale duplicate :vertical true prop
- Fix spurious extra wrapping vector around the opacity slider in the
  when branch

hsva-selector (hsva.cljs):
- Rename to hsva-selector*
- Update all four slider-selector call sites to [:> slider-selector* ...]
- Remove no-op :reverse? false prop from the value slider

color-inputs (color_inputs.cljs):
- Rename to color-inputs*

colorpicker.cljs:
- Update :refer imports for color-inputs*, harmony-selector*,
  hsva-selector* and libraries*
- Update all corresponding call sites from [:& ...] to [:> ...]
2026-04-15 12:27:18 +02:00
Andrey Antukh
90d052464f ♻️ Convert text-palette components to modern * format
Convert typography-item, palette and text-palette to typography-item*,
palette* and text-palette* using {:keys [...]} destructuring. Rename
prop name-only? to is-name-only in typography-item*. Update internal
call sites to [:> ...] and update the :refer import in palette.cljs.
2026-04-15 12:27:18 +02:00
Andrey Antukh
fbee875d75 ♻️ Convert active-sessions to modern * component format
Convert active-sessions to active-sessions* (zero-prop component).
Update call site in right_header.cljs to use [:> ...] and update the
:refer import accordingly.
2026-04-15 12:27:18 +02:00
Andrey Antukh
bf7c12ae75 ♻️ Convert coordinates to modern * component format
Convert coordinates to coordinates* using {:keys [...]} destructuring
and rename prop colorpalette? to is-colorpalette. Update call site in
workspace.cljs to use [:> ...] with new prop name.
2026-04-15 12:27:18 +02:00
Andrey Antukh
175f122a0f ♻️ Convert viewport-scrollbars to modern * component format
Convert viewport-scrollbars to viewport-scrollbars* using {:keys [...]}
destructuring and update call sites in viewport.cljs and viewport_wasm.cljs
to use [:> ...].
2026-04-15 12:27:18 +02:00
Andrey Antukh
b2f4e90a79 ♻️ Convert shape-distance-segment to modern * component format
Convert shape-distance-segment to shape-distance-segment* using {:keys [...]}
destructuring and update its internal call site in shape-distance to use [:> ...].
2026-04-15 12:27:18 +02:00
Andrey Antukh
b4ec0a6d55 🐛 Add missing zoom and page-id dep on snap-feedback use-effect 2026-04-15 12:27:18 +02:00
alonso.torres
988c277e37 🐛 Post-review enhancements 2026-04-15 09:53:36 +02:00
alonso.torres
1d8299a919 🐛 Fix problem with component thumbnails 2026-04-15 09:53:36 +02:00
Andrey Antukh
f07b954b7e
Add efficiency improvements to workspace components (refactor part 1) (#8887)
* ♻️ Convert snap-points components to modern rumext format

Migrate snap-point, snap-line, snap-feedback, and snap-points from
legacy mf/defc format to modern * suffix format. This enables
optimized props handling by the rumext macro, eliminating implicit
JS object wrapping overhead on each render. All internal and
external call sites updated to use [:> component* props] syntax.

* ♻️ Convert frame-title to modern rumext format

Migrate frame-title from legacy mf/defc format to modern * suffix
format. The component was using legacy implicit props wrapping without
::mf/wrap-props false or ::mf/props :obj, causing unnecessary JS
object conversion overhead on each render. The parent frame-titles*
call site updated to use [:> frame-title* props] syntax.

* ♻️ Convert interactions components to modern rumext format

Migrate interaction-marker, interaction-path, interaction-handle,
overlay-marker, and interactions from legacy mf/defc format to modern
* suffix format. These five components had zero props optimization
applied, causing implicit JS object wrapping on every render. All
internal and external call sites updated to use [:> component* props]
syntax.

* ♻️ Convert rulers components to modern rumext format

Migrate rulers-text, viewport-frame, and selection-area from legacy
mf/defc format to modern * suffix format. These three components in
the always-visible rulers layer had zero props optimization applied.
Internal call sites in the parent rulers component updated to use
[:> component* props] syntax.

* ♻️ Convert frame-grid components to modern rumext format

Migrate square-grid, layout-grid, grid-display-frame, and frame-grid
from legacy mf/defc format to modern * suffix format. These four
components render grid patterns per-frame with zero props optimization.
All internal and external call sites updated to use [:> component* props]
syntax.

* ♻️ Convert gradient handler components to modern rumext format

Migrate shadow, gradient-color-handler, and gradient-handler-transformed
from legacy mf/defc format to modern * suffix format. These components
are rendered during gradient editing with zero props optimization applied.
Internal call sites in gradient-handler-transformed and
gradient-handlers-impl updated to use [:> component* props] syntax.

* ♻️ Rename ?-ending props in modernized workspace viewport components

Apply prop naming rules to all * components migrated in the previous batch:
- remove-snap? -> remove-snap in snap-feedback* (and get-snap helper)
- selected? -> is-selected in interaction-path*
- hover-disabled? -> is-hover-disabled in overlay-marker* and interactions*
- show-rulers? -> show-rulers in viewport-frame*

Update all internal and external call sites consistently.

* 🐛 Fix get-snap call in snap-feedback* using JS props object

Modern rumext *-suffix components receive props as JS objects, not
Clojure maps. snap-feedback* was pushing the raw props object into the
rx/subject and get-snap was destructuring it as a Clojure map, causing
all keys to resolve to nil.

Fix by:
- Changing get-snap to take positional arguments (coord, shapes,
  page-id, remove-snap, zoom) instead of a map-destructured opts arg
- Building an explicit Clojure map from the bound locals before pushing
  to the subject
- Destructuring that map inside the rx/switch-map callback and calling
  get-snap with positional args

Also mark get-snap and add-point-to-snaps as private (defn-), consistent
with the other helpers in the namespace — none are referenced externally.
2026-04-14 19:48:59 +02:00
alonso.torres
dc5f222230 🐛 Improve change token set performance 2026-04-14 14:51:12 +02:00
Andrey Antukh
3264bc746f 🔧 Backport ci configuration changes from develop 2026-04-14 12:33:10 +02:00
Andrey Antukh
bc47b992eb Merge remote-tracking branch 'origin/main' into staging 2026-04-13 18:31:32 +02:00
Elena Torro
6b3d5d930f 🔧 Improve zoom and pan performance 2026-04-13 16:35:36 +02:00
Elena Torro
d85d63ef3c 🔧 Improve page loading 2026-04-13 14:42:03 +02:00
Aitor Moreno
83e9f85ccf
Merge pull request #8943 from penpot/ladybenko-13949-fix-resize-call
🐛 Fix initializing guards in viewport loading
2026-04-13 13:37:25 +02:00
Aitor Moreno
9c44f5bf65 🐛 Fix text editor v1 focus not being handled correctly (#8942) 2026-04-13 12:08:06 +02:00
Eva Marco
443fb60743 🐛 Fix highlight on frames after rename (#8938) 2026-04-13 12:04:04 +02:00
Luis de Dios
cbe9d31599 🐛 Fix dashboard navigation tabs overlap with content when scrolling (#8937)
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-13 12:01:10 +02:00
Luis de Dios
599a66979a
🐛 Fix dashboard navigation tabs overlap with content when scrolling (#8937)
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-13 11:59:19 +02:00
Andrey Antukh
a403175d5c
🐛 Fix TypeError in sd-token-uuid when resolving tokens interactively (#8929)
The backtrace-tokens-tree function used a namespaced keyword :temp/id
which clj->js converted to the JS property "temp/id". The sd-token-uuid
function then tried to access .id on the sd-token top-level object,
which was undefined, causing "Cannot read properties of undefined
(reading uuid)".

Fix by using the existing token :id instead of generating a temporary
one, and read it from sd-token.original (matching sd-token-name pattern).
2026-04-13 11:34:15 +02:00
Aitor Moreno
bb85b312d6
🐛 Fix text editor v1 focus not being handled correctly (#8942) 2026-04-13 10:00:56 +02:00
Eva Marco
6d1a2d449a
🐛 Fix highlight on frames after rename (#8938) 2026-04-13 09:09:03 +02:00
Belén Albeza
eb811621a9 🐛 Fix initializing guards in viewport loading 2026-04-10 13:54:06 +02:00
Alejandro Alonso
5eebc17ce2 🎉 Support for debugging cache texture 2026-04-09 19:02:14 +02:00
Alejandro Alonso
434e27bbe8 🎉 Improve panning performance 2026-04-09 19:02:14 +02:00
Alejandro Alonso
5c67cd0a4b 🐛 Avoid unnecesary text editor pointer movements 2026-04-09 16:18:58 +02:00
Eva Marco
290f37425f
🐛 Fix id prop on switch component (#8915) 2026-04-09 12:35:34 +02:00
Andrey Antukh
ef39afe9b5 Merge remote-tracking branch 'origin/main' into staging 2026-04-09 12:24:18 +02:00
Eva Marco
b0a99b65e4
🐛 Fix highlight on shape after rename (#8890) 2026-04-09 11:27:36 +02:00
Andrey Antukh
388775413e 🐛 Fix path drawing preview passing shape instead of content to next-node
In `preview-next-point`, `st/get-path` was called without extra keys,
which returns the full Shape record. That value was then passed directly
to `path/next-node` as its `content` argument.

`path/next-node` delegates to `impl/path-data`, which only accepts a
`PathData` instance, `nil`, or a sequential collection of segments. A
Shape record matches none of those cases, so `path-data` threw
"unexpected data" every time the user moved the mouse while drawing a
path.

The fix is to call `(st/get-path state :content)` so that only the
`:content` field (a `PathData` instance) is extracted and forwarded to
`path/next-node`.
2026-04-09 09:21:57 +00:00
Aitor Moreno
38a5a67b86
Merge pull request #8912 from penpot/niwinz-main-text-editor-fixes
🐛 Fix TypeError when deleting text at edge spans in text editor
2026-04-09 10:53:07 +02:00
Alejandro Alonso
f8dd64611f
Merge pull request #8625 from penpot/azazeln28-apply-styles-to-selection
🎉 Feat apply styles to selection
2026-04-09 09:22:24 +02:00
Andrey Antukh
11fbd4cb21 Merge remote-tracking branch 'origin/main' into staging 2026-04-09 09:12:23 +02:00
Andrey Antukh
5a2c09f246 🐛 Fix TypeError when deleting text at edge spans in text editor
The text editor's SelectionController threw 'TypeError: Invalid text
node' when:

- Pressing Backspace to delete the only character of the **first** text
  span in a paragraph that contains multiple spans.
- Pressing Delete to delete the only character of the **last** text
  span in the same situation.
- Pressing a word-backward shortcut that empties the first span of a
  multi-span paragraph.

In all three cases the tree-iterator (previousNode / nextNode) returned
null because no sibling text node existed in that direction, and that
null was subsequently passed to getTextNodeLength() which calls
isTextNode() — which unconditionally throws when given a falsy value.

Fix: use the null-coalescing fallback to the first/last remaining
child's text node of the paragraph before calling collapse() /
getTextNodeLength().
2026-04-08 21:03:46 +02:00
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
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
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
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
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
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