24683 Commits

Author SHA1 Message Date
Miguel de Benito Delgado
3d256c5aef 🚧 WIP Step 14, Grab correct w/h attrs from context 2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
9e117cb635 🚧 WIP Step 13, Track failure causes
AI-assisted-by: muse-spark, GLM-5.3
2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
e44fb12f35 🚧 WIP Step 12.4, Comments
AI-assisted-by: gpt6-astra
2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
b0a3e96759 🚧 WIP Step 12.3, Test cleanup + missing coverage
AI-assisted-by: muse-spark, deepseek-flash
2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
083817e2b0 🚧 WIP Step 12.2, Fix browser bridge, lifecycle
AI-assisted-by: muse-spark, glm-5.3
2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
f7f51908ec 🚧 WIP Step 12.1, Improve case validation
AI-assisted-by: muse-spark, glm-5.3
2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
c5ce9e450c 🚧 WIP Step 12, temporary pilot
- pilot runner for testing
- will be entirely removed later

AI-assisted-by: muse-spark-1.3, deepseek-flash, glm-5.3
2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
68b80b9ad2 🚧 WIP Step 11, browser bridge
- New schema:viewport
- Browser bridge (with some temp code)
- Move prepare-scene out measured regions
-
AI-assisted-by: muse-spark-1.3, deepseek-flash, glm-5.3
2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
45d3528ad7 🚧 WIP Step 10, scene upload 2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
a576582eb2 🚧 WIP Step 10, defcase body validation, docs 2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
41ea20233a 🚧 WIP Step 9, fix naming conventions 2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
a2b8715a98 🚧 WIP Step 8, simplify and clean up tests 2026-11-09 13:17:57 +01:00
Miguel de Benito Delgado
ab415ef551 🚧 WIP Step 7, scene and case declarations 2026-11-09 13:17:56 +01:00
Miguel de Benito Delgado
d44fb053ce 🚧 WIP Step 6, ellipses 2026-11-09 13:17:56 +01:00
Miguel de Benito Delgado
a7266f45cd 🚧 WIP Step 5, bools 2026-11-09 13:17:56 +01:00
Miguel de Benito Delgado
0439e6bd0f 🚧 WIP Step 4, frames and groups 2026-11-09 13:17:56 +01:00
Miguel de Benito Delgado
202371d52e 🚧 WIP Step 3, fixture macros, prng, defaults, placeholder scene 2026-11-09 13:17:56 +01:00
Miguel de Benito Delgado
30a3cc053c 🚧 WIP Step 2, common scene code, validation 2026-11-09 13:17:56 +01:00
Miguel de Benito Delgado
f0d51e16e8 🚧 WIP Step 1, scaffold cljs benchmark runner 2026-11-09 13:17:56 +01:00
Miguel de Benito Delgado
33602b9114
🔧 Allow skipping renderer build setup via env. var (#12882)
Set PENPOT_WASM_PREPARED=1 to speed up repeated builds
2026-11-09 12:53:18 +01:00
Luke Lau
943e63a99e
⚡ Group color updates in selections (#12875) 2026-11-09 12:08:36 +01:00
Hosted Weblate
9034e707bf
🌐 Merge branch 'origin/develop' into Weblate. 2026-11-09 11:09:57 +01:00
Alexis Morin
968edc6a55
🌐 Add translations for: French (Canada)
Currently translated at 96.7% (2285 of 2361 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-11-09 11:07:01 +01:00
AntonPalmqvist
a2dfcb8669
🌐 Add translations for: Swedish
Currently translated at 95.9% (2266 of 2361 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/sv/
2026-11-09 11:06:59 +01:00
Andrey Antukh
1df2941576 Merge remote-tracking branch 'origin/staging' into develop 2026-11-09 10:42:28 +01:00
Andrey Antukh
b766e88ed8 Merge remote-tracking branch 'origin/main-2.18.x' into staging 2026-11-09 10:31:58 +01:00
Andrey Antukh
f90ef83da0 📚 Update changelog 2.18.7 2026-11-09 10:28:54 +01:00
Andrey Antukh
341e3dfa65 Merge remote-tracking branch 'origin/staging' into staging 2026-11-09 10:17:47 +01:00
Andrey Antukh
e86131cf16 Merge branch 'main-2.18.x' into staging 2026-11-09 10:17:33 +01:00
Andrey Antukh
0ea6d166f0
✨ Add a session origin model to auth events (#12856)
* 🐛 Return the full profile from the LDAP invitation branch

The invitation branch responded with only {:invitation-token token},
so the session start received a map without id or auth-backend. With
the new auth-origin vocabulary that map fails validation and the login
breaks in development and tests; in production the event would be
emitted without a channel.

Return the profile plus the token, like the regular branch, and cover
both branches with RPC-level tests.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash

* 🐛 Mint an auth token in the OIDC callback with the invitation inside

The callback used the invitation JWE as the session token, so the
frontend landed directly on token verification and never ran the
regular session start: no signin event, no teams, no host hook.

Always mint an :auth token and carry the invitation in its claims.
Token verification returns the claims verbatim, so login-from-token
attaches the invitation to the profile and logged-in does the rest,
the same pattern the verify-email token already uses.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash

* ✨ Keep the session origin in the telemetry shadow rows

The anonymisation filter drops every string unless it is on a short
allowlist. Add source and activation next to lang and auth-backend so
the shadow copy keeps them; the email and the full name are still
dropped and the email domain is still derived from the email.

The filter keeps its two bindings: the transducer discards everything
that is not a UUID, a boolean or a number, so the allowlist is applied
on the filtered map, never on the raw props.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash

* ✨ Describe the signup outcome with activation, not with action

The three success branches of register-profile labelled a new account
as "login", "email-verification" or "accept-invitation", so the event
contradicted its own name and the value only lived in context, where
the analytics consumers never read it.

Add activation to the event props and drop action from those branches:
the name already says it is a signup, and a constant action would only
add noise. The retry branches keep their action in context because
there it distinguishes four different outcomes.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash

* ✨ Describe the session origin in the signin event

The event carried the profile but never said why the session started:
the only field that tried was frontend metadata that never leaves the
browser, and several flows filled it with the wrong value.

Add a small closed vocabulary (source, auth-backend, activation,
from-invitation) and compose it from the profile when signin is
emitted. The assertion fails in development and tests and is elided in
the release build, so a bad value cannot break a real login.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash

* ✨ Emit the session origin from every login entry point

The four emitters stamped a free-form string as metadata, so the
session event could not rely on it: two flows that were signups wrote
the same value as a login.

Route them through origin-meta: password and LDAP logins declare
source login, register declares source register with activation
immediate, and login-from-token derives both from the token issuer:
:auth for a login and :verify-email for a signup that needed the
email confirmation.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash

* 🐛 Send the invitation signups through the regular session start

Registering from a team invitation link navigated by hand to token
verification, so the session start never ran: no signin event, no
teams, no host hook. The same navigation was copied in four places.

Teach login-from-register to carry an optional invitation token,
extract the profile decoration into with-register-origin, and call it
from the four sites instead of the hand-written navigation.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash

* 🐛 Resolve the invitation without waiting for the teams

Registering from an invitation link now goes through the session
start, and that start only navigates after the teams request
completes. fetch-teams has no error handling, so a network failure
there would leave the invitation unaccepted and the user on the signup
screen with a session already minted.

Move the invitation navigation out of the teams path: it emits as soon
as the profile carries a token, and the teams path emits nothing for
an invited profile so it cannot move the user away from the token
verification screen.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash

* 📚 Document the session origin vocabulary and the invitation flow

Add a focused memory with the four fields, where each value comes from,
the table of flows, the LDAP exception and the invitation path through
logged-in, and link it from the frontend routing.

Update the audit-log memory: source and activation are now on the
telemetry allowlist, activation lives in props and action only exists
on the retry rows.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash

* 📎 Cover the invitation session start and fix the auth memories

The invitation flows and the token decoration in login-from-token were
only covered by helper tests, so removing the guard that keeps the
teams path from navigating, or the token attach, broke the behaviour
silently.

Run the events through ptk/watch with a teams-fetched event as input:
an invited profile navigates to the token verification screen and
never to the dashboard, and the signin keeps from-invitation. Add the
login-from-token cases with and without the token, and fix the memory
statements about action, the flow table and from-invitation.

Closes #12849

AI-assisted-by: deepseek-v4.1-flash
2026-11-09 10:11:54 +01:00
Andrey Antukh
9b1e18a478 🐛 Validate binfile library relations against the package (#12862)
Ignore imported library relations that point outside the
package or self-link a file on the v3 import path. Delete
leftover cross-team and self relations from file_library_rel
and their matching file_library_sync rows with migration
0158, sync rows first.

Move the get-files-rels SQL to a var, without behaviour change.

Cover the write-side invariants with state-based tests:
tampered manifests, package containment, self links and the
migration cleanup.

AI-assisted-by: deepseek-v4.1-flash
AI-assisted-by: muse-spark-1.3-contributor
(cherry picked from commit 0b8a428d6b28c35601793f8d05570dae614a2fd5)
2026-11-09 08:46:19 +00:00
Alexis Morin
5b9bf3cbba
🌐 Add translations for: French (Canada)
Currently translated at 96.5% (2279 of 2361 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-11-08 10:51:26 +01:00
Danial Shirali
44a0cc7147
🌐 Add translations for: Persian
Currently translated at 94.5% (2233 of 2361 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2026-11-08 10:51:22 +01:00
penpot-renovate[bot]
904cdd9716
⬆️ Update DHI base images (#12814)
- dhi.io/debian-base trixie-debian13-dev@61025ce (docker/images/Dockerfile.backend)
- dhi.io/debian-base trixie-debian13-dev@61025ce (docker/images/Dockerfile.backend)
- dhi.io/debian-base trixie-debian13-dev@61025ce (docker/imagemagick/Dockerfile)
- dhi.io/debian-base trixie-debian13-dev@61025ce (docker/devenv/Dockerfile)
- dhi.io/nginx 1.31.8-alpine3.24@34c5f1f (docker/images/Dockerfile.storybook)
- dhi.io/nginx 1.31.8-debian13-dev@f88f28c (docker/images/Dockerfile.frontend)
- dhi.io/node 24.22.2-debian13-dev@24214dd (docker/images/Dockerfile.media-processor)
- dhi.io/node 24.22.2-debian13-dev@24214dd (docker/images/Dockerfile.mcp)
- dhi.io/node 24.22.2-debian13-dev@24214dd (docker/images/Dockerfile.exporter)

Signed-off-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
2026-11-07 22:53:06 +01:00
penpot-renovate[bot]
75b3da5290
⬆️ Update dependency pnpm to v12.18.0 (#12876)
- pnpm 12.18.0 (docker/images/Dockerfile.media-processor)
- pnpm 12.18.0 (docker/images/Dockerfile.mcp)
- pnpm 12.18.0 (docker/images/Dockerfile.exporter)
- pnpm 12.18.0 (docker/devenv/Dockerfile)
- pnpm 12.18.0 (render-wasm/package.json)
- pnpm 12.18.0 (plugins/package.json)
- pnpm 12.18.0 (plugins/libs/plugins-styles/package.json)
- pnpm 12.18.0 (plugins/libs/plugins-runtime/package.json)
- pnpm 12.18.0 (plugins/libs/plugin-types/package.json)
- pnpm 12.18.0 (plugins/apps/table-plugin/package.json)
- pnpm 12.18.0 (plugins/apps/rename-layers-plugin/package.json)
- pnpm 12.18.0 (plugins/apps/poc-tokens-plugin/package.json)
- pnpm 12.18.0 (plugins/apps/poc-state-plugin/package.json)
- pnpm 12.18.0 (plugins/apps/plugin-api-test-suite/package.json)
- pnpm 12.18.0 (plugins/apps/lorem-ipsum-plugin/package.json)
- pnpm 12.18.0 (plugins/apps/icons-plugin/package.json)
- pnpm 12.18.0 (plugins/apps/example-styles/package.json)
- pnpm 12.18.0 (plugins/apps/e2e/package.json)
- pnpm 12.18.0 (plugins/apps/create-palette-plugin/package.json)
- pnpm 12.18.0 (plugins/apps/contrast-plugin/package.json)
- pnpm 12.18.0 (plugins/apps/composable-test-suite/package.json)
- pnpm 12.18.0 (plugins/apps/colors-to-tokens-plugin/package.json)
- pnpm 12.18.0 (package.json)
- pnpm 12.18.0 (media-processor/package.json)
- pnpm 12.18.0 (mcp/packages/server/package.json)
- pnpm 12.18.0 (mcp/packages/plugin/package.json)
- pnpm 12.18.0 (mcp/packages/common/package.json)
- pnpm 12.18.0 (mcp/packages/browser-extension/package.json)
- pnpm 12.18.0 (mcp/package.json)
- pnpm 12.18.0 (library/package.json)
- pnpm 12.18.0 (frontend/text-editor/package.json)
- pnpm 12.18.0 (frontend/packages/ui/package.json)
- pnpm 12.18.0 (frontend/packages/mousetrap/package.json)
- pnpm 12.18.0 (frontend/packages/draft-js/package.json)
- pnpm 12.18.0 (frontend/package.json)
- pnpm 12.18.0 (exporter/package.json)
- pnpm 12.18.0 (docs/package.json)
- pnpm 12.18.0 (common/package.json)
- pnpm 12.18.0 (backend/package.json)

Signed-off-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
2026-11-07 22:52:43 +01:00
Alexis Morin
dac470cc94
🌐 Add translations for: French (Canada)
Currently translated at 94.4% (2231 of 2361 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr_CA/
2026-11-07 08:51:35 +01:00
Stephan Paternotte
da8cdb734b
🌐 Add translations for: Dutch
Currently translated at 100.0% (2361 of 2361 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/nl/
2026-11-07 08:51:32 +01:00
Elena Torró
7ecf48c52c
⚡ Speed up uploading page shapes to wasm (#12854)
* ⚡ Compute text fonts once per shape when uploading to wasm

* ⚡ Write the shapes batch in place and coerce fills once

* 🐛 Free the upload buffer when a batch record write fails
2026-11-06 15:45:34 +01:00
Belén Albeza
988cde3c9d
✨ Make WebGL the default renderer for new accounts (#12880)
New profiles now store renderer `wasm` in their props at creation, so
every signup path (email, OIDC, LDAP, Nitrate, demo, CLI) starts on
WebGL. A renderer passed by the caller still wins.

Existing profiles without a stored renderer keep the frontend fallback
to SVG, so no backfill or version check is needed to leave them as they
are.

The `register-profile` audit event gains `renderer-default` when the
profile is created in that call, so signups before and after this
change can be told apart.

Closes #11855

AI-assisted-by: claude-opus-5-5
2026-11-06 14:44:24 +01:00
Álvaro Tejero-Cantero
87fc15b5aa
🐛 Open the first page in view mode when the URL has no page id (#12687)
A view URL without a page-id crashed builds with assertions enabled:
the viewer passes a nil page-id to `initialize`, whose schema accepted
a missing key but not a nil value. Release builds skip that check and
draw the first page, but every viewer event reads the page from the
route, so zoom to fit and fill, the next-board arrow, Navigate To
interactions, and comment pins did not work.

`initialize` now accepts a nil page-id, `fetch-bundle` makes it
optional, and `bundle-fetched` replaces a route without page-id by one
naming the file's first page. The viewer then initializes again on the
completed route, as it does when the user changes page. A file with no
pages keeps its route.

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

Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
2026-11-06 13:05:39 +01:00
Andrey Antukh
a77cb2608e
🐛 Normalize stored SVG attribute keys to kebab-case (#12006)
* 🐛 Normalize stored SVG attribute keys to kebab-case

The v3 reader already emits kebab-case keys, but birth stored the
camelCase React prop names, so import and export never converged:
fill rules painted wrongly and filter defs lost their blur.

Birth now normalizes every stored attribute key to kebab-case
through the `svg-props` whitelist (`attrs->kebab-props`), applied on
all element paths of the svg importer, on the file builder and on
defs. svg-raw `:content` is kebab-ized in spelling only and keeps
unknown keys, because the sidebar reads them. Non-spec attrs Penpot
must keep live in `penpot-extra-attrs`, seeded with `:stroke-style`.
Render boundaries translate back to camelCase for React and for the
wasm props, and the shape schema declares the closed kebab contract
derived from that same whitelist, so birth and validation cannot
drift.

Migration `0035-fix-shape-svg-attrs-and-defs` converts old files
once with the birth transforms, and carries back the svg-viewbox
rect normalization the withdrawn `0021` had. That old id leaves
`available-migrations`, so files recording it migrate again through
this one, and the binfile cleaner drops its own svg repair.

Review fixes included here: `setup-opacity` and the blend mode read
the stored keys, gradient stops accept `:style` as a CSS string or a
map, `inherit-attributes` reads `:style` so a child's inline style
beats the inherited group attr per CSS, and the svg-attrs menu shows
spec spelling (`viewBox`, `class`) through `stored-attr-display-name`.

Tests cover the migration, the closed svg-attr schemas and the
import/export round trip; the svg-content schema test pins its
sample seed, which dealt no svg-raw shape on unlucky runs.

Closes #11947

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

* 🐛 Keep legacy svg node tags and transforms valid in migration 0035

The closed svg node schemas require keyword tags and map attrs, but
stored files hold def and svg-raw content tags as strings, and some
node `:attrs` as nil. Migration 0035 left them as they were, so the
file data check that runs after migrating rejected those files and
they could not be opened. `normalize-node-tree` now turns string tags
into keywords and nil attrs into an empty map, at birth and in 0035.

With keyword tags, the svg defs renderer applies the shape transform
to user-space defs. Legacy `:svg-transform` values are plain maps,
which multiply into NaN matrices. `gmt/coerce` turns them into
matrices, in 0035 and in the binfile cleaner, because every v3 import
decodes the transform as a plain map again. Legacy masks and gradients
now render as designed in the svg renderer.

The binfile test helper calls `export-files!` with the current config
keys.

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

---------
2026-11-06 12:52:53 +01:00
Andrés Moya
4f02995a3c
🐛 Re-enable adding layout to a variant container (#12879) 2026-11-06 12:11:48 +01:00
Elena Torró
93ed713a29
🐛 Free the wasm renderer state on clean up (#12877)
Each page switch leaked the six wasm states (~200 MB on a large page).
Null state getters now panic instead of reading address 0.
2026-11-06 11:43:50 +01:00
María Valderrama
2df4d10bd5
✨ Enforce attribute-based organization permissions (#12868)
* ✨ Enforce attribute-based organization permissions

* 📎 Code review
2026-11-06 11:40:50 +01:00
Elena Torró
7fa225115e
⚡ Reuse uuid instances when decoding transit on the frontend (#12792) 2026-11-06 11:33:37 +01:00
Andrey Antukh
6a1b58c310 Merge remote-tracking branch 'origin/staging' into develop 2026-11-06 10:16:02 +01:00
Andrés Moya
018b857de1
🐛 Improve sync of layout item attributes in components (#12851)
* 🐛 Preserve layout item touched groups when switching variant

* 🔧 Add systematic tests to check layout items component sync

* 🔧 Add small updates after comments

* 🔧 Add tests for partial overrides
2026-11-06 09:51:12 +01:00
Andrey Antukh
0333410482 Merge remote-tracking branch 'origin/staging' 2.19.0-RC6 2026-11-06 09:50:49 +01:00
Andrey Antukh
22864cbfd7 📚 Update changelog 2026-11-06 09:50:15 +01:00
Andrey Antukh
55fb8c46fc 📎 Updat elibrary and mcp package version 2026-11-06 08:49:30 +01:00