24001 Commits

Author SHA1 Message Date
Andrey Antukh
4042daaed0 🔥 Remove serveral tmp files 2026-09-30 08:41:56 +02:00
Andrey Antukh
10ccfd2218
🐛 Gate pastes on page load and harden base-shape lookup (#11674)
* 🐛 Gate pastes on page load and harden base-shape lookup

Pasting while the workspace is still opening crashed the
session: the layer-order lookup called rseq on a missing
root children list.

Ignore paste events until the page objects are loaded (the
clipboard keeps its content, so retrying works), return empty
instead of throwing from the shared layer-order helpers, and
fall back to pasting at the pointer position when the selection
is detached from the shape tree. Selecting the page root keeps
working as before through the frame branches.

Closes #11666

AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Cover props paste and root-plus-other in paste guards

Address review follow-ups on the paste-before-init fix: gate
props pasting on page readiness like the shape entries, and
route root-plus-other selections without a base shape to the
pointer fallback instead of the unguarded else branch. Pin
single-root selection to the frame path with a regression test.

Closes #11666

AI-assisted-by: muse-spark-1.3-contributor

* ♻️ Hoist page lookup out of paste-shapes gate

Bind page and page-objects once in an outer let instead of
calling lookup-page twice (once for the readiness gate and
once inside the body). No behavior change.

AI-assisted-by: muse-spark-1.3-contributor

* ♻️ Reuse bound ids in paste-shapes page lookup

Bind file-id and page-id once and pass them to the lookup-page
arity that takes both, instead of resolving the page twice and
rebinding file-id in the inner let. No behavior change.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-30 07:24:03 +02:00
Andrey Antukh
0ef35fc52a
🐛 Harden browser logging against invalid levels (#11693)
* 🐛 Stop browser logging from crashing on empty levels

Stop level->int crashes from taking down the dashboard when a
nil or unknown level reaches the browser logger. Invalid levels
now warn and are ignored in enabled?, setup! and the console
handler, which renders unknown records with a neutral fallback.

Alias the schema-legal :fatal level to :error in the browser
mappings and validate the JS-exported debug.set_logging, which
previously threw on missing arguments and wrote unreachable
keyword keys into the loggers map.

Closes #11690

AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Guard logger args and strengthen logging tests

Close the residual throw paths next to the empty-level crash:
guard non-string loggers in enabled? and setup!, coerce
set_logging arguments safely, and validate logger keys.

Strengthen the regression tests so the fatal alias cannot
regress silently: enabled-logger filtering, JVM fatal and bogus
cases, setup! skip proof, and invalid-logger cases.

Related to #11690

AI-assisted-by: muse-spark-1.3-contributor

* 🐛 Address low findings from logging review

Validate the logger before the level in console-log-handler,
share a public valid-logger? predicate with debug/set-logging,
and keep warn formatting consistent across boundaries.

Document the fail-soft-FE/strict-BE split on enabled? and the
valid-level? contract on set-level!. Cover safe fallbacks, bad
logger keys, handler logger skips, and loggers-map isolation in
common tests, and add a frontend test for debug/set-logging.

Related to #11690

AI-assisted-by: muse-spark-1.3-contributor
2026-09-30 07:17:02 +02:00
Andrey Antukh
94d6f5a25c Merge remote-tracking branch 'origin/main' into staging 2026-09-29 23:07:26 +02:00
Andrey Antukh
3b886995ba 🐛 Emit the accept-organization-invitation audit event once
The event was written twice per accepted organization invitation. The
backend submitted it, and the browser then re-submitted a copy of the
props that the backend had already put in the response under
`:organization-invitation-audit` (`handle-token :team-invitation` in
`verify-token.cljs`). Both rows carried the same name with different prop
vocabularies, and the browser copy only existed when the browser finished
the flow.

Emit the event from the backend only. It now also carries the three props
that lived in the browser copy: the organization member count before the
add, the add source, and whether the invitee also joined a team. The
origin moves to the event context as `:event-origin`. The response no
longer includes `:organization-invitation-audit`, so the browser stops
emitting the event and `verify-token.cljs` drops its
`app.main.data.event` require.

The `accept-*` events of this command now share one prop vocabulary:
`:profile-id` for the accepting profile, `:invited-by` for the inviter
and `:profile-email` for the email, replacing the mix of
`:user-id`/`:user-who-send-invitation` and `:email`.

Audit consumers of `accept-organization-invitation` now see one row per
acceptance instead of two, and must read the new prop names.

AI-assisted-by: space-bunny-free
2026-09-29 22:40:03 +02:00
David Barragán Merino
0049c8b673 🐳 Remove OpenEXR support from ImageMagick and Docker images
Penpot only accepts jpeg, png, webp, gif and svg images, so the
EXR coder was never used. It was enabled only because ImageMagick's
configure auto-detected libopenexr-dev at build time.

OpenEXR accounted for 25 CVEs (15 High) in both the exporter and
media-processor images, and was also bundled into the backend via
/opt/imagick/lib/deps, where dpkg-based scanners cannot see it.

- Build ImageMagick with --without-openexr and drop libopenexr-dev
- Drop libopenexr-3-1-30 from the imagemagick, backend, exporter,
  media-processor and devenv images
- Remove `apt-get upgrade` from the ImageMagick build stage
- Bump penpotapp/imagemagick to 7.1.2-27-1

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-29 19:13:41 +02:00
David Barragán Merino
2d73adc926 🐳 Remove dist-upgrade from DHI-based Docker images
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-29 19:13:41 +02:00
Andrey Antukh
0de865748d
🐛 Keep camelCase svg attribute names when importing a penpot file (#11974)
The json reader of the binfile v3 import rewrites every key of every
entry to kebab-case, nested maps included. Shape `:svg-attrs` is the one
map whose keys are camelCase react prop names, because the svg import
path runs them through `attrs->props`, so an attribute exported as
`fillRule` came back as `:fill-rule` and was stored that way.

The renderer looks the attribute up by its camelCase name, does not find
it and falls back to the default fill rule, so a shape exported with
`fillRule: evenodd` was painted without its hole, and the attributes
panel showed `fill-rule`.

`clean-shape-post-decode` already runs on every shape right after the
schema decode, for page shapes and component objects alike, so the
repair goes there: run `:svg-attrs` back through `attrs->props`, the
same transform that built the keys. It is idempotent, so shapes that
arrive correct are left untouched.

The new tests import a real export that carries the attribute, for
page shapes and component shapes.

Closes #11954

AI-assisted-by: space-bunny-free
2026-09-29 14:15:21 +02:00
Alejandro Alonso
cba51cfb7e
🐛 Keep adjacent tile compose origins abutted (#11968)
Round the view offset once and place tiles at
origin + k * TILE_SIZE so half-pixel pans do not
open a 1px background seam between neighbors.
2026-09-29 12:38:04 +02:00
María Valderrama
be63107ed6
🐛 Fix organization/team switcher issues from UI review (#11940)
* 🐛 Fix organization/team switcher issues from UI review

* 📎 Code review
2026-09-29 12:01:45 +02:00
Andrey Antukh
dc0ea3a69c
🐛 Attribute audit events to the caller, not the response (#11952)
* 🐛 Attribute audit events to the caller, not the response

prepare-rpc-event took the event profile-id from the result map
whenever it carried one, before falling back to the caller. Any
command returning a response with a :profile-id key silently
credited the action to somebody else.

get-error-report returns the report with its decoded content
merged in, and that content holds the profile that owned the
report, so privileged reads were logged against the users whose
crashes were being inspected. verify-token on a team invitation
returns the inviter's profile-id, so accepting an invitation was
logged against the inviter.

Resolution is now ::audit/profile-id metadata, then
::rpc/profile-id, then the zero uuid; the response is never
consulted. The two verify-token branches that relied on it now
declare the profile in the result metadata. Every other command
either already declared it or returns no :profile-id; all 30
registered command namespaces were checked.

The tests that pinned the old behavior are replaced by ones
covering the new contract.

AI-assisted-by: space-bunny-free

* 🐛 Coerce the audit profile-id override to a uuid

The only sanctioned way for a command to override the profile of an
audit event is the ::audit/profile-id metadata, and the value is set
by hand in a dozen commands, some of them reading it from token
claims or other sources we do not type.

schema:event requires a uuid and submit* swallows the validation
error, so a string did not fail loudly: the row was dropped silently.
Values that cannot become a uuid are now discarded with a warning
and the event falls back to the caller, which is always a valid uuid.
A uuid, the common case, exits on the first check.

AI-assisted-by: space-bunny-free
2026-09-29 09:47:07 +02:00
Andrey Antukh
f38c7dd639
⬆️ Update deps (#11960)
* 🐛 Migrate openUIApi schema to Zod v4 function syntax

Zod 4 removed z.function().args(), which broke the
plugins-runtime build with implicit-any errors on every
openUIApi parameter and knock-on possibly-null errors on
the modal in plugin-manager.

Declare the inputs with z.function({ input: [...] }) so the
parameter and return types infer again; behavior is unchanged.

Add a regression spec covering delegation, optional args and
rejection of invalid theme and title values.

AI-assisted-by: muse-spark-1.3-contributor

* 📚 Fix deprecated markdown-it-anchor permalink option in docs

Migrate docs Eleventy config to the markdown-it-anchor v10 API.
Replace the deprecated boolean permalink option with
linkInsideHeader, keeping the same symbol and class.
Bump markdown-it-anchor to v10 and related docs deps.

AI-assisted-by: muse-spark-1.3-contributor

* ⬆️ Update deps

* ⬆️ Update base docker images

* 📎 Fix mcp tests
2026-09-29 09:07:34 +02:00
David Barragán Merino
61189fbebe 👷 Run plugins package deploy inside devenv container
Parallel deploy jobs on the shared self-hosted host raced on
/home/runner/setup-pnpm (ENOTEMPTY / missing tarball errors).
Use penpotapp/devenv, which already ships the Node and pnpm versions
pinned in develop, and mount a persistent pnpm store from the host
instead of pnpm/setup + actions/cache.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-28 22:02:35 +02:00
Andrey Antukh
a69f80fe29 ⬆️ Update pnpm to 12.6.0 and opencode pair in devenv
Bump the devenv and image Dockerfiles to pnpm 12.6.0
(latest stable; 12.8.0 is still on the next tag) with
fresh SHA256 pins, and opencode to 1.18.33 plus
opencode2 to 2.0.18 with fresh checksums.

Stamp all 35 package.json files via
scripts/sync-pnpm-version and refresh the 11 lockfiles;
diffs are metadata-only, with no dependency re-resolution.

Drop the last corepack calls from the media-processor
build script and its generated image setup: pnpm now
ships as a system binary and self-heals version drift.

AI-assisted-by: muse-spark-1.3-contributor
2026-09-28 18:19:26 +00:00
Eva Marco
0389435ced
🐛 Block typography creation from missing fonts or multiple texts (#11938)
Creating a typography from a text whose font is no longer installed
baked the broken font-id into a new asset. With several texts selected
there is no single style to capture either. The add-typography event
now does nothing in both cases, and the "Add typography" button in the
local library is disabled with a label that explains why.

The button lives in its own component so selection, shape and editor
changes re-render only the button, not the typography list, and shared
libraries do not subscribe to that state at all. The event and the
button read the editor data through the new `editor-text-options`, so
both see the same font for the wasm, v2 and v1 text editors.

AI-assisted-by: claude-opus-5-5
2026-09-28 18:33:08 +02:00
Andrey Antukh
72dad5d67d
✨ Add renderer option on create-demo-profile command (#11894)
* ✨ Add renderer option on create-demo-profile command

Allow create-demo-profile to take an optional renderer (svg or
wasm) and store it on profile props. When omitted, no renderer is
written so the Penpot default still applies.

Closes #11893

AI-assisted-by: Muse Spark 1.3 Free

* 📚 Fix renderer doc version on create-demo-profile

Correct the ::doc/changes version for the new renderer param from
2.20 to 2.18.1.

AI-assisted-by: Muse Spark 1.3 Free
2026-09-28 18:21:07 +02:00
Eva Marco
f311c7ab05
🐛 Fix token propagation on canvas color (#11950)
* 🐛 Update canvas background when its color token changes

Token propagation only walked the shapes of each page, so a canvas
background linked to a color token kept its old value after switching
the active set or editing the token. Propagation now also updates the
background of every page whose `:background-token` resolves to a new
color, inside the same undo transaction.

AI-assisted-by: claude-opus-5-5

* 🐛 Select the dragged token set by id instead of by path

Starting a drag on an unselected token set stored its path as
`:selected-token-set-id`. The sidebar then crashed on the
`(uuid? force-set-id)` assert of `get-tokens-in-active-sets-force`.
This could happen when toggling a set checkbox with a slight mouse
move.

AI-assisted-by: claude-opus-5-5

* 🎉 Add playwright test
2026-09-28 18:15:43 +02:00
David Barragán Merino
534b1a6702 🐛 Fix script injection in GitHub Actions workflows
Values coming from `${{ }}` expressions were interpolated directly into
`run:` scripts, so GitHub substituted them into the shell source before
bash parsed it. A commit title containing a double quote broke the
"Write step summary" step of the bundle build with a syntax error, and
the same pattern allowed arbitrary command execution on the
self-hosted runners.

Pass every expression used inside `run:` through step/job `env:` and
reference it as a quoted shell variable instead. Use the runner's
default variables (GITHUB_RUN_ID, GITHUB_REPOSITORY, ...) where the
value comes from the `github` context.

Also validate `plugin_name` in plugins-deploy-package.yml against
`^[a-z0-9][a-z0-9-]*$`, since it is free-form and reaches paths,
worker names, GITHUB_ENV and action inputs.

Affected workflows: build-bundle, build-docker,
build-docker-admin-console, plugins-deploy-package,
plugins-deploy-api-doc, plugins-deploy-styles-doc, release, tests-e2e.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-28 17:00:08 +02:00
Alonso Torres
c0714def01
🐛 Fix problems with react loops (#11941) 2026-09-28 16:59:06 +02:00
Andrey Antukh
b890b94d27
🐛 Add a migration dropping the obsolete stroke-per-side attr (#11946)
Individual stroke widths shipped with a `:stroke-per-side` boolean on every
stroke, telling the renderer and the CSS generator whether the four
per-side widths were meaningful. Comparing the sides answers that on its
own, so the attribute was later dropped from the closed stroke schema
and the toggle became ephemeral editor state.

Files written between those two changes still carry the attribute, and
the closed schema rejects the unknown key, so loading such a file fails
`check-file-data` with a `:malli.core/extra-key` error and surfaces as
an internal error in the editor.

Add migration `0030-remove-stroke-per-side-attr`, which drops the
attribute from every stroke of every page and component shape. The
per-side widths are the saved design data and are kept untouched, so a
file whose boolean was `true` renders exactly as before.

The migration is naturally idempotent and a no-op for files that never
carried the attribute, since `dissoc` on a map without the key returns
an equal map.

Cover it with tests for the schema rejection, the per-side and global
widths surviving, component shapes, idempotency, and the run through
`migrate-file`.

Closes #11943

AI-assisted-by: space-bunny-free
2026-09-28 14:57:17 +02:00
Luis de Dios
8f1100d0f5
✨ Enable stroke-per-side flag (#11942)
* ✨ Enable stroke-per-side flag

* 🐛 Fix stroke per side tests for the enabled flag

The default flags now include :enable-stroke-per-side, so
frontend tests and Playwright specs that assumed the flag
was off need to turn it off explicitly.

Update the token context menu test expectations: rects and
boards now expose the stroke-width submenu, and force the
flag off in the "per-side is disabled" cases.

Pass disable-stroke-per-side in the two Playwright specs
that check the flag-off behavior, since app.config always
merges the default flags.

AI-assisted-by: deepseek-v4.1-flash
2026-09-28 14:39:18 +02:00
Eva Marco
77c4072b43
🎉 Activate flag for custom shortcuts and token lib (#11945) 2026-09-28 14:08:51 +02:00
Alejandro Alonso
18c9108d47
✨ Enable WASM text editor with render-wasm (#11936)
When render-wasm/v1 is active, also enable text-editor-wasm/v1 so
the WASM text editor turns on with the renderer. Keep forcing
text-editor/v2 as before; the viewport still prefers the WASM
editor when both features are set. Classic unchanged.

Closes #11934
Relates to #11935
2026-09-28 12:44:15 +02:00
Andrey Antukh
41e4ca4869 Merge remote-tracking branch 'origin/develop' into staging 2.19.0-RC1 2026-09-28 10:31:43 +02:00
Andrey Antukh
f9b8f1ba75 Merge remote-tracking branch 'origin/staging' into develop 2026-09-28 10:31:20 +02:00
Andrey Antukh
370519937d Merge remote-tracking branch 'origin/develop' into develop 2026-09-28 10:27:20 +02:00
girafic
7e0b65f291
🐛 Hide the custom resize icon of the plugin modal in Firefox (#11798)
Firefox draws its own resize grip for a box with `resize: both` and,
unlike Chrome and Safari, gives no way to hide it: `::-webkit-resizer`
is ignored. The plugin modal therefore showed two grips in its
bottom-right corner, its own icon and the one from the browser.

Show the custom icon only where the browser grip can be hidden. In
Firefox the native grip is now the only one.

Closes #11795
2026-09-28 10:19:35 +02:00
Andrey Antukh
1349d1ef2d 🌐 Rehash and validate translation files 2026-09-28 10:15:48 +02:00
Alexis Morin
88984ee03e
🌐 Add translations for: French (Canada)
Currently translated at 95.3% (2169 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-09-28 08:14:12 +00:00
Anonymous
a45eaa2b3c
🌐 Add translations for: French (Canada)
Currently translated at 95.3% (2169 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-09-28 08:14:11 +00:00
VKing9
4eeb652893
🌐 Add translations for: Hindi
Currently translated at 76.7% (1746 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hi/
2026-09-28 08:14:10 +00:00
AntonPalmqvist
187369c393
🌐 Add translations for: Swedish
Currently translated at 98.0% (2230 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-09-28 08:14:09 +00:00
Anonymous
7aaef298a3
🌐 Add translations for: Swedish
Currently translated at 98.0% (2230 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-09-28 08:14:08 +00:00
Stephan Paternotte
d18b1285a4
🌐 Add translations for: Dutch
Currently translated at 98.0% (2230 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-09-28 08:14:07 +00:00
Anonymous
e0c23951a0
🌐 Add translations for: Dutch
Currently translated at 98.0% (2230 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-09-28 08:14:06 +00:00
Anonymous
151f6d29e9
🌐 Add translations for: Korean
Currently translated at 76.7% (1747 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ko/
2026-09-28 08:14:04 +00:00
Anonymous
623b615cdc
🌐 Add translations for: Ukrainian (ukr_UA)
Currently translated at 78.9% (1795 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/
2026-09-28 08:14:03 +00:00
Anonymous
4a4217ef7c
🌐 Add translations for: Italian
Currently translated at 81.9% (1864 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/
2026-09-28 08:14:02 +00:00
Danial Shirali
e13ef345f3
🌐 Add translations for: Persian
Currently translated at 98.0% (2230 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2026-09-28 08:14:01 +00:00
Andy Li
da273234e1
🌐 Add translations for: Chinese (Traditional Han script)
Currently translated at 97.9% (2228 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2026-09-28 08:14:00 +00:00
Anonymous
50b427b4ff
🌐 Add translations for: Hebrew
Currently translated at 83.8% (1907 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2026-09-28 08:13:58 +00:00
Anonymous
e9c9761a9f
🌐 Add translations for: German
Currently translated at 76.0% (1729 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2026-09-28 08:13:57 +00:00
DoubleCat
dd0690124e
🌐 Add translations for: Chinese (Simplified Han script)
Currently translated at 90.0% (2049 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2026-09-28 08:13:56 +00:00
Oğuz Ersen
42ae277979
🌐 Add translations for: Turkish
Currently translated at 98.0% (2230 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-09-28 08:13:55 +00:00
Anonymous
628155b6b0
🌐 Add translations for: Turkish
Currently translated at 98.0% (2230 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2026-09-28 08:13:54 +00:00
Anonymous
bfa9575bb4
🌐 Add translations for: Russian
Currently translated at 64.1% (1459 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2026-09-28 08:13:53 +00:00
Surfoo
1c69fa0c32
🌐 Add translations for: French
Currently translated at 98.0% (2230 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-09-28 08:13:52 +00:00
Anonymous
b1d4b4526f
🌐 Add translations for: French
Currently translated at 98.0% (2230 of 2275 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2026-09-28 08:13:51 +00:00
Andrey Antukh
38b33aba53 Merge remote-tracking branch 'weblate/develop' into develop 2026-09-28 09:59:20 +02:00
Alonso Torres
a31409617f
🐛 Fix loop with context menu (#11891) 2026-09-28 09:08:28 +02:00