24082 Commits

Author SHA1 Message Date
Gennadiy Ivashchenko
17042f7bea ♻️ Use uniform URI building pattern for OIDC redirects
Replace the ad-hoc `public-uri-with-path` helper with `build-public-uri`
that uses the standard `u/ensure-path-slash` + `u/join` pattern already
used throughout the backend and frontend.

This makes OIDC redirect URI construction consistent with the rest of
the codebase while correctly preserving subpath configuration.

For hash routes (frontend URLs), use `u/append-query-param` to place
query params inside the fragment, matching the expected URL structure.

Add 6 new tests covering subpath edge cases:
- Trailing slash in public-uri
- Query params inside hash fragments
- No-subpath fallback
- Multiple redirect functions with subpaths

AI-assisted-by: qwen3.7-plus
AI-assisted-by: gpt-5.6-luna
2026-10-08 13:02:39 +02:00
Andrey Antukh
a146fe2a73 ♻️ Address review findings on trusted alternate origins
Verify the OIDC state token once per callback and reuse the parsed
claims, removing the duplicate verification while preserving the
invalid-state error redirect.

Test that the trusted-origin middleware is registered in the root
router chain and ordered before auth and errors, and cover the OIDC
logout post-logout redirect with and without a trusted origin.

Warn at startup about malformed PENPOT_TRUSTED_ORIGINS entries, unify
the request stamp keyword spelling, and clean up the test helpers.

Closes #11625

AI-assisted-by: deepseek-v4.1-flash
2026-10-08 13:02:36 +02:00
Andrey Antukh
60e8bf134a ✨ Honor trusted alternate origins for backend links and OIDC
Add an optional PENPOT_TRUSTED_ORIGINS setting listing alternate origins.
When a request Origin matches, the backend uses it as the public base for
links generated while serving that request: email links, asset URLs,
audit and webhook link fields, and redirects.

A global http middleware stamps the trusted origin on the request and
binds the config public-uri to it for the request extent. OIDC
authorization stores the origin in the signed state so the callback
(a navigation without Origin) can rebuild the token-exchange
redirect_uri and its redirect targets on the alternate domain.

Unlisted origins and requests without Origin are unchanged. Only
bare origins are supported, and server-to-server or queued work
keeps the canonical public-uri.

Closes #11625

AI-assisted-by: deepseek-v4.1-flash
2026-10-08 12:50:20 +02:00
Andrey Antukh
c17eb2a804
📚 Document disable-email-verification team auto-add behavior (#11425)
The configuration docs claimed it is not possible to add someone to a
team without them accepting an invitation email, but the backend
auto-adds existing profiles to the team when the email verification
is disabled (create-invitation in app.rpc.commands.teams-invitations).

Replace the incorrect sentence with an accurate exception note, add
the missing disable-email-verification entry to the "Other flags"
reference, document the CLI-created profile auto-add behavior in the
docker getting-started guide, and describe the shortcut in the
developer authentication docs.

Closes #11424

Reported in #11376

AI-assisted-by: glm-5.3-flash
2026-10-08 12:44:39 +02:00
andrés gonzález
85ba670278
💄 Unify copy in the shortcuts settings, modals and sidebar (#12174)
* 💄 Unify copy in the shortcuts settings, modals and sidebar

Use one term per concept across the customizable shortcuts UI: "Custom"
and "Customize" for user changes, "shortcut" for the keys, and "Reset"
with "default" to go back to the defaults.

Rewrite the conflict message so it says what saving does, fix typos and
grammar, unify the empty states and rename the font size actions.

Give the reset all button, the search placeholder, the import column,
the invalid import error and the edit button aria-label their own keys,
and update the e2e tests to the new copy.

Closes #12173

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

* 💄 Reword the shortcuts sidebar link and custom empty state

The sidebar link no longer names a settings section that does not exist
in Penpot. The empty state of the custom tab now says there are no
custom shortcuts and points to the All tab by name.

AI-assisted-by: claude-opus-5-5
2026-10-08 12:00:17 +02:00
Eva Marco
c9146a30ab
🐛 Keep layer search and tree scroll across sidebar tab switches (#12117)
Switching sidebar tabs unmounts the layers panel. The search text and
filters lived only in component state, so on return the still open
search bar was treated as a new search and cleared. Keep that state per
file in a session atom, as the assets panel already does.

Closing or clearing the search mounts the tree container again, but the
restore hook only ran on mount or page change, so the tree went back to
the top. Let use-restore-scroll take an active? flag so each list
restores its own position whenever it is shown again, and drop the
saved search position on close so a new search starts at the top.

Closes #11972

AI-assisted-by: claude-opus-5-5
2026-10-08 11:45:35 +02:00
Eva Marco
31a079a7a2
🐛 Allow creating tokens when the file is its own tokens source (#12182)
Setting the local file back as the tokens source, or unlinking the
library that was the source, stored the file's own id in
:tokens-source. ensure-tokens-lib only created a tokens-lib when
:tokens-source was nil, so a file without a tokens-lib never got one,
and every token change was dropped without any error.

Now ensure-tokens-lib also creates the tokens-lib when the source is
the file itself, which fixes files already in that state, and
set-tokens-source removes the attribute instead of storing the file's
own id.

Closes #12180

AI-assisted-by: claude-opus-5-5
2026-10-08 09:55:18 +02:00
Eva Marco
41efc3558a
🐛 Fix nested libraries vanishing from the libraries modal (#12031)
* 🐛 Fix nested libraries vanishing from the libraries modal

The "This file" tab nests each linked library under a linked library
that uses it, taken from the team shared files. A library with such a
parent was removed from the top level and only drawn under its parent
if the parent was at the top level itself. So:

- In a chain of two or more levels (file -> A -> B -> C), C was lost.
- In a cycle (A uses B and B uses A), both had a parent, none was at
  the top level, and both were lost.

The libraries showed while the shared files were loading, and vanished
when they arrived.

Build the list as a tree instead: direct libraries and libraries not
used by any other linked library are roots, nested libraries follow
their parent at any depth, and each library is added only once, even
when the dependencies form a cycle.

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

* ♻️ Move the linked libraries tree to sort-linked-libraries

Extract the code that orders the linked libraries as a tree from the
file tab to a public function, so it can be tested on its own. Add
tests for a cycle with a direct root, a cycle with no root, a deep
chain and no dependencies yet.

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

* ♻️ Use connected-to to find library parents when sorting

The caller already computes :connected-to for each linked library, the
reverse index of the dependencies map. sort-linked-libraries rebuilt
the same relation from dependencies. Read it from :connected-to
instead, so the function takes only the libraries and the component
memo no longer depends on dependencies.

Reword the cycle-without-root test to say it models the unknown direct
flag, and add tests for a diamond, a self-dependency, a cycle of three
without root, a parent that is not a linked library, and for no
library being lost or duplicated.

AI-assisted-by: claude-sonnet-5-5

---------

Co-authored-by: Andrés Moya <andres.moya@kaleidos.net>
2026-10-08 09:54:46 +02:00
Elena Torró
b3a62eeb4b
🐛 Paint layout children in canvas z-order in vector exports (#12171) 2026-10-08 09:07:33 +02:00
Alejandro Alonso
30127e57a0
🐛 Skip spurious save when missing font cannot be remapped (#12164)
fix-deleted-fonts committed text and typography changes even when no
replacement font existed, so opening a page with a missing custom font
rewrote identical content and cleared position-data on every visit.

Only emit changes when the remapped content or typography actually
differs. Add focused unit coverage for the no-replacement and remapped
paths.

Closes #12151
2026-10-07 18:03:51 +02:00
Eva Marco
ddce3c2c95
🐛 Open the import dialog again from template links (#12131)
Template links (#?template=<url>) keep their params in a bare "#?"
fragment. Since routing moved to the query string, only "#/" legacy
hashes are translated, and the root route was an empty query string,
so the template param was dropped and the dashboard opened without the
import dialog. Route a "#?" fragment as the query token, and treat a
query without a screen param as the root, as before.

Logged out users also lost the template: get-teams requires a session
and its 401 ended the flow before the params were kept for after login.
Skip it for the anonymous profile. This part already failed on main.

Closes #12127

AI-assisted-by: claude-opus-5-5
2026-10-07 14:03:08 +02:00
Danny Shirely
6defe26a78 🐛 Keep per-side stroke widths out of inspect geometry (#12055)
Remove per-side stroke widths from both Inspect geometry panels so
Stroke is the only section that lists them. Keep dimensions, position,
corner radii, and transforms in Size and position.

Add rendered-component regressions for boards, rectangles, multiple
selection, uniform strokes, and CSS declarations.

Closes #12026

AI-assisted-by: GPT-6

Signed-off-by: Danial Shirali <danialshirali16@gmail.com>
Signed-off-by: Eva Marco <eva.marco@kaleidos.net>
Co-authored-by: Eva Marco <eva.marco@kaleidos.net>
2026-10-07 13:09:04 +02:00
Alonso Torres
29072e4c6b
🐛 Fix path editor crash on shortcut (#11996) 2026-10-07 13:07:07 +02:00
Alonso Torres
0d8bcf9637
🐛 Fix crash when drawing (#12101) 2026-10-07 12:32:14 +02:00
Luis de Dios
f962cf487c
🐛 Migrate legacy stroke width token references (#12018)
Stroke width tokens used to be applied to the single `:stroke-width`
token attribute. Applying tokens per side replaced it with the four
`:stroke-width-*` attributes, but no migration moved the existing
references, so files written before 2.19 look unapplied: the token
pill stays off, the Design tab shows a plain number and propagation
finds no update action.

Add migration 0031, which copies the legacy reference to every side,
keeps any side reference already present and drops the old key. It
covers page and component shapes.

Closes #12016

AI-assisted-by: deepseek-v4.1-flash
2026-10-07 11:17:40 +02:00
andrés gonzález
3f6b7a7959
✨ Take changelog highlights from the release highlight label (#12154)
* ✨ Take changelog highlights from the release highlight label

The 🚀 section was picked by the agent on each run and kept
forever, so a regeneration could add highlights product did not choose
and nobody could drop one. Make the `release highlight` label the only
source, as `breaking change` already is for 💥, and have the report
flag 🚀 entries that do not match the label.

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

* 📚 Update changelog for 2.19.0

Regenerate the 2.19.0 section from the milestone with the
update-changelog skill. Add the 79 closed issues that were missing,
drop entries labelled `no changelog` and #9263, which is not in the
milestone, and take 🚀 from the `release highlight` label.

AI-assisted-by: claude-opus-5-5
2026-10-07 10:42:54 +02:00
Andrey Antukh
806a9ee98e
⬆️ Update dependencies (#12150) 2026-10-07 10:41:29 +02:00
Andrey Antukh
7f0aa4f38e Merge remote-tracking branch 'origin/main' into staging 2026-10-07 10:23:37 +02:00
Andrey Antukh
b2d688a04c 💄 Stringify ids in comp-processors log props
Wrap every id prop in the comp-processors warn logs
with str so the log shows plain text instead of
the #uuid reader form. Also use :hint instead of
:msg and lowercase text, as the rest of the system
does. No logic changes, logging only.

AI-assisted-by: muse-spark-1.3
2026-10-07 08:17:33 +00:00
Eva Marco
2f47be6c15
🐛 Show the size badge on paths and straight lines (#12066)
* 🐛 Show the size badge on paths and straight lines

The badge was hidden when the shape was smaller than the badge in
either side. Flat paths have a zero width or height, so they never
showed it, and straight two-point paths hid it on purpose.

Paths now only compare their longest side with the badge width. A
straight line compares its length, and the badge runs along the
line, rotated with it and kept upright. Near the bottom of the
viewport the badge moves to the other side of the line.

The badge now reads the size from the shape :selrect, like the
measures inputs, so a rotated path shows the same values in both
places. The three badge branches share a single render block.

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

* ♻️ Extract the size badge layout and add tests for it

Move the badge geometry out of selection-size-badge* into the pure
size-badge-layout function, so it can be tested without rendering.
The component now only picks the color and draws the result.

Add unit tests for rects, flat paths and straight lines: when the
badge hides, where it sits, how it rotates and how it moves when it
reaches the bottom of the viewport. Keep the line rotation between
-90 and 90 degrees instead of returning 360 for lines drawn leftward.

The e2e test still expected straight lines to hide the badge. It now
checks that the badge shows and follows the line angle.

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

* ✨ Show the length of straight lines in the size badge

A straight two-point path is measured by its length, not by the box
around it, so its size badge now shows the length with two decimals at
most instead of width x height. Other shapes and paths keep width x
height.

The shorter text makes the badge narrower, so it also shows on shorter
lines; the rule is the same, the badge needs the line to be at least as
long as the badge.

Relates to #12064

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

* 🐛 Hide the path size badge when the path is lower than it

Paths other than a straight line only hid the size badge when their
longest side was narrower than it, so a wide but low path, or a narrow
but tall one, still showed it. Flat paths no longer need that special
case, since straight lines get their own length badge. Use the same
rule as any other shape: hide the badge when either the width or the
height of the path is smaller than the badge.

AI-assisted-by: claude-opus-5-5
2026-10-07 09:18:57 +02:00
Andrey Antukh
e7041234f3 📎 Set library (sdk) version to 1.2.0 2026-10-07 08:49:32 +02:00
Andrey Antukh
84f068aca7 Merge remote-tracking branch 'origin/main' into staging 2026-10-06 18:59:36 +02:00
Andrey Antukh
5b02e4d898 ✨ Add find-project-anomalies skill and helper
Check a milestone against the Main project board with
scripts/project-anomalies.py (check writes
tmp/<MILESTONE>-ANOMALIES.md): open issues with merged PRs,
milestone mismatches either way, needs triage labels and
unassigned non-community issues. Outsiders resolve in bulk;
only a missing milestone auto-fixes, on confirmation.

Teach scripts/gh.py batched issue lookup (issue subcommand),
milestone on prs output, plus assignees and projects fields,
and retry transient HTTP 504s.

AI-assisted-by: muse-spark-1.3
2026-10-06 16:58:08 +00:00
Andrey Antukh
4bdded86be ♻️ Extract changelog checks into scripts/changelog.py
Move the inline programs out of the update-changelog skill into
scripts/changelog.py (check-merged, cross-ref, report) and shrink
the skill to the workflow alone.

Teach scripts/gh.py to retry transient HTTP 504s, resolve lookups
in batches of 50 (new issue subcommand, milestone on prs output),
and report 💥 entries without the breaking change label.

Add the nine missing 2.17.0 changelog entries found while
trying the new flow.

AI-assisted-by: muse-spark-1.3
2026-10-06 16:26:37 +00:00
Eva Marco
d046f6997d
🐛 Keep assets list view scroll position across sidebar tabs (#12115)
In list view the height of each component row depended on its
thumbnail, which renders deferred. When the Assets tab mounted again,
rows were shorter until the thumbnails appeared, so the restored
scrollTop landed several rows further down and scroll anchoring kept
it there. Reserve the thumbnail height in the row grid so rows keep
their size from the first render.

Closes #11971

AI-assisted-by: claude-opus-5-5
2026-10-06 16:55:16 +02:00
Eva Marco
fcbb8f1fc0
🐛 Keep derived layout paddings non-negative (#12129)
Adding a flex or grid layout to a board with children derives the
paddings from the distance between the board and its children. A child
that overflows the board, or floating point noise after "Resize board
to fit content", gave negative paddings. The shape schema rejects them
since paddings must be non-negative, so update-file failed and autosave
stopped. Clamp the derived paddings to 0, as the derived gaps already
are.

Closes #12107

AI-assisted-by: claude-opus-5-5
2026-10-06 16:34:28 +02:00
Andrey Antukh
51ae83202e Merge remote-tracking branch 'origin/main' into staging 2026-10-06 15:56:55 +02:00
Andrey Antukh
0ccd75487f ⬆️ Update dependencies on devenv 2026-10-06 15:43:38 +02:00
Eva Marco
5981d1ee66
🐛 Open a never scrolled token set at the top of the list (#12119)
The tokens list container stays mounted when another token set is
selected; only the restore key changes. With no saved position for the
new set, use-restore-scroll did nothing, so the list kept the scroll
position of the previous set. Scroll to the top when nothing is saved
for the key. Freshly mounted panels are already at the top, so Layers
and Assets do not change.

Closes #11973

AI-assisted-by: claude-opus-5-5
2026-10-06 15:40:24 +02:00
Andrey Antukh
ff7a66f519 📚 Update changelog 2.18.3 2026-10-06 14:25:50 +02:00
Andrey Antukh
10504ff2bf
🐛 Sanitize SVG on binfile import (#12105)
* 🐛 Sanitize smuggled SVG on binfile import

Route imported SVG storage objects through the SVG sanitizer on
all three binfile paths (v3, v1, v2), closing the second bypass
of GHSA-ffhp-m958-qxvr. Integrity checks still run on the raw
bundle bytes first; only the persisted copy is the sanitized one.

Adds a shared sanitize-imported-svg helper with unit tests and a
v3 export-tamper-import test proving smuggled scripts no longer
survive the import.

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

* 🐛 Normalize SVG content-type on binfile import

Close the residual GHSA-ffhp-m958-qxvr bypass where a crafted
content-type spelling (uppercase, parameters) skipped the import
sanitizer. Detection now uses a shared case-insensitive predicate,
stored values are canonicalized, and the v3 re-read honors the
import size limit.

Extends the tamper tests to obfuscated spellings and adds
exhaustive v1 coverage, including the tempfile branch.

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

* 🐛 Gate binfile content-type on known storage types

Define the complete set of storage content-types in
app.common.media and enforce it on the binfile import schema, so
unknown types fail closed instead of passing through. Detection
keeps a single normalization at the boundary with an exact
predicate, and the string helpers use cuerdas.

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

* 🐛 Keep only produced types in storage-object-types

Drop apng, avif, penpot and plain text from the set: no flow
stores them, they only exist in the extension mapping table.
Every member must have a producing code path; unproduced types
stay out until some flow actually stores them.

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

* 🐛 Reject unknown content-type on v1 binfile import

Enforce the storage content-type allowlist on the v1 import
path, like v3 already does via schema. Bundles declaring types
outside the set, or none at all, fail closed with the same
media-type-not-allowed error as uploads. The sanitize branch is
explicit: SVG bytes are sanitized, anything else passes through.

AI-assisted-by: muse-spark-1.3-contributor
2026-10-06 14:21:12 +02:00
Eva Marco
ff74c38bbf
🐛 Keep per-side token attributes when remapping a token (#12091)
Renaming a token with "Remap tokens" grouped the references by container
and wrote the union of their attributes onto every shape in it. A shape
with a stroke width token on one side got the token on all four sides
when another shape in the same container had it on every side.

Group the references by shape, so each shape is only renamed on the
attributes it already had the token on.

Closes #12075

AI-assisted-by: claude-opus-5-5
2026-10-06 13:49:16 +02:00
Eva Marco
582f943228
🐛 Show stroke width tokens in the inspect stroke panel (#12096)
Since stroke widths can be set per side, stroke width tokens are stored
in the four per-side attributes, not in :stroke-width. The inspect stroke
panel still read :stroke-width for Border width and had no token lookup
for the per-side widths, so it showed the raw value instead of the token.

Read the token of each side for the per-side widths, and the token shared
by every side for Border width. Files that are not migrated yet keep the
token in :stroke-width, so Border width still falls back to it.

Closes #12029

AI-assisted-by: claude-opus-5-5
2026-10-06 13:48:56 +02:00
Eva Marco
dacfac17bb
🐛 Show the top stroke width in the old render (#12093)
* 🐛 Show the top stroke width in the old render

With the old render, the stroke width input showed "Mixed" when the four
sides had different widths or tokens. The old render only draws the top
side, and the input must show it as the global stroke width.

Move the input value and token logic into two pure functions. When per
side strokes are disabled, both return the top side: its width and its
token, or no token when the top has none.

Closes #12063

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

* 🐛 Add e2e tests for the stroke width input in both renders

Cover the global stroke width input when the four sides differ. With the
new render it shows Mixed. With the old render it shows the top side:
its width, or its token when the top has one.

The old render cannot create per side data from the UI, and switching
the render reloads the page and drops the test state. The tests patch
the existing stroke token fixture instead, appending the per side fields
to a rectangle and a board.

Relates to #12063

AI-assisted-by: claude-opus-5-5
2026-10-06 13:33:36 +02:00
Eva Marco
d70b754652
🐛 Lay out token pills in nested groups side by side again (#12120)
The nested group wrapper only turned into a wrapping flex row when it
had a direct button child. Since the pills are wrapped in the DS
tooltip (#11900), the button is no longer a direct child, so pills in
nested groups were stacked one per row. Match pills as "any child that
is not a group" instead, so the rule no longer depends on the pill's
own markup.

Also drop the .token-pill rules of this module: CSS modules prefix
them with the token_tree scope, so they never matched the pill button.

Closes #12099

AI-assisted-by: claude-opus-5-5
2026-10-06 13:03:38 +02:00
Eva Marco
3fe48727ea
🐛 Hide import file rows while the import error list is shown (#12121)
When the import modal content moved to import-files-stage* (#9958), the
"error list or file rows" if became a when followed by rows that always
render. A non-.penpot file yields an analyze error entry with no name,
so the modal showed an empty error row below the error list. Render the
file rows only when the error list is not shown, as before.

Closes #12116

AI-assisted-by: claude-opus-5-5
2026-10-06 12:54:39 +02:00
Elena Torró
510173199b
🐛 Show 'Export complete' toast message after a successful export (#12118) 2026-10-06 11:31:52 +02:00
Elena Torró
70dfe90571
🐛 Clear doc atlas when canvas background color changes (#12112) 2026-10-06 10:34:47 +02:00
breken
b41445d092
🐛 Keep background plugins registered when another plugin opens (#11929)
Loading a plugin closed every non-background plugin and then emptied the
runtime registry, dropping background plugins (allowBackground, such as
the MCP plugin) that were left running. The registry is what routes a
plugin iframe's postMessage traffic to its plugin and what ɵunloadPlugin
searches, so after the user opened any other plugin the MCP plugin never
received task requests from its UI (MCP tool calls timed out) and could
no longer be unloaded.

Keep background plugins in the registry and drop only the plugins that
were closed.

AI-assisted-by: claude-opus-5.5

Signed-off-by: breken-ai <312387581+breken-ai@users.noreply.github.com>
Co-authored-by: breken-ai <312387581+breken-ai@users.noreply.github.com>
2026-10-05 20:17:04 +02:00
Andrey Antukh
753e98bb9f 🐛 Raise default binfile import text total to 500MiB
Giant legitimate exports hold hundreds of MB of JSON across
hundreds of thousands of KB-sized entries without tripping any
per-entry cap, so the 200MiB cumulative default rejected them out
of the box. Raise it to 500MiB; the 20MiB per-entry cap still
bounds single-entry inflation. Pin the floor with a regression
test.

AI-assisted-by: muse-spark-1.3-contributor
2026-10-05 18:31:16 +02:00
Andrey Antukh
4b70c9aa21 🐛 Reserve S3 download path without creating the file
The S3 file download opens the temp file with CREATE_NEW, so it
fails when the path already exists. tempfile creates the empty
file on reserve, which made every large download depend on the
SDK deleting the placeholder between its own retries.

Add tmp/temp-path, which reserves a unique path and registers
cleanup without creating the file, and use it for S3 downloads.
The old retry on FileAlreadyExists stays as a safety net for a
name clash.

AI-assisted-by: muse-spark-1.3-contributor
2026-10-05 17:16:47 +02:00
Andrey Antukh
ab58922f2c
🚑 Revoke file access when a member leaves the team (#12097)
Delete the team-scoped file, project and pin relations on leave-team
and delete-team-member, require a live team membership in the file,
project, search and listing permission queries, and clean
pre-existing orphan rows with a migration. Former members now get
not-found on file actions instead of keeping read and rename access
(GHSA-v9r9-h77c-55m2).

AI-assisted-by: muse-spark-1.3-contributor
2026-10-05 16:47:07 +02:00
David Barragán Merino
2c4da98131 🐛 Request pull-requests permission in auto-label token
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-10-05 14:38:06 +02:00
David Barragán Merino
6435261359 🔧 Use client-id instead of app-id in auto-label workflow
actions/create-github-app-token v3 deprecates the app-id input in
favour of client-id. Switch to the new TRIAGE_APP_CLIENT_ID secret.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-10-05 14:38:06 +02:00
dependabot[bot]
a3ba4702d7 ⬆️ Bump the github-actions group across 1 directory with 8 updates
Bumps the github-actions group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/create-github-app-token](https://github.com/actions/create-github-app-token) | `1.12.0` | `3.2.0` |
| [actions/github-script](https://github.com/actions/github-script) | `7.1.0` | `9.0.0` |
| [actions/checkout](https://github.com/actions/checkout) | `6.1.0` | `7.0.1` |
| [pnpm/setup](https://github.com/pnpm/setup) | `2.0.2` | `3.0.0` |
| [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) | `4.3.1` | `6.3.0` |
| [actions/cache/restore](https://github.com/actions/cache) | `5.1.0` | `6.1.0` |
| [actions/cache/save](https://github.com/actions/cache) | `5.1.0` | `6.1.0` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `7.0.0` | `8.0.1` |



Updates `actions/create-github-app-token` from 1.12.0 to 3.2.0
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Changelog](https://github.com/actions/create-github-app-token/blob/main/CHANGELOG.md)
- [Commits](d72941d797...bcd2ba4921)

Updates `actions/github-script` from 7.1.0 to 9.0.0
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](f28e40c7f3...3a2844b7e9)

Updates `actions/checkout` from 6.1.0 to 7.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](d23441a48e...3d3c42e5aa)

Updates `pnpm/setup` from 2.0.2 to 3.0.0
- [Release notes](https://github.com/pnpm/setup/releases)
- [Commits](84cb39b217...fbda4c85fc)

Updates `aws-actions/configure-aws-credentials` from 4.3.1 to 6.3.0
- [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases)
- [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md)
- [Commits](7474bc4690...e1253824e5)

Updates `actions/cache/restore` from 5.1.0 to 6.1.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](caa2961268...55cc834586)

Updates `actions/cache/save` from 5.1.0 to 6.1.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](caa2961268...55cc834586)

Updates `actions/download-artifact` from 7.0.0 to 8.0.1
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](37930b1c2a...3e5f45b2cf)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/github-script
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: pnpm/setup
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: aws-actions/configure-aws-credentials
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/cache/restore
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/cache/save
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-05 14:38:06 +02:00
Andrey Antukh
cf46b53bcb 📚 Update changelog 2.18.2 2026-10-05 12:18:37 +02:00
Andrey Antukh
4a4f50e7ea 🔥 Stop printing separator lines in reports
Drop the equals-sign banners from the CLJS throwable formatter,
so new frontend error reports are born without them. The JVM
branch never printed them. Adds a test asserting full reports
carry sections but no separator runs.

AI-assisted-by: Muse Spark 1.3 Free
2026-10-05 12:17:06 +02:00
Shreyash Agare
331e9168b5
🐛 Keep interactions of flex children when syncing components (#11788)
* 🐛 Keep interactions of flex children when syncing components

The flex-child sync between main and copy children propagated every
attribute in swap-keep-attrs, which includes :interactions. Since
:interactions has no sync group, the touched check never protected it,
so any change that reflowed a flex component (like toggling a child's
visibility) overwrote the copy children's interactions with the main's.

Closes #11746

AI-assisted-by: claude-opus-5
Signed-off-by: Shreyash Agare <264953665+ShreyashAgare26@users.noreply.github.com>

* 🐛 Add another test and simplify comments

---------

Signed-off-by: Shreyash Agare <264953665+ShreyashAgare26@users.noreply.github.com>
Co-authored-by: Shreyash Agare <264953665+ShreyashAgare26@users.noreply.github.com>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-10-05 12:04:11 +02:00
Andrey Antukh
238a4d8613
🐛 Sanitize SVG namespace-prefixed elements and attributes (#12073)
* 🐛 Sanitize SVG namespace-prefixed elements and attributes

The SVG sanitizer compared qualified tag and attribute names against
its blocklists. The XML parser keeps the namespace prefix in the
keyword name (❌script), so a script element written with a prefix
bound to the SVG namespace passed sanitization untouched and was
later served as image/svg+xml, executing in the Penpot origin.

Match tags and attributes on the local name (the part after the last
colon) instead, so dangerous elements and attributes are removed no
matter which prefix they use. Legitimately prefixed attributes such
as xlink:href keep working.

Closes #12071

AI-assisted-by: glm-5.3-flash

* ♻️ Cover prefixed SVG vectors and hoist sanitizer blocklist

Apply the code review findings on top of the sanitizer fix:

- Add a test for the javascript: href vector using a prefix bound to
  the xlink namespace, the other payload variant the old exact-key
  check missed.
- Add a test asserting that legitimately prefixed attributes such as
  xml:space are preserved by the local-name matching.
- Hoist the dangerous-tags blocklist next to the other sanitizer
  policy defs and bind the keyword name once in local-name.

Relates to #12071

AI-assisted-by: glm-5.3-flash
2026-10-05 11:56:59 +02:00
Andrey Antukh
b6a108c456 📚 Restrict new ! suffix to app.db and transients
Default is now no `!` for new functions. Existing `!`
stays as legacy until its gradual refactor.

Allowed cases live in clojure/idioms: mutating public
app.db API and functions working over transients. Any
further case must be registered there first.

AI-assisted-by: muse-spark-1.3-contributor
2026-10-05 09:54:46 +00:00