24466 Commits

Author SHA1 Message Date
Eva Marco
33d304c0af
♻️ Use DS icons in the dashboard sidebar (#12534)
* ♻️ Use DS icons in the dashboard sidebar

The search, clear search, pinned projects placeholder and logout icons
of the dashboard sidebar use the DS icons, and they get their color
from the color property. The two search buttons had untranslated test
names as their labels ("dashboard-search"); they are now named "Search"
and "Clear search". The pin keeps its place: the DS small icon draws
12px inside a 16px box, so its margins shrink by 2px.

New key: dashboard.clear-search (en, es). The dashboard page object
looks the search button up by its new name. A visual test covers the
search box with and without a term, the pinned projects placeholder and
the profile menu.

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

* ♻️ Move dashboard sidebar styles off the deprecated styles

Legacy color aliases resolve to their DS tokens, deprecated mixins,
sizes and font variables are inlined with DS values, physical
properties become logical ones and the DS typography module is used
without a namespace. An invalid max-width with two values, which the
browser ignored, is gone.

Two rules that match nothing are commented out with a TODO: the profile
menu arrow svg rule (the arrow is the DS icon svg itself) and the
upgrade plan section, which sidebar.cljs does not use.

refactor/ stays only for the profile menu placeholders (%menu-dropdown,
%menu-item-base) and the shared dashboard header styles
(common-dashboard). No visual change.

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

* 🔥 Remove unused styles from the dashboard sidebar

The SCSS refactor of the dashboard sidebar left rules commented out with
a TODO because the module does not use them, or because they target an
svg inside a DS icon that is the svg itself. CSS modules scope the
classes to this module and no other source uses them, so they go:
.profile-dropdown-item .open-arrow svg, .upgrade-plan-section,
.penpot-free, .no-limits, .power-up. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-27 11:41:19 +01:00
Eva Marco
afd838ea5b
♻️ Use DS buttons in the dashboard team modals (#12537)
* ♻️ Use DS buttons in the dashboard team modals

The close buttons of the create/rename team modal, the team permission
notice and the two "before you leave" modals become ghost DS icon
buttons named "Close"; the class that places them goes on the tooltip
wrapper. The cancel and "promote and leave" inputs of the leave modals
become DS buttons: secondary, and destructive once a member is picked
(primary and disabled before).

Two fixtures make the leave and reassign modal testable (the logged-in
user owns the second team, which has another member). A visual test
covers the create team modal and the leave and reassign modal.

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

* ♻️ Move dashboard team modal styles closer to the DS

The change owner and team form stylesheets resolve their legacy color
aliases, spacing and typography mixins to DS tokens and use the DS
typography module without a namespace. The legacy uppercase title
becomes the DS headline-small typography, which differs only in its
size (12px instead of 11px): the "before you leave" title grows 1px and
the modal 2px. Unused selectors are commented out with a TODO.

refactor/ stays only for the modal placeholders (%modal-*), which move
with the modals refactor, and the input placeholders.

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

* 🔥 Remove unused styles from the dashboard team modals

The SCSS refactor of the dashboard team modals left rules commented out
with a TODO because the module does not use them, or because they target
an svg inside a DS icon that is the svg itself. CSS modules scope the
classes to this module and no other source uses them, so they go:
.input-wrapper, .cancel-button, &.danger. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-27 10:58:13 +01:00
Eva Marco
19fc9ebce8
♻️ Use DS icons in the viewer flows and interactions menus (#12520)
* ♻️ Use DS icons in the viewer flows and interactions menus

The flows and interactions triggers in the viewer header were divs with
a click handler, and their menus lived inside them. They are now
buttons with aria-expanded and aria-haspopup, and each menu sits next
to its button in a wrapper, since a button cannot hold the menu
entries. The play, arrow and tick icons use DS icons. The menu entries
stay as they are.

A visual test covers the interactions menu closed and open; the test
files have no flows, and the flows trigger shares its styles.

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

* ♻️ Move viewer interactions styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties. The DS icons take their color from the
text and no longer shrink, so the old icon placeholders go away. The
DS body-small line-height is more compact, so the trigger labels sit
1px higher. The refactor/ import stays only for the shared menu
placeholders.

AI-assisted-by: claude-opus-5-5
2026-10-27 10:47:07 +01:00
Eva Marco
9854f8e54f
♻️ Move dashboard subscription styles off the deprecated styles (#12538)
* ♻️ Move dashboard subscription styles off the deprecated styles

The subscription stylesheet inlines the deprecated button-style and
flex-center mixins, resolves its legacy color aliases and sizes to DS
tokens, uses logical properties and the DS typography module without a
namespace. Three rules that match nothing (the arrow svg, .cta-link and
.open-arrow) are commented out with a TODO.

The subscription controls need no DS change: the plan card, the current
plan card and the manage subscription link have no DS equivalent, and
they use no legacy icons. refactor/ stays only for the power up menu
item placeholder and the shared dashboard header styles.

A visual test covers the sidebar plan card (closed and open), the team
settings plan and the members seats warnings. No visual change.

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

* 🔥 Remove unused styles from the dashboard subscription

The SCSS refactor of the dashboard subscription left rules commented out
with a TODO because the module does not use them, or because they target
an svg inside a DS icon that is the svg itself. CSS modules scope the
classes to this module and no other source uses them, so they go:
.icon-dropdown svg, .cta-link, .open-arrow. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-27 10:23:55 +01:00
Eva Marco
411f661bdb
♻️ Use DS buttons in the delete shared libraries modal (#12554)
* ♻️ Use DS buttons in the delete shared libraries modal

The delete shared libraries modal (unpublish a library, delete a shared
file or project) gets a ghost DS icon button named "Close", placed on
the tooltip wrapper, and DS buttons for cancel (secondary) and accept
(destructive for the danger style, primary otherwise) instead of raw
inputs. The buttons are narrower and the accept button takes the DS
destructive red.

A new empty library references fixture and a visual test cover the
unpublish library modal from the workspace main menu.

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

* ♻️ Move delete shared libraries modal styles closer to the DS

The delete shared libraries stylesheet resolves its legacy modal color
aliases to DS tokens, uses a logical property and the DS typography
module without a namespace. refactor/ stays only for the modal
placeholders (%modal-*), which move with the modals refactor. Two
unused selectors are commented out with a TODO. No visual change.

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

* 🔥 Remove unused styles from the delete shared libraries modal

The SCSS refactor of the delete shared libraries modal left rules
commented out with a TODO because the module does not use them, or
because they target an svg inside a DS icon that is the svg itself. CSS
modules scope the classes to this module and no other source uses them,
so they go: &.transparent, .list-wrapper. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-27 10:20:11 +01:00
Pablo Alba
c65a1a3c5e 🐛 Fix swapped nested component loses absolute position after a lib update 2026-10-27 09:46:32 +01:00
Pablo Alba
e4c1b154e3
✨ Add profile name and email to nitrate audit log (#12636)
* ✨ Add profile name and email to nitrate audit log

* ✨ Mr changes
2026-10-27 09:30:28 +01:00
David Barragán Merino
e480d1b669 👷 Use only the closest tag in the build key
The full `git describe` output is not stable across checkouts: the
length of its -g<sha> suffix depends on the local object count, so
build-bundle and build-docker could compute different keys for the same
commit on different runner workspaces. The sha already pins the commit,
so the closest tag is enough to tell versions apart.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-10-26 20:38:48 +01:00
David Barragán Merino
4b13562d02 👷 Use only the closest tag in the build key
The full `git describe` output is not stable across checkouts: the
length of its -g<sha> suffix depends on the local object count, so
build-bundle and build-docker could compute different keys for the same
commit on different runner workspaces. The sha already pins the commit,
so the closest tag is enough to tell versions apart.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-10-26 20:30:08 +01:00
David Barragán Merino
dbcd1e0d03 👷 Include the version in the bundle and image build key
The bundle stamps the version into version.txt, so a commit that gets a
new tag (2.18.1-RC1 -> 2.18.1) needs a new bundle and image set. Keying
them by commit sha alone reused the RC artifacts for the final release.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-10-26 19:55:53 +01:00
David Barragán Merino
cf19d7f870 👷 Include the version in the bundle and image build key
The bundle stamps the version into version.txt, so a commit that gets a
new tag (2.18.1-RC1 -> 2.18.1) needs a new bundle and image set. Keying
them by commit sha alone reused the RC artifacts for the final release.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-10-26 19:55:11 +01:00
Eva Marco
01e0b32850
🐛 Remap library refs inside deleted components on absorb (#12232)
* 🐛 Remap library refs inside deleted components on absorb

Deleted components keep their shapes in :objects, to be restored and
to resolve their remaining copies. When a library was absorbed into a
file (export with libraries merged, unsharing or deleting the
library), absorb-assets only looked for usages in pages and in live
components. The shapes inside deleted components kept pointing to the
library, so they no longer matched their copies in the pages: the file
failed validation with :component-id-mismatch and could not be saved.

Also look for usages in deleted components, so their library
components, colors and typographies are remapped to the local file.

Relates to #10806

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

* 🐛 Persist pointer-map fragments when absorbing a library

absorb-library-by-file saved the file without its :features. Without
"fdata/pointer-map" there, encode-file does not persist the changed
fragments, so in files with pointer maps the pages and components
that absorb-assets remapped were never written. After unpublishing or
deleting a library, its consumers failed to load with "fragment not
found".

Pass :features to update-file!. Exporting with shared libraries does
not go through this path, so it was not affected.

AI-assisted-by: claude-opus-5-5
2026-10-26 17:56:31 +01:00
Eva Marco
f07452e36b
♻️ Use DS buttons and icons in the inspect code tab (#12521)
* ♻️ Use DS buttons and icons in the inspect code tab

"Copy all code" becomes a DS secondary button, and the expand panel
buttons ghost DS icon buttons labelled "Expand panel" (new key, en and
es). The collapse toggles of the CSS and markup blocks stay raw
buttons, since they sit in a 12px column; their arrow uses DS icons and
they get a name and aria-expanded. The copy buttons belong to the
shared copy button and move with it.

A visual test covers the code tab buttons, expanded and collapsed.

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

* ♻️ Move inspect code tab styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties. The 11px uppercase titles become the
12px DS headline-small, the only difference. The code typography keeps
its values with a TODO: the DS code-font names "Roboto Mono", while
the app loads "robotomono". The full-width "Copy all code" DS button
centers its label explicitly. The refactor/ import stays only for the
copy buttons, styled here until the shared copy button moves to the DS.

AI-assisted-by: claude-opus-5-5
2026-10-26 17:55:54 +01:00
Eva Marco
96b62f6475
♻️ Use DS buttons in the delete account modal (#12540)
* ♻️ Use DS buttons in the delete account modal

The close button of the delete account modal becomes a ghost DS icon
button named "Close"; the class that places it goes on the tooltip
wrapper. The cancel and delete buttons become DS secondary and
destructive buttons, so their labels no longer wrap and the delete
button takes the DS destructive red. The delete button keeps its test
id.

A visual test covers the modal at rest and with the close and delete
buttons hovered.

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

* ♻️ Move delete account modal styles closer to the DS

The delete account stylesheet resolves its legacy sizes, spacing,
mixins and color aliases to DS tokens and uses logical properties and
the DS typography module without a namespace. The legacy uppercase
title becomes the DS headline-small typography, which differs only in
its size (12px instead of 11px), so the title and the content below sit
1px lower. The organization counts used a fixed dark-theme gray; they
now use the themed DS secondary foreground. Two unused selectors are
commented out with a TODO.

refactor/ stays only for the modal placeholders (%modal-*), which move
with the modals refactor.

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

* 🔥 Remove unused styles from the delete account modal

The SCSS refactor of the delete account modal left rules commented out
with a TODO because the module does not use them, or because they target
an svg inside a DS icon that is the svg itself. CSS modules scope the
classes to this module and no other source uses them, so they go:
.fields-row, .select-title. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-26 17:17:46 +01:00
Eva Marco
376ecda94e
♻️ Move viewport top bar styles off the deprecated styles (#12488)
* ♻️ Move viewport top bar styles off the deprecated styles

The viewport top bars (view only bar of the inspect tab, path edition
bar) already use DS buttons. Move top_bar.scss to DS sizes, borders,
spacing tokens and logical properties, resolve the panel and shadow
color aliases, and drop the refactor import. The unused done-btn class,
the last user of the deprecated button placeholders here, goes away.
The two z-index values have no DS token, so they stay with a TODO.

Add a visual test of the view only bar; it looks the same as before,
pixel for pixel.

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

* ♻️ Use the body-small typography in the viewport top bar title

The title only set a 12px font size, which body-small already has.
Use the DS typography instead of the inline size and its TODO. The
view only bar looks the same, pixel for pixel.

AI-assisted-by: claude-opus-5-5
2026-10-26 15:53:05 +01:00
Pablo Alba
f2e5b7e20e
🐛 Fix swapped nested component loses absolute position after a lib update (#12556) 2026-10-26 15:24:18 +01:00
Eva Marco
86e01d56a2
🐛 Update canvas and keep local sets when switching tokens source (#12442)
Setting a library as tokens source did not update shapes that use tokens,
because set-tokens-source never ran the token propagation that every other
token operation runs. Run it after the commit.

Going back to the local tokens, or unlinking the library that was the
source, left no local set active: the file has a single tokens status, and
switching to a library replaced it with the library's one. Keep the status
of the file's own tokens lib aside in a new optional :own-tokens-status
attribute while an external library is the source, and restore it when the
file is its own source again. Exporting without libraries and absorbing a
deleted source library restore it as well.

Closes #12430

AI-assisted-by: claude-opus-5-5
2.19.0-RC2
2026-10-26 15:23:05 +01:00
Filip Sajdak
576bf4ed3d
✨ Close search and shortcuts panels with Escape (#12374)
* ✨ Close search and shortcuts panels with Escape

The workspace search and shortcuts panels could only be dismissed with
the mouse, which interrupts keyboard-first workflows.

A new use-escape-to-close hook gives each panel an on-key-down handler
that closes it on Escape while the user is working in it: the latest
pointer press or focus change must have landed inside the panel, or the
panel must hold focus when it opens (a panel that autofocuses its search
box, such as Shortcuts, is closed by the first Escape). Focus left
behind in a panel after the user moved on to the canvas does not count,
so Escape keeps its usual meaning there.

The handler never stops the event. The global Escape shortcut still
runs and finishes in-progress operations such as path drawing; an
earlier version stopped propagation and left a half-drawn path that
failed shape validation on the next commit.

In the search panel an open filters menu takes the first Escape and
the panel closes on the second.

The history panel needs no handler: Escape already closes it through
the :interrupt it emits.

Closes #9540

AI-assisted-by: claude-opus-5-5
AI-assisted-by: claude-sonnet-5-5
Signed-off-by: Filip Sajdak <filip.sajdak@siili.com>

* 📎 Add e2e test for closing panels with Escape

Cover the Escape handling added for the layers search and shortcuts
panels with four Playwright cases on the mocked workspace:

- Escape closes the layers search panel.
- With the search filters menu open, the first Escape closes only the
  menu and the second closes the panel.
- Escape closes the shortcuts panel right after it opens with `?`. The
  panel autofocuses its search box, so the first Escape must close it
  instead of only blurring the box.
- A path drawn while focus sits in the shortcuts panel is finished by
  Escape, which proves the panel does not swallow the global shortcut.

The first three cases fail on develop. The shortcuts case also fails
when the hook does not seed its tracker from the focused element.

AI-assisted-by: claude-sonnet-5-5
Signed-off-by: Filip Sajdak <filip.sajdak@siili.com>

* 🐛 Close History with Escape when opened by shortcut or menu

Only the header button opened History through initialize-history, which
removes the :document-history flag on the next :interrupt. The
Ctrl/Cmd+Alt+H shortcut and the "Version history" menu item toggled the
flag directly, so the global Escape left the panel open. Both now emit
initialize-history, like the header, and keep their own event origin.

Two e2e cases cover the shortcut and the menu; both fail without this
change.

AI-assisted-by: claude-sonnet-5-5
Signed-off-by: Filip Sajdak <filip.sajdak@siili.com>

* ♻️ Apply review suggestions to Escape-to-close

Use dom/child? instead of .contains in the Escape hook and drop the
redundant nil checks. Drive the path test through clickAt so its clicks
wait for the path tool. Add a negative Search case: Escape reaches the
panel after the last click landed on the viewport, and the panel stays
open. That case fails when the containment check is removed.

AI-assisted-by: claude-sonnet-5-5
Signed-off-by: Filip Sajdak <filip.sajdak@siili.com>

---------

Signed-off-by: Filip Sajdak <filip.sajdak@siili.com>
2026-10-26 14:42:43 +01:00
Eva Marco
e24e9dc747
♻️ Use DS buttons and icons in the viewer header (#12523)
* ♻️ Use DS buttons and icons in the viewer header

The section buttons, full screen and edit become ghost DS icon buttons
(the last two were spans), with aria-pressed where they toggle. Share
and "Log in or sign up" become DS buttons. The zoom widget becomes a
button that toggles its menu, like the workspace one, with DS zoom
buttons inside. The breadcrumb and the current board name become
buttons with aria-expanded, and the page menu moves next to its
button. The arrow and tick icons use DS icons. The logo stays: it is
brand art.

A visual test covers the header in two sections and both menus.

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

* ♻️ Move viewer header styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties, and the button placeholders go away.
The section buttons column goes from a fixed 92px to auto, since the
three 32px DS buttons no longer fit; the 1fr side columns keep it
centered. The zoom widget follows the workspace header one. The 11px
project name becomes the 12px DS headline-small. The global .selected
rule also highlighted the current page in the page menu: that row
keeps its highlight through its own rule. The refactor/ import stays
only for the shared menu placeholders; the options z-index (10) has no
DS token and keeps a TODO.

AI-assisted-by: claude-opus-5-5
2026-10-26 14:39:06 +01:00
Eva Marco
48a5875a71
♻️ Use DS icon buttons in the viewer board navigation (#12527)
* ♻️ Use DS icon buttons in the viewer board navigation

The previous, next and first board buttons of the viewer become
secondary DS icon buttons with the DS arrow and reload icons. They keep
their size and the viewer background at rest; on hover they take the DS
secondary look. The first board button had no accessible name; it is
now labelled. The position classes move to the tooltip wrappers, so the
fullscreen and comments sidebar offsets still apply.

New key: viewer.go-to-first-board (en, es). A visual test covers the
buttons at rest and on hover.

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

* ♻️ Move viewer styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties, and the file no longer imports
refactor/. The section scrollbar uses the DS custom-scrollbar mixin; the
text selection and placeholder styles that %new-scrollbar gave to the
comments layer stay, with DS tokens of the same value. An invalid
padding-right with four values, which the browser ignored, is gone.

The DS body-small typography differs from the old one only in a more
compact line-height, so the board counter sits 1px higher. The z-index
values (10, 2) have no DS token and keep a TODO. The .viewer-header,
.not-fixed, .fixed and .frame-children rules are not used by this module
and are commented out with a TODO.

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

* 🔥 Remove unused styles from the viewer

The SCSS refactor of the viewer left rules commented out with a TODO
because the module does not use them, or because they target an svg
inside a DS icon that is the svg itself. CSS modules scope the classes
to this module and no other source uses them, so they go:
.viewer-header, .not-fixed, .fixed, .frame-children g. No visual change.

AI-assisted-by: claude-opus-5-5
2026-10-26 12:48:43 +01:00
andrés gonzález
3f8aee7933
✨ Record imported file ids in the import-binfile audit event (#12491)
The RPC layer emitted the event as soon as the handler returned the
SSE response, before the import ran, so the event could not say which
files the import created. Emit it from inside the SSE handler once the
import ends, with the ids and count of the created files and whether
the import completed or failed.

Closes #12485

AI-assisted-by: claude-opus-5-5
2026-10-26 12:38:02 +01:00
Belén Albeza
577cbe43e4
🐛 Validate the text payload before pasting it (#12612)
Text copied in Penpot carries its content in a data attribute of the
clipboard HTML, which plugins and other apps can rewrite. The v3 editor
and canvas paste used it as is, so bad data could reach WASM and the
file and break the shape.

The payload is now dropped, and the paste falls back to plain text in
the caret style, unless its content passes the text shape schema, its
span geometry holds only safe numbers and its file and team ids are
uuids. Payloads in HTML over the paste size limit are not decoded.

Copied text shapes pasted into the editor get the same checks, plus a
numeric position; when they fail, nothing is pasted.

Closes #12167

AI-assisted-by: claude-opus-5-5
2026-10-26 12:36:13 +01:00
Andrés Moya
6f185c624f
🐛 Fix swap when the original main component is not available (#12333)
* 🐛 Fix swap when the original main component is not available

* 🐛 Change the shape-ref for the id
2026-10-26 12:25:00 +01:00
Alonso Torres
4fe73011ae
🐛 Reject restoring components of a linked library from plugins (#12611)
LibraryComponent.restore() did not check that the component belongs to
the current file's library. Called on a deleted component of a connected
shared library, it restored that component and saved the change to the
library file, while remove(), annotation and duplicate() reject such
handles with a validation error.

restore() now rejects them the same way: "Can only restore components
from the current file's library".

Closes #12607
2026-10-26 12:21:58 +01:00
Juan de la Cruz
55ad7d1da8
✨ Add new slides content for 2.19 release (#12185)
* ✨ Add new slides content for 2.19 release

* 🎉 Update user settings instructions in v2_19.cljs

Signed-off-by: Juan de la Cruz <delacruzgarciajuan@gmail.com>

---------

Signed-off-by: Juan de la Cruz <delacruzgarciajuan@gmail.com>
2026-10-26 12:11:12 +01:00
Eva Marco
68ecfb1234
🐛 Fix default shortcuts that fail on some layouts and browsers (#12506)
Fixes the problems found after #12326 and listed in #12327.

Mousetrap now tries several names for each key press, in order, and
fires the first one with a binding: letters and digits by keyCode when
a modifier is held, punctuation by position (layout map, or a US table
when the browser has none), the typed glyph, and the typed glyph
without the Shift it needed. With AltGr the typed glyph comes before
the position. The shortcut recorder asks Mousetrap for the key it
stores, so a recorded shortcut always fires.

This makes "?" and "+" work on every layout, Shift and Ctrl+Shift with
punctuation work in Firefox and Safari, Option+letter work on macOS,
and AltGr+º hide the UI on Linux with a Spanish layout.

Chrome on Linux returns the map of the first configured layout, not
the active one. Mousetrap now drops the map when an unmodified
punctuation key types something else.

The layout twins are no longer needed and are removed: "shift+'" made
"?" zoom to the selection on Spanish layouts. The Add node shortcut is
removed too: on US layouts its key is the "+" zoom key, and on Firefox
with a Spanish layout it cannot fire. The Firefox and Safari warning
in the shortcuts settings is gone, since those shortcuts now work
there. The user guide and translations follow these changes.

AI-assisted-by: claude-opus-5-5
2026-10-26 11:56:09 +01:00
Eva Marco
a5fd2df1f0
♻️ Make the grid track direction icon and the swap groups buttons (#12476)
* ♻️ Make the grid track direction icon a button

The direction icon at the start of each grid track value field selected
that column or row from a div with a click handler: no button role, no
name and no keyboard access. Make it a button labeled "Select column" or
"Select row", keeping its class and handler, and reset the browser
border so it looks the same. It stays a raw button: it is a segment of
the track field, not a DS button.

Add the workspace.layout-grid.editor.select-column and select-row keys
(en, es).

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

* ♻️ Make the component swap panel groups buttons

The groups of the component swap panel opened from a div with a click
handler: no button role and no keyboard access. Make each group a
button, keeping its class, title and handler, and reset the browser
button styles so it looks the same. Its name is the visible group name.

Add a visual test with a fixture whose components sit in two groups, so
the swap panel lists them.

AI-assisted-by: claude-opus-5-5
2026-10-26 11:38:33 +01:00
Eva Marco
bac62a27bf
♻️ Use DS icons in the viewer comments filter (#12519)
* ♻️ Use DS icons in the viewer comments filter

The "Comments" filter trigger in the viewer header was a div with a
click handler, and its menu lived inside it. It is now a button with
aria-expanded and aria-haspopup, and the menu sits next to it in a
wrapper, since a button cannot hold the menu entries. The arrow and the
ticks use DS icons. The menu entries stay as they are.

A visual test covers the filter closed and open.

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

* ♻️ Move viewer comments styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties. The DS icons take their color from the
text and no longer shrink, so the old icon placeholders go away. The
DS body-small line-height is more compact, so "Comments" sits 1px
higher. The refactor/ import stays only for the shared menu
placeholders. The z-index values (1 and 10) have no DS token and keep a
TODO.

AI-assisted-by: claude-opus-5-5
2026-10-26 11:36:46 +01:00
Pablo Alba
3cd073fc6a
✨ Add auto-repair for integrity error files (#12524)
* ✨ Add auto-repair for integrity error files

* ✨ MR changes
2026-10-26 11:04:06 +01:00
Eva Marco
ef17aea39e
♻️ Use DS icon buttons in the viewer thumbnails panel (#12518)
* ♻️ Use DS icon buttons in the viewer thumbnails panel

The expand, close and scroll buttons of the viewer thumbnails panel
become ghost DS icon buttons. They had no accessible name; they are now
labelled, and the expand button says whether the panel is expanded. The
rotated legacy arrow gives way to the DS arrow icons. The thumbnails
stay raw buttons, since they are list items.

New keys: labels.expand and viewer.thumbnails.scroll-left/right (en,
es). A visual test covers the panel collapsed, on hover and expanded.

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

* ♻️ Move viewer thumbnails panel styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties, and the file no longer imports
refactor/. The scroll buttons keep their 60px height through
--button-height and still show only on hover; their tooltip wrappers
take the grid cells. The DS body-small typography differs from the old
one only in a more compact line-height, so the counter and the board
names sit 1px higher. The panel z-index (10) has no DS token and keeps
a TODO.

AI-assisted-by: claude-opus-5-5
2026-10-26 10:33:11 +01:00
Eva Marco
9911f82087
♻️ Use a DS icon in the grid editor track options button (#12486)
* ♻️ Use a DS icon in the grid editor track options button

The options button of each grid track on the canvas drew a deprecated
icon and had no accessible name. Use the DS menu icon and label the
button "Track options". The button stays raw: it lives on the canvas,
where the track editor keeps a fixed screen size at any zoom.

The DS icon has a fixed 16px size, so its class scales it down with the
zoom (transform: scale(1 / zoom)), as the old svg rule did with its
width. It looks the same, but for a few antialiasing pixels.

Add the workspace.layout-grid.editor.track-options key (en, es) and a
visual test of a track at two zoom levels.

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

* ♻️ Move grid editor styles off the deprecated styles

Move grid_layout_editor.scss to DS sizes, borders, spacing tokens,
colors and logical properties, and drop the refactor import. The canvas
sizes keep their division by the zoom. The grid editor color aliases
resolve to their DS tokens, and the unused close-btn class, the last
user of the deprecated button placeholders here, goes away.

The track editor and the editing bar look the same as before.

AI-assisted-by: claude-opus-5-5
2026-10-26 10:32:06 +01:00
Eva Marco
7ff8df6ffb
♻️ Name the grid track expand buttons and make the annotation toggle a button (#12489)
* ♻️ Name the grid track expand buttons

The menu button that expands the columns or rows of a grid layout had no
accessible name. Label it "Columns" or "Rows" and expose its state with
aria-expanded. The track title next to it also toggles on click; it
stays a div, as a larger pointer target for the same action, so the
keyboard does not stop twice on it.

Add the workspace.layout-grid.editor.columns and rows keys (en, es).

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

* ♻️ Make the annotation title toggle a button

The annotation title of a component toggled from a div with a click
handler, and it holds the edit, delete, save and discard buttons, so it
cannot become a single button. Move the toggle to a button with the
arrow and the "Annotation" name, with aria-expanded, that takes the
whole free width of the title. While editing or creating, the title
stays text and no longer collapses the annotation on click, as its
cursor already suggested.

Extend the annotation visual test with the collapsed state; it looks the
same as before, pixel for pixel.

AI-assisted-by: claude-opus-5-5
2026-10-26 10:31:51 +01:00
Eva Marco
ac08e38c1a
♻️ Use DS buttons and icons in the export assets modal (#12517)
* ♻️ Use DS buttons and icons in the export assets modal

The export modal close button becomes a ghost DS icon button and the
progress toast close button an action one, which fits its 24px grid
column; both are labelled "Close". Cancel and Export become DS buttons.
The checkbox ticks and the toast icons use DS icons. The selection rows
stay raw buttons, since they are list rows, and so does the link-styled
retry/cancel button of the toast.

The export frames e2e spec now finds the Export button by role, and a
visual test covers the modal with all, some and no rows selected.

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

* ♻️ Make the export progress ring a button

The export progress ring in the header opens the export details, but it
was a div with a click handler. It is now a button named after the
export status, with aria-expanded, and keeps its look.

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

* ♻️ Move export assets modal styles off the deprecated styles

Spacing, sizes, borders, typographies and color aliases move to DS
tokens and logical properties, and the button placeholders go away.
The classes left without use are removed: the old modal buttons, the
old close icon, and the file export entries, which live in files.scss.

The refactor/ import stays only for the shared modal and checkbox
placeholders, which move to the DS with the modals and the checkboxes.
The checkbox placeholder shrinks a ticked icon to 12px, which pushed
the DS tick off center: the tick keeps its 16px box now, and does not
shrink inside the 14px flex box either.

AI-assisted-by: claude-opus-5-5
2026-10-26 10:31:34 +01:00
Eva Marco
29b00f8535
♻️ Use DS icons in the text palette and its menu (#12452)
* ♻️ Use DS icons in the text palette and its menu

The scroll arrows of the text palette and the tick of the selected
palette in its menu used deprecated icons; use the DS arrow and tick
icons instead. The arrows are full-height scroll buttons with a fade, not
DS button sizes, so they stay raw buttons.

Add a visual test that opens a file with 16 local typographies, so the
arrows show, and captures the palette at rest, with the pointer over the
right arrow, after scrolling and with its menu open. Its fixture is the
library fixture with more typographies.

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

* ♻️ Move text palette styles off the deprecated styles

Inline the button, flex and ellipsis mixins, move spacing, sizes and
borders to DS tokens and logical properties, and resolve the color
aliases to their DS tokens. The arrow, item and menu colors that change
on hover, disable or selection go through local variables, and the new
icons take them through currentColor instead of svg selectors. The three
typography lines share their ellipsis rule. The .disabled ::after rule
of the left arrow never matched and goes away; :disabled::after already
covers it.

The items, the empty message and the menu use the DS body-small
typography, whose line-height is 1.3 instead of 1.4. The arrows keep
their z-index, now commented, and the menu its legacy context menu one.

text_palette.scss and text_palette_ctx_menu.scss no longer import
refactor/.

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

* 🐛 Show the text palette scroll arrows at rest

The enabled scroll arrows of the text palette had no color at rest, so
they only showed on hover, while the color palette arrows show in the
secondary color. Give them the same rest color.

AI-assisted-by: claude-opus-5-5
2026-10-26 10:31:14 +01:00
Eva Marco
904f456c61
♻️ Use DS icons in the color palette and its menu (#12444)
* ♻️ Use DS icons in the color palette and its menu

The scroll arrows of the color palette and the tick of the selected
palette in its menu used deprecated icons; use the DS arrow and tick
icons instead. The arrows are full-height scroll buttons with a fade, not
DS button sizes, so they stay raw buttons. The color cells are swatches
and stay as they are.

Add a visual test that opens a file with 24 local colors, so the arrows
show, and captures the palette before and after scrolling and with its
menu open. Its fixture is the library fixture with more colors.

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

* ♻️ Move color palette styles off the deprecated styles

Inline the button, flex and ellipsis mixins, move spacing, sizes and
borders to DS tokens and logical properties, and resolve the color
aliases to their DS tokens. The arrow and menu item colors that change
on hover, disable or selection go through local variables, and the new
icons take them through currentColor instead of svg selectors.

The empty palette message and the menu items use the DS body-small
typography, whose line-height is 1.3 instead of 1.4. The arrows keep
their z-index, now commented: the left arrow comes before the
positioned color list, so without it the colors would cover its fade.
The menu keeps its legacy context menu z-index, also commented.

color_palette.scss and color_palette_ctx_menu.scss no longer import
refactor/.

AI-assisted-by: claude-opus-5-5
2026-10-26 10:27:13 +01:00
David Barragán Merino
14449d9f3d Merge branch 'staging' into develop, keeping the subpath support 2026-10-26 10:03:57 +01:00
David Barragán Merino
7cf914088a Merge branch 'main' into staging 2026-10-26 10:01:26 +01:00
David Barragán Merino
f44566963d ⏪ Revert the subpath support brought by the docker/ backport
The backport of docker/ from develop (2df5f417a) also brought the subpath
support from #12358 and its fix (95bc48a08). It needs the Admin Console
from 2.20, so with the current one every Admin Console URL fails. Restore
the previous nginx configuration and docker-compose.yaml.

AI-assisted-by: Claude
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2.18.5-RC1 2.18.5
2026-10-26 09:59:00 +01:00
Alejandro Alonso
e764b17c31 Merge remote-tracking branch 'origin/staging' into develop 2026-10-26 09:53:59 +01:00
Alejandro Alonso
a0ef927faa
🚑 Drop invoke_* glue checks from staging CI (#12623)
* 🚑 Drop invoke_* glue checks from staging CI

The .github backport from develop added check-no-invoke-v to WASM and
Exporter CI, but staging still ships glue that uses invoke_* wrappers.
Remove those steps (and the WASM production build only used by the
check) so staging CI matches the binary it builds.

* 🔧 Disable credential persistence in commit-checker checkout

zizmor fails CI: Workflows when actions/checkout omits
persist-credentials: false. Match the rest of the workflows.
2026-10-26 07:05:13 +01:00
Andrey Antukh
d9aab9ba79 Merge remote-tracking branch 'origin/main' into staging 2026-10-23 18:43:10 +02:00
Andrey Antukh
d362266694 📖 Add rewrite-commit-message skill 2026-10-23 18:42:48 +02:00
Andrey Antukh
74add05226 Merge remote-tracking branch 'origin/staging' into develop 2026-10-23 13:40:44 +02:00
Luis de Dios
4bf70de73c
🐛 Restrict per-side stroke width tokens to boards and rects in plugin API (#12332)
* 🐛 Fix restrict per-side stroke width tokens to rectangles in plugins API

* ♻️ Reuse per-side stroke restriction check across plugin API

Move the per-side stroke width restriction into the public
`per-side-stroke-attrs-restricted?` helper, now taking `page-id`, and reuse
it from both `shape.applyToken` and the token application path. Keep the
error message in a single `per-side-stroke-widths-message` def used by all
three rejection sites.

Wait for token resolution in the uniform stroke width regression test
instead of relying on a bare `settle`, which could assert before the
applied token lands.

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

---------

Co-authored-by: Luis de Dios <luis.dedios@kaieldos.net>
2026-10-23 13:32:39 +02:00
Andrey Antukh
c04af5ddc5 Merge remote-tracking branch 'origin/main' into staging 2026-10-23 13:11:52 +02:00
Belén Albeza
2045fc4900
🐛 Keep text image fills in place when pasting (#12552)
A text image fill was always laid over the whole text box, so a pasted
image span showed another part of the picture in a box of another size.

Text image fills now honour the image transform, as shape fills do: the
image covers the transform rect instead of the whole box. Paste gives
each pasted image span a transform that keeps the image at the same
pixel offset from the span, like the gradients.

The SVG export emits one linked image per span transform, rather than
one per image id, and the style panel keeps the transform it reads back.

AI-assisted-by: claude-opus-5-5
2026-10-23 13:00:53 +02:00
Andrey Antukh
2df5f417a0 ⏪ Backport .github and docker/ changes from develop 2.18.4-RC5 2.18.4 2026-10-23 12:40:24 +02:00
Andrey Antukh
76a83387d9
⬆️ Update JVM, Node, pnpm and devenv dependencies (#12600)
Move the backend JDK to its latest maintenance build within Java 26 and
refresh the dependency versions pinned by hand in the development and
production images.

- Backend: Zulu JDK 26.0.2 to 26.0.2.1. The jlink module list keeps
  jdk.management, so the prometheus process metrics stay exported.
- Node.js 24.22.1 in .nvmrc, the development image tarball and the two
  plugins deploy workflows that pin it.
- pnpm 12.15.0 in the four Dockerfiles, the 34 packageManager fields and
  the eleven lockfiles.
- Development tools: clj-kondo 2026.10.20, Babashka 1.13.226, Pixi
  0.83.0, GitHub CLI 2.103.0, AWS CLI 2.38.5 and uv 0.13.5.
- Infra services: RustFS 1.0.3, Mailpit v1.32.0, and Valkey pinned to
  8.1.10 instead of the floating 8.1 tag.

Fix SERENA_UPDATE_VERSION in the compose file. A value there makes the
start script reinstall that Serena release on every start, overriding
the one baked into the image; it was replacing 1.7.0 with 1.6.1.

The hardened base image updates are left to Renovate.

Closes #12599

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

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-10-23 12:36:11 +02:00
penpot-renovate[bot]
033b9a3362
⬆️ update GitHub Actions (#12598)
- actions/create-github-app-token v3.3.0 (.github/workflows/auto-label.yml)
- pnpm/setup v3.1.1 (.github/workflows/plugins-deploy-styles-doc.yml)
- pnpm/setup v3.1.1 (.github/workflows/plugins-deploy-api-doc.yml)
- zizmor 1.31.1 (.github/workflows/lint-workflows.yml)

Signed-off-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
Co-authored-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
2026-10-23 11:53:02 +02:00
penpot-renovate[bot]
c3a23174a1
⬆️ Update DHI base images (#12597)
- dhi.io/debian-base trixie-debian13-dev@9b02576 (docker/images/Dockerfile.backend)
- dhi.io/debian-base trixie-debian13-dev@9b02576 (docker/images/Dockerfile.backend)
- dhi.io/debian-base trixie-debian13@de80b46 (docker/imagemagick/Dockerfile)
- dhi.io/debian-base trixie-debian13-dev@9b02576 (docker/imagemagick/Dockerfile)
- dhi.io/debian-base trixie-debian13-dev@9b02576 (docker/devenv/Dockerfile)
- dhi.io/nginx 1.31.7-debian13-dev@940ee35 (docker/images/Dockerfile.frontend)
- dhi.io/node 24.22.1@ (docker/devenv/Dockerfile)
- dhi.io/node 24.22.1@ (.nvmrc)
- dhi.io/node 24.22.1-debian13-dev@20cf36e (docker/images/Dockerfile.media-processor)
- dhi.io/node 24.22.1-debian13@32a2fa6 (docker/images/Dockerfile.mcp)
- dhi.io/node 24.22.1-debian13-dev@20cf36e (docker/images/Dockerfile.mcp)
- dhi.io/node 24.22.1-debian13-dev@20cf36e (docker/images/Dockerfile.exporter)

Signed-off-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
Co-authored-by: penpot-renovate[bot] <338182062+penpot-renovate[bot]@users.noreply.github.com>
2026-10-23 11:50:52 +02:00