From 722290dd7d6354b442444f7671f7422e666eaa1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 4 Aug 2026 11:57:31 +0200 Subject: [PATCH 1/4] :wrench: Release admin-console images on final tags --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c00f841ff..d54b33ec03 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,7 +62,7 @@ jobs: echo "$PUB_DOCKER_PASSWORD" | skopeo login --username "$PUB_DOCKER_USERNAME" --password-stdin docker.io - IMAGES=("frontend" "backend" "exporter" "mcp" "storybook") + IMAGES=("frontend" "backend" "exporter" "admin-console" "mcp" "storybook") SHORT_TAG=${TAG%.*} for image in "${IMAGES[@]}"; do From 7fb15da927170368a8202822db6094f1bf6d0146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 23 Sep 2026 12:32:24 +0200 Subject: [PATCH 2/4] :whale: Bump penpotapp images to 2.18 in docker-compose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: David Barragán Merino --- docker/images/docker-compose.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/images/docker-compose.yaml b/docker/images/docker-compose.yaml index 9d5082b4b4..df846c43b4 100644 --- a/docker/images/docker-compose.yaml +++ b/docker/images/docker-compose.yaml @@ -85,7 +85,7 @@ services: # - "443:443" penpot-frontend: - image: "penpotapp/frontend:${PENPOT_VERSION:-2.17}" + image: "penpotapp/frontend:${PENPOT_VERSION:-2.18}" restart: always ports: - 9001:8080 @@ -124,7 +124,7 @@ services: # PENPOT_DISABLE_IPV6_LISTEN: "true" penpot-backend: - image: "penpotapp/backend:${PENPOT_VERSION:-2.17}" + image: "penpotapp/backend:${PENPOT_VERSION:-2.18}" restart: always volumes: @@ -183,7 +183,7 @@ services: PENPOT_SMTP_SSL: "false" penpot-admin-console: - image: "penpotapp/admin-console:${PENPOT_VERSION:-2.16}" + image: "penpotapp/admin-console:${PENPOT_VERSION:-2.18}" restart: always depends_on: @@ -201,13 +201,13 @@ services: PENPOT_INTERNAL_URI: http://penpot-frontend:8080 penpot-mcp: - image: "penpotapp/mcp:${PENPOT_VERSION:-2.17}" + image: "penpotapp/mcp:${PENPOT_VERSION:-2.18}" restart: always networks: - penpot penpot-exporter: - image: "penpotapp/exporter:${PENPOT_VERSION:-2.17}" + image: "penpotapp/exporter:${PENPOT_VERSION:-2.18}" restart: always depends_on: From c497bbeb0cf2abc537f2011b55099566f6172321 Mon Sep 17 00:00:00 2001 From: Alonso Torres Date: Fri, 25 Sep 2026 09:40:06 +0200 Subject: [PATCH 3/4] :bug: Ignore errors from unknown sources (#11816) --- frontend/src/app/main/errors.cljs | 49 ++++++++++++++- frontend/test/frontend_tests/errors_test.cljs | 59 ++++++++++++++++++- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index c745bebad7..c2689420de 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -588,13 +588,58 @@ (and (string? stack) (str/includes? stack "posthog")))) +;; Captures the script location of a stack frame: the part preceding +;; ":line:column", in both the V8 ("at fn (location:1:2)") and the +;; SpiderMonkey/JSC ("fn@location:1:2") formats. +(def ^:private frame-location-rx + #"(?:at |[@(])([^\s()]+):\d+:\d+") + +(defn- stack-frames + "The frame lines of a stack trace, without the leading message line + that V8 puts before them." + [stack] + (into [] + (comp (map str/trim) + (filter (fn [line] + (or (str/starts-with? line "at ") + (str/includes? line "@"))))) + (str/lines stack))) + +(defn- located-frame? + "True when a stack frame names the script it belongs to. Code injected + into the page carries no script to name and reports ``." + [frame] + (when-let [location (second (re-find frame-location-rx frame))] + (not (str/starts-with? location "<")))) + +(defn- from-injected-code? + "True when the stack is blank, or has frames but none of them names a + script. Either way the error was raised by third-party code injected + into the page (an extension content script, a desktop wrapper, a + bookmarklet) rather than by application code. Firefox, for instance, + raises \"can't access dead object\" with a blank stack when an unloaded + extension's code runs. + + Anything our own code calls keeps our frames further down the stack, so + a stack without a single named script never belongs to us and there is + nothing actionable on our side." + [cause] + (let [stack (.-stack cause)] + (and (string? stack) + (or (str/blank? stack) + (let [frames (stack-frames stack)] + (and (some? (seq frames)) + (not (some located-frame? frames)))))))) + (defn is-ignorable-exception? - "True when the error is known to be harmless (browser extensions, analytics, - React/extension DOM conflicts, etc.) and should NOT be surfaced to the user." + "True when the error is known to be harmless (browser extensions, injected + third-party code, analytics, React/extension DOM conflicts, etc.) and + should NOT be surfaced to the user." [cause] (let [message (ex-message cause)] (or (from-extension? cause) (from-posthog? cause) + (from-injected-code? cause) (= message "Possible side-effect in debug-evaluate") (= message "Unexpected end of input") (str/starts-with? message "invalid props on component") diff --git a/frontend/test/frontend_tests/errors_test.cljs b/frontend/test/frontend_tests/errors_test.cljs index bff54bc113..75a69472e1 100644 --- a/frontend/test/frontend_tests/errors_test.cljs +++ b/frontend/test/frontend_tests/errors_test.cljs @@ -10,8 +10,9 @@ [cljs.test :as t :include-macros true])) (defn- make-error - "Create a JS Error-like object with the given name, message, and optional stack." - [error-name message & {:keys [stack] :or {stack ""}}] + "Create a JS Error-like object with the given name, message, and optional + stack. Without a stack, the error keeps the one the runtime gave it." + [error-name message & {:keys [stack]}] (let [err (js/Error. message)] (set! (.-name err) error-name) (when (some? stack) @@ -93,3 +94,57 @@ (t/testing "Regular TypeError is NOT ignorable" (let [cause (make-error "TypeError" "undefined is not a function")] (t/is (false? (errors/is-ignorable-exception? cause)))))) + +(t/deftest test-ignorable-injected-script + (t/testing "Errors raised by code injected into the page are ignorable" + (let [cause (make-error "TypeError" "Cannot read properties of undefined (reading 'get')" + :stack (str "TypeError: Cannot read properties of undefined (reading 'get')\n" + " at nativeVis (:5:35)\n" + " at HTMLDocument. (:21:64)\n" + " at :17:33"))] + (t/is (true? (errors/is-ignorable-exception? cause)))))) + +(t/deftest test-not-ignorable-injected-script-calling-into-app + (t/testing "Errors whose stack reaches application code are NOT ignorable" + (let [cause (make-error "TypeError" "Cannot read properties of undefined (reading 'get')" + :stack (str "TypeError: Cannot read properties of undefined (reading 'get')\n" + " at nativeVis (:5:35)\n" + " at zLe (https://design.penpot.app/js/main.js:42:13)"))] + (t/is (false? (errors/is-ignorable-exception? cause)))))) + +(t/deftest test-not-ignorable-release-build-v8-stack + (t/testing "Application errors from a release build are NOT ignorable (V8 stack)" + ;; Frames as they appear in production: advanced-compiled names, an + ;; asset URL carrying the version query string, and a trailing frame + ;; without a function name. + (let [cause (make-error "TypeError" "Cannot read properties of null (reading 'toString')" + :stack (str "TypeError: Cannot read properties of null (reading 'toString')\n" + " at $app$main$ui$workspace$shapes$path$editor$path_editor_STAR_$$ " + "(https://design.penpot.app/js/main-workspace.js?version=2.18.0-RC5-1789051786:8580:99)\n" + " at $re (https://design.penpot.app/js/libs.js?version=2.18.0-RC5-1789051786:150:48345)\n" + " at https://design.penpot.app/js/libs.js?version=2.18.0-RC5-1789051786:150:125675"))] + (t/is (false? (errors/is-ignorable-exception? cause)))))) + +(t/deftest test-not-ignorable-release-build-spidermonkey-stack + (t/testing "Application errors from a release build are NOT ignorable (SpiderMonkey/JSC stack)" + ;; SpiderMonkey stacks carry no message line and name the script after + ;; an @ instead of wrapping it in parentheses. + (let [cause (make-error "InternalError" "too much recursion" + :stack (str "$APP.$JSCompiler_prototypeAlias$$.$inode_lookup$" + "@https://design.penpot.app/js/shared.js?version=2.18.0-RC5-1789051786:23076:58\n" + "$app$common$types$container$get_component_shape$cljs$0core$0IFn$0_invoke$0arity$03$$" + "@https://design.penpot.app/js/shared.js?version=2.18.0-RC5-1789051786:7596:1"))] + (t/is (false? (errors/is-ignorable-exception? cause)))))) + +(t/deftest test-ignorable-blank-stack + (t/testing "Errors with a blank stack are ignorable" + ;; Firefox raises "can't access dead object" with an empty stack when + ;; code from an unloaded extension runs in the page. + (let [cause (make-error "TypeError" "can't access dead object" :stack "")] + (t/is (true? (errors/is-ignorable-exception? cause)))))) + +(t/deftest test-not-ignorable-message-only-stack + (t/testing "Errors whose stack holds only the message line are NOT ignorable" + ;; V8 gives native rejections such as a failed fetch no frames at all. + (let [cause (make-error "TypeError" "Failed to fetch" :stack "TypeError: Failed to fetch")] + (t/is (false? (errors/is-ignorable-exception? cause)))))) From a31409617fcc216bd3311ec486a11055f8bf06d6 Mon Sep 17 00:00:00 2001 From: Alonso Torres Date: Mon, 28 Sep 2026 09:08:28 +0200 Subject: [PATCH 4/4] :bug: Fix loop with context menu (#11891) --- frontend/src/app/main/data/workspace.cljs | 10 ++- .../data/workspace_context_menu_test.cljs | 69 +++++++++++++++++++ frontend/test/frontend_tests/runner.cljs | 2 + 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 frontend/test/frontend_tests/data/workspace_context_menu_test.cljs diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index bfa0276ce9..94ee1b5efa 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1253,10 +1253,18 @@ no-bool-shapes? (->> all-selected (some (comp #{:frame :text} :type)))] - (if (and (some? shape) (not (contains? selected (:id shape)))) + (cond + ;; A shape missing from the current page can never become + ;; selected, so selecting it and retrying would recurse forever + (and (some? shape) (not (contains? objects (:id shape)))) + (rx/empty) + + (and (some? shape) (not (contains? selected (:id shape)))) (rx/concat (rx/of (dws/select-shape (:id shape))) (rx/of (show-shape-context-menu params))) + + :else (rx/of (show-context-menu (-> params (assoc diff --git a/frontend/test/frontend_tests/data/workspace_context_menu_test.cljs b/frontend/test/frontend_tests/data/workspace_context_menu_test.cljs new file mode 100644 index 0000000000..f0e1783293 --- /dev/null +++ b/frontend/test/frontend_tests/data/workspace_context_menu_test.cljs @@ -0,0 +1,69 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC Sucursal en España SL + +(ns frontend-tests.data.workspace-context-menu-test + (:require + [app.common.geom.point :as gpt] + [app.common.uuid :as uuid] + [app.main.data.workspace :as dw] + [app.main.data.workspace.selection :as dws] + [beicon.v2.core :as rx] + [cljs.test :as t :include-macros true] + [potok.v2.core :as ptk])) + +(def ^:private file-id (uuid/next)) +(def ^:private page-id (uuid/next)) +(def ^:private rect-id (uuid/next)) + +(defn- make-state + [selected] + {:current-file-id file-id + :current-page-id page-id + :workspace-local {:selected selected} + :files {file-id + {:data + {:pages-index + {page-id + {:objects {uuid/zero {:id uuid/zero :type :frame :shapes [rect-id]} + rect-id {:id rect-id :type :rect :parent-id uuid/zero + :frame-id uuid/zero}}}}}}}}) + +(defn- emitted-types + "Collect the types of the events emitted by the event's watch." + [event state done f] + (let [types (atom [])] + (->> (ptk/watch event state (rx/empty)) + (rx/subs! + #(swap! types conj (ptk/type %)) + (fn [err] + (t/do-report {:type :error :message "Stream error" :actual err}) + (done)) + (fn [] + (f @types) + (done)))))) + +(defn- menu-event + [shape-id] + (dw/show-shape-context-menu {:position (gpt/point 10 10) + :shape {:id shape-id :type :rect}})) + +(t/deftest shape-context-menu-ignores-shape-outside-page + (t/async + done + (emitted-types (menu-event (uuid/next)) (make-state #{}) done + #(t/is (= [] %))))) + +(t/deftest shape-context-menu-selects-unselected-shape + (t/async + done + (emitted-types (menu-event rect-id) (make-state #{}) done + #(t/is (= [::dws/select-shape ::dw/show-shape-context-menu] %))))) + +(t/deftest shape-context-menu-opens-for-selected-shape + (t/async + done + (emitted-types (menu-event rect-id) (make-state #{rect-id}) done + #(t/is (= [::dw/show-context-menu] %))))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 96b79f0419..31ef564cba 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -19,6 +19,7 @@ [frontend-tests.data.viewer-test] [frontend-tests.data.workspace-colors-test] [frontend-tests.data.workspace-comments-test] + [frontend-tests.data.workspace-context-menu-test] [frontend-tests.data.workspace-interactions-test] [frontend-tests.data.workspace-mcp-test] [frontend-tests.data.workspace-media-test] @@ -123,6 +124,7 @@ 'frontend-tests.data.viewer-test 'frontend-tests.data.workspace-colors-test 'frontend-tests.data.workspace-comments-test + 'frontend-tests.data.workspace-context-menu-test 'frontend-tests.data.workspace-interactions-test 'frontend-tests.data.workspace-mcp-test 'frontend-tests.data.workspace-media-test