Support currently fixes wrong profile emails and false bounce marks with
raw SQL. Add update-profile-email! (rejects collisions) and
clear-email-complaint-reports! (deletes global and profile reports and
unmutes the matching profile) so these ops run through the REPL.
Closes#12663
The Nextcloud link to the enterprise migration guide no longer works.
Point it to the new share.
The box also had a bold "Open the full guide (PDF)" line that looked
clickable but was not, with the real link below it. Keep a single
link and mark it as a PDF at the end of the text.
AI-assisted-by: claude-opus-5-5
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>
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>
* 🐛 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
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
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
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
* ✨ 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>
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
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>
* 🚑 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.
* 🐛 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>
Text image shaders are baked into Skia paragraphs at layout time.
touch_shapes_with_image only scanned shape.fills/strokes, so text
spans were never invalidated after store_image. Detect text content
image fills and force a layout rebuild before touching tiles.
Closes#12563
* 🐛 Skip unknown entries when importing shortcuts
The shortcuts import rejected the whole file when it held a single entry
it did not know, so a profile exported from an older Penpot version, or
edited by hand, could not be imported at all. The spec in #9924 asks to
skip unknown keys, tell the user which ones, and apply the rest.
Importing now keeps the entries that map to a known shortcut with a valid
key combination and skips the rest: unknown contexts, unknown shortcut
keys, and commands that are not a key combination. The import modal
lists the skipped entries. The file is only rejected when it is not a
map or holds nothing that can be applied.
A context whose entries are all skipped is left out: an empty context
clears the user's shortcuts on import, so keeping it would wipe them.
valid-command? reads a command the way mousetrap does, so it accepts
every default shortcut and rejects strings mousetrap could never match.
AI-assisted-by: claude-opus-5-5
* 📎 Sort requires in shortcuts settings test
cljfmt expects the namespace requires in alphabetical order.
AI-assisted-by: claude-opus-5-5
* ✨ Reserve cancel, copy and paste shortcuts
Cancel, copy and paste are no longer customizable, in the workspace and
in the path editor, which shares their shortcut keys. Their key
combinations (Escape, Ctrl/Cmd+C, Ctrl/Cmd+V) are now reserved: the
shortcut recorder warns and blocks saving one of them for another
action, and the import skips entries that use them.
Reserved combinations come from the non-customizable workspace
shortcuts, so marking another one as non-customizable reserves its key
too. The path editor's cancel also binds V, the move tool key, so path
shortcuts are left out. Combinations compare the way mousetrap matches
them: aliases and modifier order do not matter.
apply-custom-overrides now ignores overrides of non-customizable
shortcuts. An override saved before this change would otherwise still
apply, with no way to reset it from the settings page.
AI-assisted-by: claude-opus-5-5
* 🐛 Pass version tag to worker so it loads the current render wasm
* 🐛 Keep wasm thumbnail queue alive when one render fails
* 🐛 Fail wasm thumbnail request when canvas context cannot init
* ✨ Support running several exporter replicas
* 📚 Document one exporter per machine instead of compose replicas
* 📚 Trim exporter replica docs to the essentials
* ✨ Cancel an exporter's jobs when it shuts down
* 📚 Replace exporter replica docs with a short beta note
* 🐛 Fix legacy path migrations on content without coordinates
PRE still returned 500 on get-team-shared-files after #12357, now with
"expected rect instance" in legacy-6. When a path has no points, legacy-6
rebuilds its rect from the content, but content whose points are not
numbers (NaN or infinite coordinates, segments without params, only a
close path) gives no rect either, and rect->points rejects nil.
Fall back to the empty rect in legacy-6. A path with segments missing
coordinates then failed later, in 0003-convert-path-content-v2, since the
binary path data can not store them; drop those segments there.
Relates to #12343
AI-assisted-by: claude-opus-5-5
* 🐛 Skip shared files that fail to load in get-team-shared-files
get-team-shared-files loads and migrates every shared file of the team,
so one file whose migration fails made the whole request return 500 and
the "Add library" modal could not open.
Catch the error per file, log it with the file and team ids, and leave
that file out of the list. The other libraries still show, and the error
report names the broken file.
Closes#12343
AI-assisted-by: claude-opus-5-5
`SET STORAGE DEFAULT` needs PostgreSQL 16, but the official docker
images pin `postgres:15`, so migration 0156 failed on those
deployments. The migration now uses `SET STORAGE EXTENDED`, the
storage mode of the type of every column it touches (text, text[],
jsonb and bytea), which leaves the catalog in the same state on any
supported version.
The documented PostgreSQL floor goes back to >=15, the reason for
raising it was that syntax.
Verified on the test database: the 62 columns reset and no table
column keeps the legacy EXTERNAL storage.
The backend memory now records that migrations must stay within
PostgreSQL 15 syntax.
AI-assisted-by: minimax-m3.1-flash-free
The prod-infra memory now says that the `$host` multi-variable needs
its `query`, its `options` list and its `allValue` regex updated
together. A host present only in `query` is dropped when `All` is
selected, so the panels that filter with `instance=~"$host..."` stop
showing it. Panel 104 is the visible symptom.
AI-assisted-by: minimax-m3.1-flash-free
`app.redis/del` logged `:key key`, but `key` is not bound in that
`let`: it resolved to `clojure.core/key`, so the line carried
`#object[clojure.core$key ...]` instead of the keys that failed to be
deleted. Log `:keys keys`, which is always the normalized vector, so a
single key and a collection report the same shape.
The tests pin the line: the keys, the hint, the error level, the logger
and the timeout as cause, plus the `nil` the caller gets.
The prod-infra memory now notes that client-side redis timeouts mean the
JVM is paused, not that the server is slow.
AI-assisted-by: deepseek-v4.1-flash
* 🐛 Fix render_to_pdf call in the ToUnicode test
The test passed a scale argument that render_to_pdf does not take, so
the render-wasm test build failed to compile.
* 🐛 Draw masked group inner shadows in PDF and multi-element export
The CPU renderer behind PDF and multi-element raster export drew a
masked group's drop shadow but never its inner shadow, so the masked
content came out flat. Wrap the masked content in a layer that draws
the inner shadows over it, as the GPU single export does.
* 🐛 Export every visible inner shadow of a masked group
The workspace skips shadows too small to see at the current zoom, to
render faster. Export has no such need, so draw every visible inner
shadow instead of filtering by scale.
Noto Sans Symbols lacks most arrows and math operators. Fall back to
Noto Sans Math for them, and to Noto Sans for letterlike symbols.
Closes#12469
AI-assisted-by: claude-opus-5-5
WASM find_guide_at only checked distance on the guide axis, so board-bound
guides stayed interactive outside their board. Respect frame_range in
GuidePool hit testing so interaction matches the drawn solid segment and
the SVG hit area.
Closes#12322
Skia maps ligatures and other substituted glyphs to U+0000 in the PDF
ToUnicode maps. Rebuild those maps from the laid-out paragraphs and
append them as an incremental update.
Closes#12393
The exception page compared the screen query param against literals
that never occur ("view", "dashboard"), so viewer and dashboard urls
of inaccessible files and teams fell through to the plain not-found
page instead of the request access modal. Classify the param with a
tested helper that recognizes "viewer" and every "dashboard-*" route.
Closes#11224
AI-assisted-by: muse-spark-1.3-contributor
Add the missing 2.18.4 entries for the closed storage GC,
version quota and file_change revision issues, with their
fix PRs referenced inline.
AI-assisted-by: muse-spark-1.3-contributor
Drop the legacy TOAST STORAGE EXTERNAL overrides set when PostgreSQL
ran on ZFS with transparent compression. Migration 0156 restores the
type default (SET STORAGE DEFAULT) on 62 columns across 21 tables;
existing TOAST data keeps its strategy until rewritten.
Raise the documented PostgreSQL floor to >=16, required by the new
SET STORAGE DEFAULT syntax.
AI-assisted-by: muse-spark-1.3-contributor
Inner shadows were joined into extrect via the drop-shadow filter, so a
Y offset grew the PNG/SVG export surface. Only drop
shadows expand bounds, matching classic export. Closes#12386.
Comment out the media-storage-bytes-per-team check in
upload-file-media-object. The SUM over storage_object takes
3-12s per upload on prod (3.5TB DB, ~34 slow runs/hour)
and blocks uploads. Keep the SQL and quote definition
for re-enable with a cheaper implementation.
Update the upload quota test to document the disabled
state until the new implementation lands.
AI-assisted-by: muse-spark-1.3-contributor
A modified shape is a cached copy, and edits through get_mut did not
reset it. After Enter the preview stops sending modifiers while the size
holds, so the canvas kept painting the old text.
AI-assisted-by: claude-opus-5-5