20727 Commits

Author SHA1 Message Date
Andrey Antukh
bba3610b7b ♻️ Rename shadowed 'fn' parameter to 'pred' in removev
The removev function used 'fn' as its predicate parameter name,
which shadows clojure.core/fn. Rename to 'pred' for clarity and
to follow the naming convention used elsewhere in the namespace.
2026-04-15 11:42:49 +02:00
Andrey Antukh
83da487b24 🐛 Fix append-class producing leading space for empty class
When called with an empty string as the base class, append-class
was producing " bar" (with a leading space) because (some? "")
returns true. Use (seq class) instead to treat both nil and empty
string as absent, avoiding invalid CSS class strings with leading
whitespace.
2026-04-15 11:42:49 +02:00
Andrey Antukh
da8e44147c Remove redundant str call in format-number
format-precision already returns a string, so wrapping its result
in an additional (str ...) call was unnecessary.
2026-04-15 11:42:49 +02:00
Andrey Antukh
69e25a4998 📚 Fix typo in namespace docstring ('if' -> 'of') 2026-04-15 11:42:49 +02:00
Andrey Antukh
eca9b63d68 Remove redundant map lookups in map-diff
The :else branch of diff-attr was calling (get m1 key) and
(get m2 key) again, but v1 and v2 were already bound to those
exact values. Reuse the existing bindings to avoid the extra
lookups.
2026-04-15 11:42:49 +02:00
Andrey Antukh
29ea1cc495 📚 Fix misleading without-obj docstring
The docstring claimed the function removes nil values in addition to
the specified object, but the implementation only removes elements
equal to the given object. Fix the docstring in both data.cljc and
the local copy in files/changes.cljc.
2026-04-15 11:42:49 +02:00
Andrey Antukh
d73ab3ec92 🐛 Fix safe-subvec 3-arity evaluating (count v) before nil check
The 3-arity of safe-subvec called (count v) in a let binding before
checking (some? v). While (count nil) returns 0 in Clojure and does
not crash, the nil guard was dead code. Restructure to check (some? v)
first with an outer when, then compute size inside the guarded block.
2026-04-15 11:42:49 +02:00
Andrey Antukh
1cc860807e Use seq/next idiom in enumerate instead of empty?/rest
Replace (empty? items) + (rest items) with (seq items) + (next items)
in enumerate. The seq/next pattern is idiomatic Clojure and avoids
the overhead of empty? which internally calls seq and then negates.
2026-04-15 11:42:49 +02:00
Andrey Antukh
92dd5d9954 🐛 Fix index-of-pred early termination on nil elements
The index-of-pred function used (nil? c) to detect end-of-collection,
which caused premature termination when the collection contained nil
values. Rewrite using (seq coll) / (next s) pattern to correctly
distinguish between nil elements and end-of-sequence.
2026-04-15 11:42:49 +02:00
Andrey Antukh
057c6ddc0d 🐛 Fix deep-mapm double-applying mfn on leaf entries
The deep-mapm function was applying the mapping function twice on
leaf entries (non-map, non-vector values): once when destructuring
the entry, and again on the already-transformed result in the else
branch. Now mfn is applied exactly once per entry.
2026-04-15 11:42:49 +02:00
Andrey Antukh
a2e6abcb72 🐛 Fix spurious argument to dissoc in patch-object
The patch-object function was calling (dissoc object key value) when
handling nil values. Since dissoc treats each argument after the map
as a key to remove, this was also removing nil as a key from the map.
The correct call is (dissoc object key).
2026-04-15 11:42:49 +02:00
Andrey Antukh
6c90ba1582 🐛 Fix move-files allowing same project as target when multiple files selected
The 'Move to' menu in the dashboard file context menu only filtered
out the first selected file's project from the available target list.
When multiple files from different projects were selected, the other
files' projects still appeared as valid targets, causing a 400
'cant-move-to-same-project' backend error.

Now all selected files' project IDs are collected and excluded from
the available target projects.
2026-04-14 15:19:19 +02:00
Andrey Antukh
18f0ad246f 🐛 Fix parse-long crash when index query param is duplicated in URL
lambdaisland/uri's query-string->map uses :multikeys :duplicates by
default: a key that appears once yields a plain string, but the same
key repeated yields a vector. cljs.core/parse-long only accepts
strings and therefore threw "Expected string, got: object" whenever
a URL contained a duplicate 'index' parameter.

Add rt/get-query-param to app.main.router. The helper returns the
scalar value of a query param key, taking the last element when the
value is a sequential (i.e. the key was repeated). Use it at every
call site that feeds a query-param value into parse-long, in both
app.main.ui (page*) and app.main.data.viewer.
2026-04-14 15:16:04 +02:00
Andrey Antukh
62f3454607 🔧 Backport ci configuration changes from develop 2026-04-14 12:34:04 +02:00
Andrey Antukh
c39609b991
♻️ Use shared singleton containers for React portals (#8957)
Refactor use-portal-container to allocate one persistent <div> per
logical category (:modal, :popup, :tooltip, :default) instead of
creating a new div for every component instance. This keeps the DOM
clean with at most four fixed portal containers and eliminates the
arbitrary growth of empty <div> elements on document.body while
preserving the removeChild race condition fix.
2026-04-14 10:48:30 +02:00
Andrey Antukh
b3645658fb
Merge pull request #8963 from penpot/raguirref-fix/builder-bool-media-validation
🐛 Fix builder bool styles and media validation
2026-04-13 18:35:12 +02:00
Andrey Antukh
e46b34efc7 📎 Fix formatting issues 2026-04-13 15:41:38 +02:00
raguirref
94c6045dd9 🔥 Remove accidental dev_server.pid
Remove unrelated local pid file that was accidentally included in previous commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com>
2026-04-13 15:40:40 +02:00
raguirref
f656266e5c Fix builder bool and media handling
Fixes three concrete builder issues in common/files/builder:\n- Use bool type from shape when selecting style source for difference bools\n- Persist :strokes correctly (fix typo :stroks)\n- Validate add-file-media params after assigning default id\n\nAlso adds regression tests in common-tests.files-builder-test and registers them in runner.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com>
2026-04-13 15:40:40 +02:00
Andrey Antukh
0fc2050526 ⬆️ Update deps on root package.json 2026-04-13 15:00:47 +02:00
Andrey Antukh
28f65fec91 📚 Update changelog 2026-04-13 12:15:17 +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
Andrey Antukh
d6045c80a1 💄 Fix docstrings and clarify filter expression in path namespaces
- Fix 'conten' typo to 'content' in path.cljc docstring
- Fix 'curvle' typo to 'curve' in shape_to_path.cljc docstring
- Replace confusing XOR-style filter with readable
  (contains? #{:line-to :curve-to} ...) in bool.cljc
- Align handler-indices and opposite-index docstrings with
  matching API in path.cljc
2026-04-13 11:48:30 +02:00
Andrey Antukh
8d1906f56e 🐛 Fix ^:cosnt typo to ^:const on bool-group-style-properties
The metadata key was misspelled as :cosnt instead of :const,
preventing the compiler from recognizing the Var as a compile-time
constant.
2026-04-13 11:48:30 +02:00
Andrey Antukh
2eaf117b56 🐛 Fix swapped arguments in CLJS PathData -nth with default
The CLJS implementation of PathData's -nth protocol method had
swapped arguments in the 3-arity version (with default value).
The call (d/in-range? i size) should be (d/in-range? size i)
to match the CLJ implementation. With swapped args, valid indices
always returned the default value, and invalid indices attempted
out-of-bounds buffer reads.
2026-04-13 11:48:30 +02:00
Andrey Antukh
e511576f66 🐛 Normalize PathData coordinates to safe integer bounds on read
Add normalize-coord helper function that clamps coordinate values to
max-safe-int and min-safe-int bounds when reading segments from PathData
binary buffer. Applies normalization to read-segment, impl-walk,
impl-reduce, and impl-lookup functions to ensure coordinates remain
within safe bounds.

Add corresponding test to verify out-of-bounds coordinates are properly
clamped when reading PathData.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-04-13 11:48:30 +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
Pablo Alba
ef6eeb5693
🐛 Fix variants corner cases with selrect and points (#8882)
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-10 11:23:03 +02:00
Marek Hrabe
9785a13e67
🐛 Add webp export format to plugin types (#8870)
* 🐛 Add webp export format to plugin types

Align plugin API typings with runtime export support by including 'webp' in
'Export.type' and updating the exported formats documentation.

Signed-off-by: Marek Hrabe <marekhrabe@me.com>

* 📚 Add plugin-types changelog entry for missing webp export format

Signed-off-by: Marek Hrabe <marekhrabe@me.com>

---------

Signed-off-by: Marek Hrabe <marekhrabe@me.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-04-10 11:22:20 +02:00
Elena Torró
a88f8f1394
Merge pull request #8918 from penpot/niwinz-main-path-preview-issue
🐛 Fix path drawing preview passing shape instead of content to next-node
2.14.3-RC1
2026-04-09 11:48:58 +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
Andrey Antukh
dfa45ec8d8 ⬆️ Update deps on root package.json 2026-04-09 09:10:44 +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
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
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
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
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
Andrey Antukh
3ff1acfb6a
🐛 Fix vector index out of bounds in viewer zoom-to-fit/fill (#8834)
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.
2026-04-02 09:49:33 +02:00