Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2026-06-02 17:50:45 +02:00
commit feca7cef41
14 changed files with 268 additions and 43 deletions

View File

@ -255,6 +255,7 @@ jobs:
PENPOT_TEST_REDIS_URI: "redis://redis/1"
run: |
mkdir -p /tmp/penpot;
clojure -M:dev:test --reporter kaocha.report/documentation
test-library:

View File

@ -312,10 +312,12 @@
(defn del-page
[changes page]
(-> changes
(update :redo-changes conj {:type :del-page :id (:id page)})
(update :undo-changes conj {:type :add-page :id (:id page) :page page})
(apply-changes-local)))
(let [page-id (:id page)]
(assert (some? page-id) "page must have a valid :id")
(-> changes
(update :redo-changes conj {:type :del-page :id page-id})
(update :undo-changes conj {:type :add-page :id page-id :page page})
(apply-changes-local))))
(defn move-page
[changes page-id index prev-index]

View File

@ -57,9 +57,9 @@
{:result :update :updated-shape (dissoc shape :component-root)})
{:result :keep})))
(catch #?(:clj Throwable :cljs :default) e
(log/error :msg "Failed to normalize :component-root on shapes"
:file-id (:id file-data)
:cause e)
(log/warn :msg "Failed to normalize :component-root on shapes"
:file-id (:id file-data)
:cause e)
file-data)))
(defn fix-missing-swap-slots
@ -89,9 +89,9 @@
{:result :keep}))
{:result :keep})))
(catch #?(:clj Throwable :cljs :default) e
(log/error :msg "Failed to fix missing swap slots on shapes"
:file-id (:id file-data)
:cause e)
(log/warn :msg "Failed to fix missing swap slots on shapes"
:file-id (:id file-data)
:cause e)
file-data)))
(defn sync-component-id-with-ref-shape
@ -136,9 +136,9 @@
{:result :keep}))
{:result :keep})))
(catch #?(:clj Throwable :cljs :default) e
(log/error :msg "Failed to sync component id and file with ref shape"
:file-id (:id file-data)
:cause e)
(log/warn :msg "Failed to sync component id and file with ref shape"
:file-id (:id file-data)
:cause e)
file-data)))]
;; If a copy inside a main is updated, we need to repeat the process for the change to be
;; propagated to all copies.

View File

@ -44,7 +44,7 @@ export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
export PENPOT_NITRATE_URI=${PENPOT_NITRATE_URI:-http://penpot-nitrate:3000}
export PENPOT_HTTP_SERVER_MAX_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_BODY_SIZE:-367001600} # Default to 350MiB
export PENPOT_IPV6_LISTEN_DIRECTIVE=${PENPOT_IPV6_LISTEN_DIRECTIVE:-"listen [::]:8080 default_server;"}
export PENPOT_IPV6_LISTEN_DIRECTIVE=${PENPOT_IPV6_LISTEN_DIRECTIVE:-"listen [::]:8080 default_server reuseport backlog=16384;"}
if [ "${PENPOT_DISABLE_IPV6_LISTEN}" = "true" ]; then
export PENPOT_IPV6_LISTEN_DIRECTIVE=""
fi

View File

@ -3,7 +3,7 @@ pid /tmp/nginx.pid;
include /etc/nginx/overrides/main.d/*.conf;
events {
worker_connections 2048;
worker_connections 65535;
multi_accept on;
}
@ -72,7 +72,7 @@ http {
include /etc/nginx/overrides/http.d/*.conf;
server {
listen 8080 default_server;
listen 8080 default_server reuseport backlog=16384;
${PENPOT_IPV6_LISTEN_DIRECTIVE}
server_name _;

View File

@ -0,0 +1,143 @@
{
"~:features": {
"~#set": [
"fdata/path-data",
"plugins/runtime",
"design-tokens/v1",
"variants/v1",
"layout/grid",
"styles/v2",
"fdata/objects-map",
"tokens/numeric-input",
"render-wasm/v1",
"components/v2",
"fdata/shape-data-type"
]
},
"~:team-id": "~u99e49e93-362f-80ef-8007-3450ea52c9a4",
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true,
"~:can-read": true,
"~:is-logged": true
},
"~:has-media-trimmed": false,
"~:comment-thread-seqn": 0,
"~:name": "New File 11",
"~:revn": 5,
"~:modified-at": "~m1780390315939",
"~:vern": 838776213,
"~:id": "~uf3a9df94-b15a-80eb-8008-1d22a57a9f94",
"~:is-shared": false,
"~:migrations": {
"~#ordered-set": [
"legacy-2",
"legacy-3",
"legacy-5",
"legacy-6",
"legacy-7",
"legacy-8",
"legacy-9",
"legacy-10",
"legacy-11",
"legacy-12",
"legacy-13",
"legacy-14",
"legacy-16",
"legacy-17",
"legacy-18",
"legacy-19",
"legacy-25",
"legacy-26",
"legacy-27",
"legacy-28",
"legacy-29",
"legacy-31",
"legacy-32",
"legacy-33",
"legacy-34",
"legacy-36",
"legacy-37",
"legacy-38",
"legacy-39",
"legacy-40",
"legacy-41",
"legacy-42",
"legacy-43",
"legacy-44",
"legacy-45",
"legacy-46",
"legacy-47",
"legacy-48",
"legacy-49",
"legacy-50",
"legacy-51",
"legacy-52",
"legacy-53",
"legacy-54",
"legacy-55",
"legacy-56",
"legacy-57",
"legacy-59",
"legacy-62",
"legacy-65",
"legacy-66",
"legacy-67",
"0001-remove-tokens-from-groups",
"0002-normalize-bool-content-v2",
"0002-clean-shape-interactions",
"0003-fix-root-shape",
"0003-convert-path-content-v2",
"0005-deprecate-image-type",
"0006-fix-old-texts-fills",
"0008-fix-library-colors-v4",
"0009-clean-library-colors",
"0009-add-partial-text-touched-flags",
"0010-fix-swap-slots-pointing-non-existent-shapes",
"0011-fix-invalid-text-touched-flags",
"0012-fix-position-data",
"0013-fix-component-path",
"0013-clear-invalid-strokes-and-fills",
"0014-fix-tokens-lib-duplicate-ids",
"0014-clear-components-nil-objects",
"0015-fix-text-attrs-blank-strings",
"0015-clean-shadow-color",
"0016-copy-fills-from-position-data-to-text-node",
"0017-fix-layout-flex-dir",
"0018-remove-unneeded-objects-from-components",
"0019-fix-missing-swap-slots",
"0020-sync-component-id-with-near-main",
"0021-fix-shape-svg-attrs",
"0022-normalize-component-root-and-resync"
]
},
"~:version": 67,
"~:project-id": "~ucd8f7672-e5d1-810f-8007-87e124eda82a",
"~:created-at": "~m1780389392874",
"~:backend": "legacy-db",
"~:data": {
"~:pages": [
"~uf3a9df94-b15a-80eb-8008-1d22a57a9f95"
],
"~:pages-index": {
"~uf3a9df94-b15a-80eb-8008-1d22a57a9f95": {
"~:objects": {
"~#penpot/objects-map/v2": {
"~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^H\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~uf78d0909-e64a-8018-8008-1d22cc6ebe3d\",\"~u9da48676-d394-80d9-8008-1d26279cf232\"]]]",
"~uf78d0909-e64a-8018-8008-1d22cc6ebe3d": "[\"~#shape\",[\"^ \",\"~:y\",220,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",188,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",774,\"~:y\",220]],[\"^<\",[\"^ \",\"~:x\",962,\"~:y\",220]],[\"^<\",[\"^ \",\"~:x\",962,\"~:y\",332]],[\"^<\",[\"^ \",\"~:x\",774,\"~:y\",332]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~uf78d0909-e64a-8018-8008-1d22cc6ebe3d\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",774,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",774,\"~:y\",220,\"^8\",188,\"~:height\",112,\"~:x1\",774,\"~:y1\",220,\"~:x2\",962,\"~:y2\",332]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",112,\"~:flip-y\",null]]",
"~u9da48676-d394-80d9-8008-1d26279cf232": "[\"~#shape\",[\"^ \",\"~:y\",-170,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Ellipse\",\"~:width\",110,\"~:type\",\"~:circle\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",664,\"~:y\",-170]],[\"^<\",[\"^ \",\"~:x\",774,\"~:y\",-170]],[\"^<\",[\"^ \",\"~:x\",774,\"~:y\",-99]],[\"^<\",[\"^ \",\"~:x\",664,\"~:y\",-99]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~u9da48676-d394-80d9-8008-1d26279cf232\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",664,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",664,\"~:y\",-170,\"^8\",110,\"~:height\",71,\"~:x1\",664,\"~:y1\",-170,\"~:x2\",774,\"~:y2\",-99]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^F\",71,\"~:flip-y\",null]]"
}
},
"~:id": "~uf3a9df94-b15a-80eb-8008-1d22a57a9f95",
"~:name": "Page 1"
}
},
"~:id": "~uf3a9df94-b15a-80eb-8008-1d22a57a9f94",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
}
}
}

View File

@ -0,0 +1,34 @@
[
{
"~:revn": 3,
"~:modified-at": "~m1780390308760",
"~:deleted-at": "~m1780476708760",
"~:created-by": "system",
"~:label": "snapshot-2026-06-02-08-51-48-760883637",
"~:id": "~uf3a9df94-b15a-80eb-8008-1d2623e61ed3",
"~:profile-id": "~u99e49e93-362f-80ef-8007-3450ea5204aa",
"~:version": 67,
"~:created-at": "~m1780390308760"
},
{
"~:revn": 0,
"~:modified-at": "~m1780389436291",
"~:deleted-at": "~m1780475836238",
"~:created-by": "system",
"~:label": "internal/snapshot/0",
"~:id": "~uf3a9df94-b15a-80eb-8008-1d22cfe0f1d4",
"~:profile-id": "~u99e49e93-362f-80ef-8007-3450ea5204aa",
"~:version": 67,
"~:created-at": "~m1780389436291"
},
{
"~:id": "~uf3a9df94-b15a-80eb-8008-1d22d3c4857a",
"~:label": "Inicial",
"~:revn": 1,
"~:version": 67,
"~:created-at": "~m1780389440274",
"~:modified-at": "~m1780389440274",
"~:created-by": "user",
"~:profile-id": "~u99e49e93-362f-80ef-8007-3450ea5204aa"
}
]

View File

@ -0,0 +1,9 @@
[
{
"~:id": "~u99e49e93-362f-80ef-8007-3450ea5204aa",
"~:email": "leia@example.com",
"~:name": "Leia Organa",
"~:fullname": "Leia Organa",
"~:is-active": true
}
]

View File

@ -147,3 +147,23 @@ test("BUG 13385 - Fix viewport not updating when restoring version", async ({ pa
// assert that the circle shape exists
await expect(workspacePage.layers.getByText("Ellipse")).toBeVisible();
});
test("BUG 14289 - Fix WASM crash when restoring version directly without preview", async ({ page }) => {
const workspacePage = new WasmWorkspacePage(page);
await workspacePage.setupEmptyFile();
await workspacePage.mockGetFile("versions/get-file-14289.json");
await workspacePage.mockRPC("get-profiles-for-file-comments?file-id=*", "versions/get-profiles-for-file-comments-14289.json");
await workspacePage.mockRPC("get-file-snapshots?file-id=*", "versions/get-file-snapshots-14289.json");
await workspacePage.goToWorkspace();
await workspacePage.rightSidebar.getByRole("button", { name: "History" }).click();
await workspacePage.rightSidebar.getByRole("button", { name: "Open version menu" }).click();
await workspacePage.rightSidebar.getByRole("button", { name: "Restore" }).click();
const dismissButton = workspacePage.page.getByRole("button", { name: /Dismiss/i });
await dismissButton.click();
await expect(dismissButton).toBeHidden();
await expect(workspacePage.viewport).toBeVisible();
});

View File

@ -274,7 +274,8 @@
(ptk/reify ::navigate-to-comment-id
ptk/WatchEvent
(watch [_ state _]
(let [file-id (:current-file-id state)]
(if-let [file-id (:current-file-id state)]
(->> (rp/cmd! :get-comment-threads {:file-id file-id})
(rx/map #(d/seek (fn [{:keys [id]}] (= thread-id id)) %))
(rx/map navigate-to-comment))))))
(rx/map navigate-to-comment))
(rx/empty)))))

View File

@ -379,15 +379,18 @@
pages (:pages fdata)
index (d/index-of pages id)
page (get pindex id)
page (assoc page :index index)
pages (filter #(not= % id) pages)
page (get pindex id)]
changes (-> (pcb/empty-changes it)
(pcb/with-library-data fdata)
(delete-page-components page)
(pcb/del-page page))]
(if (nil? page)
(rx/empty)
(let [page (assoc page :index index)
pages (filter #(not= % id) pages)
(rx/of (dch/commit-changes changes)
(when (= id (:current-page-id state))
(dcm/go-to-workspace {:page-id (first pages)})))))))
changes (-> (pcb/empty-changes it)
(pcb/with-library-data fdata)
(delete-page-components page)
(pcb/del-page page))]
(rx/of (dch/commit-changes changes)
(when (= id (:current-page-id state))
(dcm/go-to-workspace {:page-id (first pages)})))))))))

View File

@ -176,24 +176,35 @@
;; RESTORE VERSION EVENTS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn exit-preview
"Exit from preview mode and reload the live file data"
(defn- exit-preview-cleanup
"Restore the backed-up live file data and clear the preview flags."
[]
(ptk/reify ::exit-preview
(ptk/reify ::exit-preview-cleanup
ptk/UpdateEvent
(update [_ state]
(let [backup (dm/get-in state [:workspace-versions :backup])]
(-> state
(update :workspace-versions dissoc :backup)
(update :workspace-global dissoc :read-only? :preview-id)
(update :files assoc (:id backup) backup))))
(update :files assoc (:id backup) backup))))))
(defn exit-preview
"Exit from preview mode and reload the live file data.
No-op when there is no preview to exit (no backup stored), so it is
safe to call from the restore dialog dismiss action even when the
restore was triggered directly without entering preview first."
[]
(ptk/reify ::exit-preview
ptk/WatchEvent
(watch [_ state _]
(let [file-id (:current-file-id state)
page-id (:current-page-id state)]
(rx/of (dwpg/initialize-page file-id page-id))))))
;; Ensure we are actually in preview mode. Otherwise there
;; is no backup to restore and wasm crashes
(when (dm/get-in state [:workspace-versions :backup])
(let [file-id (:current-file-id state)
page-id (:current-page-id state)]
(rx/of (exit-preview-cleanup)
(dwpg/initialize-page file-id page-id)))))))
(defn- restore-version
[id]

View File

@ -52,7 +52,7 @@ pub fn render_wasm_label(render_state: &mut RenderState) {
}
let canvas = render_state.surfaces.canvas(SurfaceId::Target);
let skia::ISize { width, height } = canvas.base_layer_size();
let canvas_width = canvas.base_layer_size().width as f32;
let mut paint = skia::Paint::default();
paint.set_color(skia::Color::GRAY);
@ -61,18 +61,19 @@ pub fn render_wasm_label(render_state: &mut RenderState) {
} else {
"WebGL rendering"
};
let dpr = render_state.options.dpr;
let (scalar, _) = render_state.fonts.debug_font().measure_str(str, None);
let mut p = skia::Point::new(width as f32 - 25.0 - scalar, height as f32 - 25.0);
let mut p = skia::Point::new(canvas_width - (20.0 * dpr) - scalar, 50.0 * dpr);
let debug_font = render_state.fonts.debug_font();
canvas.draw_str(str, p, debug_font, &paint);
if render_state.options.is_text_editor_v3() {
str = "TEXT EDITOR / V3";
str = "Text Editor v3";
let (scalar, _) = render_state.fonts.debug_font().measure_str(str, None);
p.x = width as f32 - 25.0 - scalar;
p.y -= 20.0;
p.x = canvas_width - (20.0 * dpr) - scalar;
p.y += 15.0 * dpr;
canvas.draw_str(str, p, debug_font, &paint);
}
}

View File

@ -39,7 +39,7 @@ impl FontStore {
"Failed to match default font".to_string(),
))?;
let debug_font = skia::Font::new(debug_typeface, 10.0);
let debug_font = skia::Font::new(debug_typeface, 12.0);
Ok(Self {
font_mgr,
@ -51,7 +51,7 @@ impl FontStore {
}
pub fn set_scale_debug_font(&mut self, dpr: f32) {
let debug_font = skia::Font::new(self.debug_font.typeface(), 10.0 * dpr);
let debug_font = skia::Font::new(self.debug_font.typeface(), 12.0 * dpr);
self.debug_font = debug_font;
}