3450 Commits

Author SHA1 Message Date
Andrey Antukh
c8691d0cde
🐛 Fix crash when a nil shape id reaches WASM modifiers (#10896)
* 🐛 Fix crash when a nil shape id reaches WASM modifiers

A modif-tree containing a nil shape id crashed the WASM heap write
with "Cannot read properties of null (reading '__u32_buffer')" in
propagate-modifiers. Production builds elide the upstream asserts,
so a nil id (e.g. update-dimensions called with a missing shape's
parent id in the variants flow) reached the uuid heap write.

Drop nil-keyed modif-tree entries in apply-wasm-modifiers and
set-wasm-modifiers before they reach propagate-modifiers and
set-structure-modifiers, filter nil ids at the parse level, and
guard the variant reposition/creation events against missing shapes.

AI-assisted-by: hy3

* 🐛 Fix let scoping bugs in modifiers.cljs

Two let forms in set-wasm-modifiers and apply-wasm-modifiers
had their bindings vectors closed prematurely, causing undeclared
var warnings for snap-pixel?, translation?, ids, update-shape,
options, bool-ids, undo-id, and other bindings.

In set-wasm-modifiers: removed premature ) after ] on the let
bindings so the let body wraps snap-pixel? and translation?.

In apply-wasm-modifiers: the cond form was not explicitly closed,
so all subsequent bindings (ignore-tree, options, modif-tree, ids,
update-shape, bool-ids, undo-id) were parsed as cond clauses
instead of let bindings. Added ) to close cond after :else, and
moved ] to close the bindings vector after undo-id.

AI-assisted-by: mimo-v2.5-pro
2026-09-07 08:34:06 +02:00
Eva Marco
df48c834e3
🐛 Fix create nil typography token (#11489)
* 🐛 Fix stroke width token dropdown closing mid-interaction

The stroke-row* key included a hash of applied-tokens, forcing a full
remount whenever the async token-resolution pipeline updated that
state (e.g. after a detach action settles). If the remount landed
while the width dropdown was open, it destroyed the dropdown and
reset its open state, permanently closing it before the user could
pick a token.

Drop the hash from the key so the row only remounts on actual
structural changes (add/remove/reorder), not on every token
resolution tick.

AI-assisted-by: claude-sonnet-5

* 🐛 Fix crash when a typography token has a nil value

A composite typography token saved with no fields filled in stores a
nil :value. That value reached StyleDictionary's tokens-studio
font-styles preprocessor, which assumes a typography value is never
null and threw, crashing token resolution for every other token in
the file.

Reject the nil value at the source in the typography token form
validation, and as defense in depth, filter nil-valued tokens out of
every resolve-tokens* entry point before they reach StyleDictionary,
tagging them with the existing empty-input error instead.

Also remove a stray debug prn left in find-refs.

AI-assisted-by: claude-sonnet-5

* 🔧 Gitignore local CLAUDE.md

Keep the commit-conventions instructions file local to each
contributor's checkout instead of tracking it in the repo.

AI-assisted-by: claude-sonnet-5

* ⚡ Use a single transducer to tag invalid-value tokens

merge-invalid-value-tokens ran three separate passes over the token
map (remove, map, into) and then merged the result back in. Combine
the remove/map steps into one ns-level transducer, defined once
instead of rebuilt on every call, and pass resolved as the seed to
into so the trailing merge isn't needed either.

AI-assisted-by: claude-sonnet-5

* ♻️ Drop redundant t/testing wrapper in nil-value token test

The outer t/testing just repeated the deftest's own name and added
nothing the two inner t/testing blocks (each covering one concrete
assertion group) don't already say.

AI-assisted-by: claude-sonnet-5

* 🐛 Fail the nil-value token test on a resolution error

rx/sub! only handles the success case, so if token resolution ever
errors instead, done is never called and the async test hangs
instead of failing. Switch to rx/subs! with an error handler that
reports the failure and calls done, matching the pattern already
used elsewhere in the tokens test suite.

AI-assisted-by: claude-sonnet-5
2026-09-04 12:31:26 +02:00
Eva Marco
064f200774
🐛 Fix crash pressing Ctrl+D with no shape selected (#11491)
AI-assisted-by: claude-sonnet-5
2026-09-04 10:21:31 +02:00
Eva Marco
214dbd0c8b
🐛 Stop opening Comments section from persisting display-comments (#11492)
AI-assisted-by: claude-sonnet-5
2026-09-04 10:15:38 +02:00
Elena Torró
80dea409c6
🐛 Fix share-link viewers unable to load file fragments (#11484) 2026-09-03 12:15:35 +02:00
Pablo Alba
10e40e62a2
✨ Add airgapped check to nitrate (#11421) 2026-08-28 13:58:18 +02:00
Andrey Antukh
1baa8bb29f
🐛 Center canvas and select layer when navigating layer search results (#10422) (#11391)
When using previous/next buttons in the layer search panel, the canvas
now centers on the matched layer and selects it in the layers panel.
Previously only the internal search cursor was advanced without any
visible canvas or selection update.

Co-authored-by: Sumit Ridhal <sridhal@redhat.com>
2026-08-27 18:33:48 +02:00
Andrey Antukh
43bbf756b0
🐛 Fix font change regression and activate font preview by default (#11341)
* ✨ Materialize font-preview flag to make it permanent

* 🐛 Fix font change regression when WebGL is disabled

* 📎 Add fixes for e2e tests

* 🐛 Fix font change from legacy render

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
2026-08-27 18:30:33 +02:00
María Valderrama
6bedd5ac76
🐛 Fix move organization modal (#11390)
* 🐛 Fix move organization modal

* 📎 Code review
2026-08-27 12:41:32 +02:00
Andrey Antukh
b79680eeb7
🐛 Fix asset 404 for unauthenticated share-link viewers (#11342)
PR #11036 added a per-request permission check to the file-media asset
endpoints (/assets/by-file-media-id/:id and the /thumbnail variant) using
bfc/get-file-permissions. Anonymous share-link viewers were then rejected
because they have neither a session nor an access token, the asset URL
carries no share context, and the 2-arg get-file-permissions short-circuits
to nil when profile-id is nil.

Make the asset endpoints share-link aware, mirroring how get-view-only-bundle
already authorizes the same scenario: read the share-id from the query
string, validate it as a UUID, and call the 3-arg perms/get-file-read-permissions
(which chains the existing 2-arg bfc lookup, the bfc share-link fallback,
and the Nitrate org-owner fallback). On the frontend, extend
cf/resolve-file-media with an optional share-id arg and pass it from the
WASM viewer render path using the share-id already present in
[:viewer-local :share-id]. Non-viewer call sites (workspace, clipboard,
code-gen) keep the original URL shape because the new arg defaults to nil.

Closes #11338

AI-assisted-by: minimax-m3
2026-08-25 13:10:55 +02:00
María Valderrama
316b58bc75
🐛 Fix team dropdown not refreshing after organization deletion (#11336) 2026-08-24 20:02:59 +02:00
Andrey Antukh
47d599fe34
✨ Persist binfile manifest and emit workspace audit events (#11106) (#11138)
Persist binfile manifest metadata in file_data on import so file
statistics are available at open-workspace time. Emit a new
open-workspace-file audit event enriched with file statistics:
page count, shape count, component count, linked libraries,
design tokens, and whether the file is a shared library.

Closes #11106

AI-assisted-by: mimo-v2.5-pro
2026-08-21 14:37:21 +02:00
Andrey Antukh
c200a4d777
🐛 Fix HTML escaping in notification pill detail section (#11275)
The notification pill component now properly respects the `is-html`
flag when rendering the detail section, matching the behavior of the
children section. Token import error messages now escape HTML
characters in user-provided values like token names and type names
before displaying them in notifications.

AI-assisted-by: qwen3.7-plus
2026-08-19 18:21:55 +02:00
Alonso Torres
4ac14cfd08
✨ Add component synchronization to waitForLayoutUpdate (#10964)
* ✨ Add component synchronization to waitForLayoutUpdate

* 🐛 Fix async mock leak in workspace-reflow-test

Use mock/with-mocks instead of with-redefs for http/send! mock in
failed-google-font-css-does-not-abort-shared-consumers test.

with-redefs restores bindings when the block exits synchronously,
but the RxJS subscription fires asynchronously. This caused the mock
to leak into subsequent tests (workspace-media-test), producing 3
spurious failures.

AI-assisted-by: mimo-v2.5-pro

* ♻️ Replace async with-redefs with mock/with-mocks in frontend tests

with-redefs restores bindings when the block exits synchronously,
which is too early for async code (t/async, rx/subs!, promises).
mock/with-mocks uses set! and restores in the done callback, keeping
mocks alive across async boundaries.

Converted 15 with-redefs usages across 4 test files:
- workspace_reflow_test.cljs: 2 genuinely async tests (P1)
- routes_test.cljs: 3 SSO caching tests (P2)
- main_errors_test.cljs: 8 expired-org SSO tests (P2)
- comments_test.cljs: 2 comment thread tests (P2)

36 purely sync with-redefs usages left unchanged — with-redefs
is correct for synchronous code.

AI-assisted-by: mimo-v2.5-pro

* 📎 Fix fmt issues

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-08-18 17:45:42 +02:00
Andrey Antukh
5b4a5776cb 🐛 Prevent nil theme in profile updates
Omit nil optional profile fields before frontend schema validation and RPC persistence. Preserve omitted language and theme values in backend updates, and add regression coverage for partial profile saves.

AI-assisted-by: gpt-5.6-luna
2026-08-18 11:18:44 +00:00
María Valderrama
c72bb331ef
🐛 Fix nitrate advanced permissions error (#11255) 2026-08-18 11:20:02 +02:00
Pablo Alba
ed04d509ed
🐛 Fix bad managed error on backend sso failure (#11247) 2026-08-17 12:40:18 +02:00
Andrey Antukh
4ecd8ffb89
🐛 Fix crash when editing tokens with group nodes (#11144)
Make sd-token-uuid nil-safe when accessing .original.id to prevent
crashes when StyleDictionary emits group nodes alongside real tokens.

Group nodes have an original object but no id property, causing
undefined is not an object errors during interactive token resolution
in the edit modal.

Closes #11143

AI-assisted-by: qwen3.7-plus
2026-08-17 11:53:48 +02:00
Andrey Antukh
0797d7235a
🐛 Fix workspace crash on rapid sidebar measures input changes (#10793) (#10794)
The sidebar measures panel numeric inputs (X, Y, width, height,
rotation) emitted one full apply-modifiers commit per DOM event with
no throttle: every arrow key-repeat, wheel tick and scrub pointermove
became update-positions / update-dimensions / increase-rotation. A
sustained gesture starved the React renderer and crashed the
workspace with error #185 (Maximum update depth exceeded).

Coalesce those bursts at the data layer (potok), following the
update-position-data debounce pattern in texts.cljs:

- update-positions is now burst-coalesced in place (its only caller
  is the measures panel); new update-dimensions-coalesced and
  increase-rotation-coalesced variants are used by the measures
  panel, while the immediate events keep serving plugins, variants
  and token application (including the delta? rotation path).
- The first event of a burst commits immediately (leading edge, so
  single edits stay synchronous); further ticks commit at most once
  per 50 ms (throttle); a trailing debounced flush guarantees the
  exact final value lands. All payloads are absolute values, so
  keeping the latest queued value per shape/attribute is lossless.
- Pending payloads are drained atomically and stale shape ids
  (deleted mid-burst) are skipped. The drain stream lives until the
  workspace is finalized, so bursts reuse a single subscription.
- Fewer commits per burst also means fewer undo entries; scrub drags
  still produce a single entry via the input's outer transaction.

Tests: new frontend-tests.logic.sidebar-transform-coalescing-test (8
tests, legacy SVG and WASM renderer branches) guards the invariant
that a 20-event burst commits the exact final value in a handful of
commits. The previously unregistered update-position-test is wired
into the runner with WASM mock fixtures (it fails in full-suite
context without them due to a pre-existing global mock-state issue).

AI-assisted-by: kimi-k3
2026-08-17 11:51:24 +02:00
Eva Marco
53985dc630
🐛 Fix setting dark theme on onboarding (#11212)
* 🐛 Fix setting dark theme on onboarding

* 🎉 Add test
2026-08-11 13:49:48 +02:00
Eva Marco
d7daefafe2
🐛 Fix select shape after enter path edition (#11205) 2026-08-11 13:05:47 +02:00
Eva Marco
c4d1a1bc94
🐛 Fix node deleting (#11126)
* 🐛 Fix delete path node

* 🐛 Fix typography on shortcuts list
2026-08-11 13:03:29 +02:00
María Valderrama
1e6d438257
🐛 Fix SSO failure logging user out instead of showing error page (#11129)
* 🐛 Fix SSO failure logging user out instead of showing error page

* 📎 Code review
2026-08-11 09:14:29 +02:00
Luis de Dios
86c563f11f
🐛 Fix font family typography asset persist across files in new created text layers (#11134) 2026-08-10 11:35:51 +02:00
Andrey Antukh
e2d429d283
🐛 Add timeout to plugin manifest fetch (#11120)
The fetch-manifest function previously had no timeout, causing the
plugin installation flow to hang indefinitely if the server accepted
the connection but never completed the response.

Added a 15-second timeout using rx/timeout to abort the request
automatically.

Closes #11119

AI-assisted-by: qwen3.7-plus
2026-08-07 13:37:00 +02:00
Andrey Antukh
81e44afbe3
🐛 Add backend password validation with complexity rules and dictionary check (#11059)
* 🐛 Add backend password validation with complexity rules and dictionary check

Enforce minimum 8-character password length, require at least 1 lowercase
letter, 1 uppercase letter, 1 digit, and 1 special character, and reject
common passwords using Passay library with a 10k-entry wordlist from
SecLists during registration and password change flows.

AI-assisted-by: mimo-v2.5-pro

* ✨ Improve user feedback

When the password is invalid, the user now gets extra indications to make it stronger, so it can be valid.

* 🐛 Fix remove unneeded common password check

The dictionary check is only relevant for passwords that meet all other requirements, but all 10,000 common passwords would fail the character requirements, so this check is not needed

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
2026-08-06 10:17:14 +02:00
Andrey Antukh
a60b648c6c 🐛 Fix issues with draft-js tests 2026-08-06 08:55:58 +02:00
Andrey Antukh
49119e0339
♻️ Rename nitrate config to admin-console (#10929)
* ♻️ Rename nitrate config to admin-console

Rename user-facing configuration from 'nitrate' to 'admin-console':
- Feature flags: :nitrate -> :admin-console, :nitrate-bulk-create-profiles -> :admin-console-bulk-create-profiles
- Config keys: :nitrate-shared-key -> :admin-console-shared-key, :nitrate-backend-uri -> :admin-console-uri
- Shared-keys map entry: :nitrate -> :admin-console (setup.clj + main.clj)
- Env vars: PENPOT_NITRATE_SHARED_KEY -> PENPOT_ADMIN_CONSOLE_SHARED_KEY, PENPOT_NITRATE_BACKEND_URI removed (consolidated into PENPOT_ADMIN_CONSOLE_URI)
- Docker/nginx: PENPOT_NITRATE_URI -> PENPOT_ADMIN_CONSOLE_URI

Code namespaces, file paths, CSS classes, and i18n keys stay as-is.

AI-assisted-by: mimo-v2.5-pro

* ♻️ Rename initialize-user-in-nitrate-organization to initialize-user-in-organization

Part of the nitrate -> admin-console rename series. The function and all 9 references across 6 files have been renamed.

* ♻️ Rename :nitrate-bulk-create-profiles-not-allowed to :bulk-create-profiles-not-allowed

* ♻️ Inline nitrate-permissions into app.common.types.organization

- Delete app.common.types.nitrate-permissions and its test
- Move permission rules (allowed?, can-send-invitations?, etc.) into organization.cljc
- Harmonize all consumers to use alias cto for app.common.types.organization
- Update test runner and create organization_test.cljc
2026-08-03 17:23:55 +02:00
Andrey Antukh
e741313add Merge remote-tracking branch 'origin/staging' into develop 2026-07-31 19:21:07 +02:00
Eva Marco
9bb5861322
🐛 Fix undo delete pages change order (#10969) 2026-07-31 13:09:18 +02:00
Eva Marco
36553a7c6f
🐛 Fix reset cancel shortcut (#10980) 2026-07-31 12:51:01 +02:00
María Valderrama
1744d07731
✨ Allow nitrate onboarding to be handled by admin-console (#10936)
* ✨ Allow nitrate onboarding to be handled by admin-console

* 📎 Code review
2026-07-31 12:24:30 +02:00
Marina López
94f51afb20
♻️ Normalize organization naming across Penpot (#10977) 2026-07-31 12:04:10 +02:00
Elena Torró
a2231a8bf9
🐛 Fix text align shown as left in v3 editor (#10973) 2026-07-31 11:48:31 +02:00
Luis de Dios
e952d9c70a
🐛 Fix draw a line/arrow with a single click (#10974) 2026-07-31 11:25:20 +02:00
Eva Marco
fe85a5717e
♻️ Fixes on shortcuts (#10906)
* 🎉 Improve reset to default flow

* 🎉 Add diff modal

* 🐛 Fix measurement shortcuts

* 🎉 Separate open-sections for each tab

* 🐛 Align button link icon

* 🎉 Reset only works after press save

* 🐛 Fix little things

* 🎉 Make the import export row to be fixed

* ♻️ Fix CI

* 🐛 Cancel shortcut is not appearing on disabled tab

* 🐛 Fix tests

* 🐛 Fix loop on personalized

* 🐛 Fix show measurements shortcut
2026-07-30 16:56:10 +02:00
Pablo Alba
370e91c3e3
🐛 Fix bad error callback on nitrate checkout (#10886) 2026-07-30 16:50:47 +02:00
Marina López
31ccfa546f
✨ Add nitrate audit events 2026-07-30 15:32:45 +02:00
Belén Albeza
91d4b16171
🐛 Fix selrect not being recalculated after pasting properties (#10963) 2026-07-30 15:01:50 +02:00
Andrey Antukh
040080749b
🐛 Fix shape export failures when export name is nil or empty (#10852)
Use cuerdas blank-name handling directly when normalizing frontend export
payloads. Replace nil or blank export names with the object-id string in
request-simple-export, request-multiple-export, clipboard export, and plugin
direct export payloads so that the backend always receives a valid name. Add
focused frontend tests for nil/blank name normalization and normalized request
params.

AI-assisted-by: nex-n2-pro
2026-07-30 12:49:19 +02:00
Andrey Antukh
fadb3124a0
🐛 Clamp gradient stop offsets to valid range (#10881)
* 🐛 Clamp gradient stop offsets to valid [0, 1] range

Fixed a bug where gradient stop offsets outside the valid [0, 1] range were being sent to the server, causing schema validation errors ('invalid shape found').

Changes:
- Viewport gradient handler: clamp offset in points-on-pointer-down before creating new stops
- Colorpicker gradient preview: clamp offset in handle-preview-down before adding stops
- Data layer: clamp offset parameter in update-colorpicker-add-stop and all stop offsets in update-colorpicker-stops; added app.common.math require

All clamping follows the existing pattern used in handle-marker-pointer-move.

AI-assisted-by: deepseek-v4-flash

* 💄 Fix formatting in gradient handlers

Fix cljfmt formatting issues in gradient handler functions.

AI-assisted-by: qwen3.7-plus
2026-07-30 12:49:00 +02:00
Pablo Alba
03f9393200
🐛 Fix nitrate sso doesn't notify workspace on activation (#10955) 2026-07-30 11:56:06 +02:00
Belén Albeza
be5d9f9e9c
🐛 Fix invalid shape due to missing fonts (#10942)
* 🐛 Fix setting a font-related properties to nil

* ✨ Repair already-corrupted text nodes

* 🐛 Fix deleted fonts appearing in Recents and as the auto-selected font for new text shapes
2026-07-30 11:18:19 +02:00
Belén Albeza
3e7fb07931
🐛 Fix undo crash after deleting a word (#10862) 2026-07-29 15:53:41 +02:00
Marina López
056cd3d379 🐛 Fix SSO review 2026-07-29 11:26:16 +02:00
Alejandro Alonso
4cd34b22e2
🐛 Fix wasm context restore guards (#10824)
* 🐛 Prevent WASM panics during WebGL context restore

Delay context-restored until reload finishes and no-op app
mutations while reloading so resize/modifiers cannot panic
mid-teardown.

* 🐛 Fall back to CLJS bool content when WASM is not ready

Returning nil from calculate-bool would persist empty path content
during context loss/reload; use path/calc-bool-content instead.
2026-07-29 10:06:52 +02:00
Alonso Torres
cb21f6401a
✨ Add waitForLayoutUpdate plugin method (#9898)
* ✨ Add waitForLayoutUpdate plugin method

* ✨ Refactor internal wait for tasks
2026-07-28 14:44:30 +02:00
Andrey Antukh
e9603f00b0 Merge remote-tracking branch 'origin/staging' into develop 2026-07-28 12:50:41 +02:00
Elena Torró
d4d22ff1a4
🔧 Prepare wasm render for headless exporter (#10795)
* 🔧 Support background blur on PDF render for strokes and text shapes

* ✨ Add LRU eviction to the wasm image store

* ✨ Add RasterFormat to encode png, jpeg and webp from wasm

* 🐛 Fix client-side wasm export encoding jpeg and webp as png
2026-07-28 11:54:22 +02:00
Andrey Antukh
7eb188b077
🐛 Fix text content repair and remove blast-radius containment (#10731)
Repair text shapes with empty/broken content at all three levels
(root, paragraph-set, paragraph) in migration 0025 to prevent
workspace update failures. Handle all corner cases: nil/empty/non-
vector children, non-map items, wrong types. Remove geometry-only
validation skip in changes.cljc so all shapes are validated.

AI-assisted-by: qwen3.7-plus
2026-07-28 11:00:53 +02:00